├── .DS_Store ├── .env.example ├── .eslintignore ├── .eslintrc.js ├── .github ├── CODEOWNERS └── workflows │ ├── coverage.yml │ ├── forge-test.yml │ └── slither.yml ├── .gitignore ├── .gitmodules ├── .npmignore ├── .prettierignore ├── .prettierrc ├── .solcover.js ├── .solhint.json ├── .solhint.json.sol ├── .solhintignore ├── .vscode └── settings.json ├── LICENCE.md ├── README.md ├── deploy └── deploy.ts ├── deployments ├── goerli │ ├── .chainId │ ├── HookCoveredCallFactory.json │ ├── HookCoveredCallImplV1.json │ ├── HookERC721MultiVaultImplV1.json │ ├── HookERC721VaultFactory.json │ ├── HookERC721VaultImplV1.json │ ├── HookProtocol.json │ ├── HookUpgradeableBeacon.json │ ├── TokenURI.json │ └── solcInputs │ │ └── bce784fa557baf3b8bfd2d09431e04e2.json ├── mainnet │ ├── .chainId │ ├── .migrations.json │ ├── Font1.json │ ├── Font2.json │ ├── Font3.json │ ├── HookCoveredCallFactory.json │ ├── HookCoveredCallImplV1.json │ ├── HookERC721MultiVaultImplV1.json │ ├── HookERC721VaultFactory.json │ ├── HookERC721VaultImplV1.json │ ├── HookProtocol.json │ ├── HookUpgradeableBeacon.json │ ├── TokenURI.json │ └── solcInputs │ │ └── 27cdac36318da08ab52a4db662ee1263.json ├── ropsten-old │ ├── .chainId │ ├── .migrations.json │ ├── HookCoveredCallFactory.json │ ├── HookCoveredCallImplV1.json │ ├── HookERC721MultiVaultImplV1.json │ ├── HookERC721VaultFactory.json │ ├── HookERC721VaultImplV1.json │ ├── HookProtocol.json │ ├── HookUpgradeableBeacon.json │ ├── TokenURI.json │ └── solcInputs │ │ └── f9079719c55ec367359fccdad37d4c49.json └── ropsten │ ├── .chainId │ ├── .migrations.json │ ├── HookCoveredCallFactory.json │ ├── HookCoveredCallImplV1.json │ ├── HookERC721MultiVaultImplV1.json │ ├── HookERC721VaultFactory.json │ ├── HookERC721VaultImplV1.json │ ├── HookProtocol.json │ ├── HookUpgradeableBeacon.json │ ├── TokenURI.json │ └── solcInputs │ └── bce784fa557baf3b8bfd2d09431e04e2.json ├── docs ├── Definitions.md ├── Overview.md └── generated │ ├── index.html │ ├── main.js │ └── main.js.LICENSE.txt ├── foundry.toml ├── hardhat.config.ts ├── img ├── hook-protocol-banner.png └── option-flow-diagram.svg ├── integration ├── helpers │ └── index.ts └── integration.test.ts ├── package-lock.json ├── package.json ├── remappings.txt ├── slither.config.json ├── slither.db.json ├── src ├── .prettierrc ├── HookBeaconProxy.sol ├── HookBidPool.sol ├── HookCoveredCallFactory.sol ├── HookCoveredCallImplV1.sol ├── HookERC721MultiVaultImplV1.sol ├── HookERC721VaultFactory.sol ├── HookERC721VaultImplV1.sol ├── HookProtocol.sol ├── HookUpgradeableBeacon.sol ├── interfaces │ ├── IERC721FlashLoanReceiver.sol │ ├── IHookCoveredCall.sol │ ├── IHookCoveredCallFactory.sol │ ├── IHookERC20Vault.sol │ ├── IHookERC721Vault.sol │ ├── IHookERC721VaultFactory.sol │ ├── IHookOption.sol │ ├── IHookProtocol.sol │ ├── IHookVault.sol │ ├── IInitializeableBeacon.sol │ ├── IWETH.sol │ ├── delegate-cash │ │ ├── IDelegationRegistry.sol │ │ └── README.md │ └── zeroex-v4 │ │ └── IPropertyValidator.sol ├── lib │ ├── BeaconSalts.sol │ ├── Entitlements.sol │ ├── HookStrings.sol │ ├── PoolOrders.sol │ ├── Signatures.sol │ ├── TokenURI.sol │ ├── lyra │ │ ├── BlackScholes.sol │ │ ├── FixedPointMathLib.sol │ │ ├── Math.sol │ │ └── README.md │ └── synthetix │ │ ├── DecimalMath.sol │ │ └── SignedDecimalMath.sol ├── mixin │ ├── EIP712.sol │ ├── HookInstrumentERC721.sol │ └── PermissionConstants.sol └── test │ ├── HookBidPoolTest.t.sol │ ├── HookCoveredCallBiddingRevertTests.t.sol │ ├── HookCoveredCallIntegrationTest.t.sol │ ├── HookCoveredCallTests.t.sol │ ├── HookMultiVaultTests.t.sol │ ├── HookVaultTests.t.sol │ ├── pnm │ ├── HookCoveredCallBidTests.t.sol.skip │ ├── HookVaultStorageTest.t.sol.skip │ └── base.t.sol │ └── utils │ ├── base.t.sol │ ├── mocks │ ├── FlashLoan.sol │ ├── MaliciousBidder.sol │ ├── PropertyValidator1.sol │ └── PropertyValidatorReverts.sol │ └── tokens │ ├── TestERC721.sol │ └── WETH.sol └── tsconfig.json /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hookart/protocol/de64025fcf17d26d6f23775bc6d327148215758b/.DS_Store -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | ETHERSCAN_API_KEY=ABC123ABC123ABC123ABC123ABC123ABC1 2 | ROPSTEN_URL=https://eth-ropsten.alchemyapi.io/v2/ 3 | PRIVATE_KEY=0xabc123abc123abc123abc123abc123abc123abc123abc123abc123abc123abc1 4 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | artifacts 3 | cache 4 | coverage 5 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: false, 4 | es2021: true, 5 | mocha: true, 6 | node: true, 7 | }, 8 | plugins: ["@typescript-eslint"], 9 | extends: [ 10 | "standard", 11 | "plugin:prettier/recommended", 12 | "plugin:node/recommended", 13 | ], 14 | parser: "@typescript-eslint/parser", 15 | parserOptions: { 16 | ecmaVersion: 12, 17 | }, 18 | rules: { 19 | "node/no-unsupported-features/es-syntax": [ 20 | "error", 21 | { ignores: ["modules"] }, 22 | ], 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | @jake-nyquist 2 | @regynald 3 | -------------------------------------------------------------------------------- /.github/workflows/coverage.yml: -------------------------------------------------------------------------------- 1 | name: coverage 2 | 3 | on: [push] 4 | 5 | jobs: 6 | coverage: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - name: Check out 11 | uses: actions/checkout@v3 12 | - uses: actions/setup-node@v3 13 | - run: npm ci 14 | - run: npx hardhat coverage 15 | -------------------------------------------------------------------------------- /.github/workflows/forge-test.yml: -------------------------------------------------------------------------------- 1 | name: Build and Test Contracts 2 | 3 | on: [push] 4 | 5 | jobs: 6 | check: 7 | name: Foundry project 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout repo 11 | uses: actions/checkout@v3 12 | with: 13 | submodules: recursive 14 | 15 | - name: Install Foundry 16 | uses: foundry-rs/foundry-toolchain@v1 17 | with: 18 | version: nightly 19 | 20 | - uses: actions/setup-node@v3 21 | - run: npm ci 22 | 23 | - name: Run tests 24 | run: forge test -vvv 25 | -------------------------------------------------------------------------------- /.github/workflows/slither.yml: -------------------------------------------------------------------------------- 1 | name: Slither Analysis 2 | 3 | on: [push] 4 | 5 | jobs: 6 | analyze: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Checkout Repository 10 | uses: actions/checkout@v3 11 | with: 12 | submodules: recursive 13 | 14 | - name: Run Slither 15 | uses: crytic/slither-action@v0.1.1 16 | # [NOTE]: continue-on-error ignores warnings. We are using all available Detectors for every Severity. 17 | continue-on-error: true 18 | # id: slither 19 | # with: 20 | # sarif: results.sarif 21 | 22 | # [TODO]: Configure repository to allow us using SARIF or wait for the repo to be public. 23 | # This will allow us to get proper visual report of the analysis done by Slither. 24 | # - name: Upload SARIF file 25 | # uses: github/codeql-action/upload-sarif@v1 26 | # with: 27 | # sarif_file: ${{ steps.slither.outputs.sarif }} 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | cache/ 2 | out/ 3 | 4 | node_modules 5 | .env 6 | coverage 7 | coverage.json 8 | typechain 9 | 10 | #Hardhat files 11 | cache 12 | artifacts 13 | cache-hardhat/ 14 | 15 | #Narya files 16 | reports/ 17 | pnm-cov.json 18 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/forge-std"] 2 | path = lib/forge-std 3 | url = https://github.com/foundry-rs/forge-std 4 | ignore = dirty 5 | [submodule "lib/narya-contracts"] 6 | path = lib/narya-contracts 7 | url = https://github.com/NaryaAI/narya-contracts 8 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | hardhat.config.ts 2 | scripts 3 | test 4 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | artifacts 3 | cache 4 | coverage* 5 | gasReporterOutput.json 6 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /.solcover.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | skipFiles: ["test", "mixin/HookInstrumentERC721.sol", "lib/HookStrings.sol", "lib/lyra", "lib/synthetix"] 3 | }; -------------------------------------------------------------------------------- /.solhint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solhint:recommended", 3 | "rules": { 4 | "compiler-version": ["error", "^0.8.0"], 5 | "func-visibility": ["warn", { "ignoreConstructors": true }] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.solhint.json.sol: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solhint:default" 3 | } 4 | -------------------------------------------------------------------------------- /.solhintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.enableFiletypes": [ 3 | "solidity" 4 | ], 5 | "cSpell.words": [ 6 | "flashloan", 7 | "IERC", 8 | "IWETH", 9 | "keccak", 10 | "Nyquist", 11 | "Permissionlessly", 12 | "struct", 13 | "timelocks" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /LICENCE.md: -------------------------------------------------------------------------------- 1 | Copyright 2022 Abstract Labs, Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hook Protocol 2 | 3 | ![Hook](img/hook-protocol-banner.png) 4 | 5 | ## About 6 | 7 | Hook is an oracle-free, on-chain option protocol for non-fungible tokens (NFTs). Unlike many popular approaches to NFT DeFi, Hook does not sacrifice the non-fungible nature of NFTs by requiring that they are converted into fungible tokens. 8 | 9 | Note: NFTs deposited into the Hook protocol only contain unique artistic images and do not contain, reference, represent the price, rate or level of any security, commodity or financial instrument. 10 | 11 | ## Documentation 12 | 13 | [Documentation Site](https://docs.hook.xyz/) 14 | 15 | [Protocol Overview](docs/Overview.md) 16 | 17 | [Definitions](docs/Definitions.md) 18 | 19 | [Natspec Generated Docs](docs/generated/index.html) 20 | 21 | ## Setup 22 | 23 | Hook utilizes Foundry for test suites and 24 | `forge install` to install dependencies from git submodules 25 | `npm install` to install hardhat dependencies 26 | 27 | The hardhat project is used for coverage testing and deployments 28 | External non-test deps (ie openzeppelin contracts) are added using yarn, 29 | added to the `package.json` file, and then the {remappings.txt} is subsequently 30 | updated. `yarn add -D @openzeppelin/contracts` 31 | 32 | ## Contract Addresses 33 | 34 | See our docs: https://docs.hook.xyz/docs/smart-contract-addresses 35 | 36 | ## Testing 37 | 38 | `forge test` to run all forge tests 39 | `forge test --match-contract ` to run tests on a specific contract 40 | `npx hardhat coverage` to run the coverage suite 41 | 42 | ## Additional Foundry Info 43 | 44 | [Foundry](https://github.com/foundry-rs/foundry) 45 | 46 | [Foundry Book](https://book.getfoundry.sh) 47 | 48 | ## Licence 49 | 50 | [MIT](LICENCE.md) Copyright 2022 Abstract Labs, Inc. 51 | -------------------------------------------------------------------------------- /deploy/deploy.ts: -------------------------------------------------------------------------------- 1 | import { HardhatRuntimeEnvironment } from "hardhat/types"; 2 | import { DeployFunction } from "hardhat-deploy/types"; 3 | import { ethers } from "hardhat"; 4 | 5 | const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { 6 | const { deployments, getNamedAccounts } = hre; 7 | const { deploy } = deployments; 8 | console.log("what is happening"); 9 | 10 | let { 11 | deployer, 12 | vaultUpgrader, 13 | callsUpgrader, 14 | pauserRole, 15 | marketConf, 16 | collectionConf, 17 | allowlister, 18 | weth, 19 | approvedMarket, 20 | } = await getNamedAccounts(); 21 | 22 | console.log(deployer); 23 | 24 | console.log("Deploying from", deployer); 25 | 26 | console.log("Deploying with these args:", [ 27 | allowlister, 28 | pauserRole, 29 | vaultUpgrader, 30 | callsUpgrader, 31 | marketConf, 32 | collectionConf, 33 | weth, 34 | ]); 35 | 36 | const protocol = await deploy("HookProtocol", { 37 | from: deployer, 38 | args: [ 39 | allowlister, 40 | pauserRole, 41 | vaultUpgrader, 42 | callsUpgrader, 43 | marketConf, 44 | collectionConf, 45 | weth, 46 | ], 47 | log: true, 48 | autoMine: true, 49 | }); 50 | 51 | const protocolImpl = await ethers.getContractAt( 52 | "HookProtocol", 53 | protocol.address 54 | ); 55 | 56 | const soloVault = await deploy("HookERC721VaultImplV1", { 57 | from: deployer, 58 | args: [], 59 | log: true, 60 | autoMine: true, 61 | }); 62 | 63 | const multiVault = await deploy("HookERC721MultiVaultImplV1", { 64 | from: deployer, 65 | args: [], 66 | log: true, 67 | autoMine: true, 68 | }); 69 | 70 | const multiVaultBeacon = await deploy("HookUpgradeableBeacon", { 71 | from: deployer, 72 | args: [ 73 | multiVault.address, 74 | protocol.address, 75 | ethers.utils.id("VAULT_UPGRADER"), 76 | ], 77 | log: true, 78 | autoMine: true, 79 | }); 80 | 81 | const soloVaultBeacon = await deploy("HookUpgradeableBeacon", { 82 | from: deployer, 83 | args: [ 84 | soloVault.address, 85 | protocol.address, 86 | ethers.utils.id("VAULT_UPGRADER"), 87 | ], 88 | log: true, 89 | autoMine: true, 90 | }); 91 | 92 | const vaultFactory = await deploy("HookERC721VaultFactory", { 93 | from: deployer, 94 | args: [protocol.address, soloVaultBeacon.address, multiVaultBeacon.address], 95 | log: true, 96 | autoMine: true, 97 | }); 98 | 99 | if (vaultUpgrader === deployer) { 100 | const vfSet = await protocolImpl.setVaultFactory(vaultFactory.address); 101 | console.log("Set vault factory onto protocol with hash: ", vfSet.hash); 102 | } 103 | 104 | const font1 = await deploy("Font1", { 105 | from: deployer, 106 | args: [], 107 | log: true, 108 | // maxPriorityFeePerGas: "2000000000", 109 | // maxFeePerGas: "50000000000", 110 | autoMine: true, 111 | }); 112 | const font2 = await deploy("Font2", { 113 | from: deployer, 114 | args: [], 115 | log: true, 116 | // maxPriorityFeePerGas: "2000000000", 117 | // maxFeePerGas: "50000000000", 118 | autoMine: true, 119 | }); 120 | const font3 = await deploy("Font3", { 121 | from: deployer, 122 | args: [], 123 | log: true, 124 | // maxPriorityFeePerGas: "2000000000", 125 | // maxFeePerGas: "50000000000", 126 | autoMine: true, 127 | }); 128 | 129 | const tokenURI = await deploy("TokenURI", { 130 | from: deployer, 131 | args: [], 132 | libraries: { 133 | Font1: font1.address, // "0x1Ac06Ef3cda4dC2CB30A866090041D3266c33d45", 134 | Font2: font2.address, //"0xfa10218700bFd179DE800a461C98357b39525f38", 135 | Font3: font3.address, //"0x4C6eDA9CBb9B31152f3f002CAe5E3eF805Ad19f9", 136 | }, 137 | log: true, 138 | // maxPriorityFeePerGas: "2000000000", 139 | // maxFeePerGas: "50000000000", 140 | autoMine: true, 141 | }); 142 | const callV1 = await deploy("HookCoveredCallImplV1", { 143 | from: deployer, 144 | libraries: { 145 | TokenURI: tokenURI.address, 146 | }, 147 | args: [], 148 | log: true, 149 | autoMine: true, 150 | }); 151 | 152 | const callBeacon = await deploy("HookUpgradeableBeacon", { 153 | from: deployer, 154 | args: [ 155 | "0x3648080307faC2EE51A01463e47B9ca076DC14A1", 156 | "0xE11CCED3E6555A1BcbA2E19b9Cf161f040186069", 157 | ethers.utils.id("CALL_UPGRADER"), 158 | ], 159 | log: true, 160 | autoMine: true, 161 | }); 162 | 163 | const callFactory = await deploy("HookCoveredCallFactory", { 164 | from: deployer, 165 | args: [ 166 | "0xE11CCED3E6555A1BcbA2E19b9Cf161f040186069", 167 | callBeacon.address, 168 | approvedMarket, 169 | ], 170 | log: true, 171 | autoMine: true, 172 | }); 173 | 174 | if (deployer === callsUpgrader) { 175 | const cfSet = await protocolImpl.setCoveredCallFactory(callFactory.address); 176 | console.log("Set call factory onto protocol with hash: ", cfSet.hash); 177 | } 178 | 179 | if (deployer == pauserRole) { 180 | // Will need to pause outside of this context 181 | // for the process to work with mainnet deploys 182 | await protocolImpl.connect(deployer).pause(); 183 | } 184 | 185 | return true; 186 | }; 187 | export default func; 188 | func.id = "deploy_hook_protocol"; // id required to prevent reexecution 189 | func.tags = ["HookProtocol"]; 190 | -------------------------------------------------------------------------------- /deployments/goerli/.chainId: -------------------------------------------------------------------------------- 1 | 5 -------------------------------------------------------------------------------- /deployments/mainnet/.chainId: -------------------------------------------------------------------------------- 1 | 1 -------------------------------------------------------------------------------- /deployments/mainnet/.migrations.json: -------------------------------------------------------------------------------- 1 | { 2 | "deploy_hook_protocol": 1661271437 3 | } -------------------------------------------------------------------------------- /deployments/ropsten-old/.chainId: -------------------------------------------------------------------------------- 1 | 3 -------------------------------------------------------------------------------- /deployments/ropsten-old/.migrations.json: -------------------------------------------------------------------------------- 1 | { 2 | "deploy_hook_protocol": 1654034471 3 | } -------------------------------------------------------------------------------- /deployments/ropsten/.chainId: -------------------------------------------------------------------------------- 1 | 3 -------------------------------------------------------------------------------- /deployments/ropsten/.migrations.json: -------------------------------------------------------------------------------- 1 | { 2 | "deploy_hook_protocol": 1661193315 3 | } -------------------------------------------------------------------------------- /docs/Definitions.md: -------------------------------------------------------------------------------- 1 | # Definitions relevant for the Hook Protocol 2 | 3 | ## General 4 | 5 | | term | definition | 6 | | -------------------- | -------------------------------------------------------------------------------------------------------------------- | 7 | | european call option | the right, but not the obligation, to buy an underlying asset for a specific price at a specific time in the future | 8 | | underlying asset | the specific asset upon which the option contract is based | 9 | | option holder | the person that holds the right but not the obligation to buy a specific underlying asset | 10 | | strike price | the price for which, at the end of a european call option, an underlying asset can be purchased by the option holder | 11 | | covered option | an option where the underlying asset is posted as collateral | 12 | | account | either a smart contract or EOA (externally owned account, i.e. private key) on a EVM-compatible blockchain | 13 | 14 | ## Hook Vault 15 | 16 | The Vault holds an asset on behalf of the owner. The owner is able to post this asset as collateral to other accounts by creating an "entitlement", that gives a specific account the ability to change the beneficial owner of the asset. While the asset is held within the vault, any account set as the beneficial owner is able to make external contract calls to benefit from the utility of the asset. 17 | 18 | | term | definition | 19 | | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 20 | | beneficial owner | The account on behalf of which a vault holds an asset | 21 | | entitlement | An entitlement grants a specific entitled account (contract or EOA) to change the beneficial owner of an asset contained in a vault until the entitlement either expires or is removed. | 22 | 23 | ## Covered Call Options 24 | 25 | Terms defined within the scope of a Hook Protocol Covered Call Option 26 | 27 | | term | definition | 28 | | ------------------ | --------------------------------------------------------------------------------- | 29 | | settlement auction | the process by which the fair market value for the underlying asset is determined | 30 | | option holder | the account that holds the ERC-721 representing the call option | 31 | | strike price | the price in wei at which the protocol can purchase the underlying asset | 32 | -------------------------------------------------------------------------------- /docs/generated/index.html: -------------------------------------------------------------------------------- 1 | Hardhat Docgen
-------------------------------------------------------------------------------- /docs/generated/main.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /*! 2 | * Vue.js v2.6.14 3 | * (c) 2014-2021 Evan You 4 | * Released under the MIT License. 5 | */ 6 | -------------------------------------------------------------------------------- /foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = 'src' 3 | out = 'out' 4 | libs = ['lib'] 5 | solc = "0.8.10" 6 | optimizer = true 7 | optimizer_runs = 20 8 | # See more config options https://github.com/gakonst/foundry/tree/master/config -------------------------------------------------------------------------------- /hardhat.config.ts: -------------------------------------------------------------------------------- 1 | import * as dotenv from "dotenv"; 2 | dotenv.config(); 3 | import { readFileSync } from "fs"; 4 | import * as toml from "toml"; 5 | import "@nomiclabs/hardhat-ethers"; 6 | import "@nomiclabs/hardhat-etherscan"; 7 | import "hardhat-gas-reporter"; 8 | import "solidity-coverage"; 9 | import "hardhat-deploy"; 10 | import "hardhat-docgen"; 11 | import { HardhatUserConfig, subtask } from "hardhat/config"; 12 | import { TASK_COMPILE_SOLIDITY_GET_SOURCE_PATHS } from "hardhat/builtin-tasks/task-names"; 13 | // import { LedgerSigner } from "@anders-t/ethers-ledger"; 14 | 15 | // default values here to avoid failures when running hardhat 16 | const ROPSTEN_RPC = process.env.ROPSTEN_RPC || "1".repeat(32); 17 | const GOERLI_RPC = process.env.GOERLI_RPC || "1".repeat(32); 18 | const MAINNET_RPC = process.env.MAINNET_RPC || "1".repeat(32); 19 | const PRIVATE_KEY = process.env.PRIVATE_KEY || "1".repeat(64); 20 | const SOLC_DEFAULT = "0.8.10"; 21 | 22 | // try use forge config 23 | let foundry: any; 24 | try { 25 | foundry = toml.parse(readFileSync("./foundry.toml").toString()); 26 | foundry.default.solc = foundry.default["solc-version"] 27 | ? foundry.default["solc-version"] 28 | : SOLC_DEFAULT; 29 | } catch (error) { 30 | foundry = { 31 | default: { 32 | solc: SOLC_DEFAULT, 33 | }, 34 | }; 35 | } 36 | 37 | // prune forge style tests from hardhat paths 38 | subtask(TASK_COMPILE_SOLIDITY_GET_SOURCE_PATHS).setAction( 39 | async (_, __, runSuper) => { 40 | const paths = await runSuper(); 41 | return paths.filter((p: string) => !p.endsWith(".t.sol")); 42 | } 43 | ); 44 | 45 | const config: any = { 46 | docgen: { 47 | path: "./docs/generated", 48 | clear: true, 49 | runOnCompile: true, 50 | only: ["src/"], 51 | except: ["test/"], 52 | }, 53 | paths: { 54 | cache: "cache-hardhat", 55 | sources: "./src", 56 | tests: "./integration", 57 | }, 58 | defaultNetwork: "hardhat", 59 | networks: { 60 | hardhat: { chainId: 1337, allowUnlimitedContractSize: true }, 61 | ropsten: { 62 | url: ROPSTEN_RPC, 63 | accounts: [PRIVATE_KEY], 64 | }, 65 | goerli: { 66 | url: GOERLI_RPC, 67 | accounts: [PRIVATE_KEY], 68 | }, 69 | mainnet: { 70 | url: MAINNET_RPC, 71 | }, 72 | }, 73 | solidity: { 74 | version: foundry.default?.solc || SOLC_DEFAULT, 75 | settings: { 76 | optimizer: { 77 | enabled: true, 78 | runs: 10, 79 | }, 80 | }, 81 | }, 82 | gasReporter: { 83 | currency: "USD", 84 | gasPrice: 77, 85 | excludeContracts: ["src/test"], 86 | // API key for CoinMarketCap. https://pro.coinmarketcap.com/signup 87 | coinmarketcap: process.env.CMC_KEY ?? "", 88 | }, 89 | namedAccounts: { 90 | deployer: "ledger://0x1cAA0034b17786E18D94Ca176b1F8ec3F7972908", 91 | weth: process.env.WETH_ADDRESS || "", 92 | approvedMarket: 93 | process.env.APROVED_MARKET || 94 | "0xdef1c0ded9bec7f1a1670819833240f027b25eff", 95 | vaultUpgrader: process.env.VAULT_UPGRADER || "", 96 | callsUpgrader: process.env.CALLS_UPGRADER || "", 97 | pauserRole: process.env.PAUSER_ROLE || "", 98 | marketConf: process.env.MARKET_CONF || "", 99 | collectionConf: process.env.COLLECTION_CONF || "", 100 | allowlister: process.env.ALLOWLISTER || "", 101 | }, 102 | etherscan: { 103 | // API key for Etherscan. https://etherscan.io/ 104 | apiKey: process.env.ETHERSCAN_API_KEY ?? "", 105 | }, 106 | }; 107 | 108 | export default config; 109 | -------------------------------------------------------------------------------- /img/hook-protocol-banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hookart/protocol/de64025fcf17d26d6f23775bc6d327148215758b/img/hook-protocol-banner.png -------------------------------------------------------------------------------- /integration/helpers/index.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "hardhat"; 2 | import type { 3 | TypedDataDomain, 4 | TypedDataField, 5 | } from "@ethersproject/abstract-signer"; 6 | import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; 7 | 8 | export interface Entitlement { 9 | beneficialOwner: string; 10 | operator: string; 11 | vaultAddress: string; 12 | assetId: string; 13 | expiry: string; 14 | } 15 | 16 | export enum OrderDirection { 17 | BUY = 0, 18 | SELL = 1, 19 | } 20 | 21 | export enum OptionType { 22 | CALL = 0, 23 | PUT = 1, 24 | } 25 | 26 | export interface VolOrderNFTProperties { 27 | propertyValidator: string; 28 | propertyData: string; 29 | } 30 | 31 | export interface VolOrder { 32 | direction: OrderDirection; 33 | maker: string; 34 | orderExpiry: string; 35 | nonce: string; 36 | size: string; 37 | optionType: OptionType; 38 | maxStrikePriceMultiple: string; 39 | minOptionDuration: string; 40 | maxOptionDuration: string; 41 | maxPriceSignalAge: string; 42 | nftProperties: VolOrderNFTProperties[]; 43 | optionMarketAddress: string; 44 | impliedVolBips: string; 45 | skewDecimal: string; 46 | riskFreeRateBips: string; 47 | } 48 | 49 | const signTypedData = async ( 50 | domain: TypedDataDomain, 51 | types: Record, 52 | value: Record, 53 | signer: SignerWithAddress, 54 | // TODO: Validate we might not need this for "getSigner" it's optional 55 | // we need to make sure that it always uses the correct one when not sending. 56 | address: string 57 | ) => { 58 | const rawSignature = await signer._signTypedData(domain, types, value); 59 | const { v, r, s } = ethers.utils.splitSignature(rawSignature); 60 | const signature = { 61 | signatureType: 2, // EIP712 - signature utils 0x 62 | v, 63 | r, 64 | s, 65 | }; 66 | return signature; 67 | }; 68 | 69 | export function genVolOrderTypedData( 70 | order: VolOrder, 71 | verifyingContract: string 72 | ) { 73 | return { 74 | // All properties on a domain are optional 75 | domain: { 76 | name: "Hook", 77 | version: "1.0.0", 78 | chainId: 1337, // pulled from hardhat.config.ts 79 | verifyingContract, // Hook Protocol 80 | }, 81 | // The named list of all type definitions 82 | types: { 83 | Property: [ 84 | { name: "propertyValidator", type: "address" }, 85 | { name: "propertyData", type: "bytes" }, 86 | ], 87 | Order: [ 88 | { name: "direction", type: "uint8" }, 89 | { name: "maker", type: "address" }, 90 | { name: "orderExpiry", type: "uint256" }, 91 | { name: "nonce", type: "uint256" }, 92 | { name: "size", type: "uint8" }, 93 | { name: "optionType", type: "uint8" }, 94 | { name: "maxStrikePriceMultiple", type: "uint256" }, 95 | { name: "minOptionDuration", type: "uint64" }, 96 | { name: "maxOptionDuration", type: "uint64" }, 97 | { name: "maxPriceSignalAge", type: "uint64" }, 98 | { name: "nftProperties", type: "Property[]" }, 99 | { name: "optionMarketAddress", type: "address" }, 100 | { name: "impliedVolBips", type: "uint64" }, 101 | { name: "skewDecimal", type: "uint64" }, 102 | { name: "riskFreeRateBips", type: "uint64" }, 103 | ], 104 | }, 105 | // The data to sign 106 | value: order, 107 | }; 108 | } 109 | 110 | function genEntitlementTypedData( 111 | entitlement: Entitlement, 112 | verifyingContract: string 113 | ) { 114 | return { 115 | // All properties on a domain are optional 116 | domain: { 117 | name: "Hook", 118 | version: "1.0.0", 119 | chainId: 1337, // pulled from hardhat.config.ts 120 | verifyingContract, // Hook Protocol 121 | }, 122 | // The named list of all type definitions 123 | types: { 124 | Entitlement: [ 125 | { name: "beneficialOwner", type: "address" }, 126 | { name: "operator", type: "address" }, 127 | { name: "vaultAddress", type: "address" }, 128 | { name: "assetId", type: "uint32" }, 129 | { name: "expiry", type: "uint32" }, 130 | ], 131 | }, 132 | // The data to sign 133 | value: entitlement, 134 | }; 135 | } 136 | 137 | export async function signEntitlement( 138 | beneficialOwner: string, 139 | operator: string, 140 | vaultAddress: string, 141 | assetId: string, 142 | expiry: string, 143 | signer: SignerWithAddress, 144 | hookProtocol: string // Hook Protocol 145 | ) { 146 | // Sign Entitlement 147 | const entitlement = { 148 | beneficialOwner, 149 | operator, 150 | vaultAddress, 151 | assetId, 152 | expiry, 153 | }; 154 | const { domain, types, value } = genEntitlementTypedData( 155 | entitlement, 156 | hookProtocol 157 | ); 158 | const signature = await signTypedData( 159 | domain, 160 | types, 161 | value, 162 | signer, 163 | beneficialOwner 164 | ); 165 | 166 | return signature; 167 | } 168 | 169 | export async function signVolOrder( 170 | order: VolOrder, 171 | signer: SignerWithAddress, 172 | hookProtocol: string // Hook Protocol 173 | ) { 174 | const { domain, types, value } = genVolOrderTypedData(order, hookProtocol); 175 | const rawSignature = await signer._signTypedData(domain, types, value); 176 | return rawSignature; 177 | } 178 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hardhat-project", 3 | "devDependencies": { 4 | "@ethersproject/abstract-signer": "^5.6.1", 5 | "@ethersproject/providers": "^5.6.7", 6 | "@nomiclabs/hardhat-ethers": "npm:hardhat-deploy-ethers@^0.3.0-beta.13", 7 | "@nomiclabs/hardhat-etherscan": "^3.0.3", 8 | "@openzeppelin/contracts": "4.6.0", 9 | "@typechain/ethers-v5": "^7.2.0", 10 | "@typechain/hardhat": "^2.3.1", 11 | "@types/chai": "^4.3.1", 12 | "@types/mocha": "^9.1.1", 13 | "@types/node": "^12.20.52", 14 | "@typescript-eslint/eslint-plugin": "^4.33.0", 15 | "@typescript-eslint/parser": "^4.33.0", 16 | "chai": "^4.3.6", 17 | "dotenv": "^16.0.1", 18 | "eslint": "^7.32.0", 19 | "eslint-config-prettier": "^8.5.0", 20 | "eslint-config-standard": "^16.0.3", 21 | "eslint-plugin-import": "^2.26.0", 22 | "eslint-plugin-node": "^11.1.0", 23 | "eslint-plugin-prettier": "^3.4.1", 24 | "eslint-plugin-promise": "^5.2.0", 25 | "ethereum-waffle": "^4.0.10", 26 | "ethers": "^5.7.0", 27 | "hardhat": "^2.9.6", 28 | "hardhat-docgen": "^1.3.0", 29 | "hardhat-gas-reporter": "^1.0.8", 30 | "prettier": "^2.6.2", 31 | "prettier-plugin-solidity": "^1.0.0-beta.19", 32 | "solhint": "^3.3.7", 33 | "solidity-coverage": "^0.8.2", 34 | "ts-node": "^10.8.0", 35 | "typechain": "^5.2.0", 36 | "typescript": "^4.6.4" 37 | }, 38 | "dependencies": { 39 | "@ethersproject/hardware-wallets": "^5.7.0", 40 | "hardhat-deploy": "github:jake-nyquist/hardhat-deploy#close-ledger-connection", 41 | "toml": "^3.0.0" 42 | }, 43 | "resolutions": { 44 | "@tenderly/hardhat-tenderly/@nomiclabs/hardhat-ethers": "npm:hardhat-deploy-ethers" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /remappings.txt: -------------------------------------------------------------------------------- 1 | @openzeppelin/contracts=node_modules/@openzeppelin/contracts 2 | @narya-ai/contracts/=lib/narya-contracts/ 3 | forge-std/=lib/forge-std/src/ 4 | -------------------------------------------------------------------------------- /slither.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "filter_paths": "lib|src/test", 3 | "solc_remaps": [ 4 | "ds-test/=lib/ds-test/src/", 5 | "forge-std/=lib/forge-std/src/" 6 | ] 7 | } -------------------------------------------------------------------------------- /src/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "useTabs": false 4 | } 5 | -------------------------------------------------------------------------------- /src/HookBeaconProxy.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // Modified version of : OpenZeppelin Contracts v4.4.1 (proxy/beacon/BeaconProxy.sol) 3 | 4 | pragma solidity ^0.8.0; 5 | 6 | import "@openzeppelin/contracts/proxy/beacon/IBeacon.sol"; 7 | import "@openzeppelin/contracts/proxy/Proxy.sol"; 8 | import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Upgrade.sol"; 9 | 10 | /// @title HookBeaconProxy a proxy contract that points to an implementation provided by a Beacon 11 | /// @dev This contract implements a proxy that gets the implementation address for each call from a {UpgradeableBeacon}. 12 | /// 13 | /// The beacon address is stored in storage slot `uint256(keccak256('eip1967.proxy.beacon')) - 1`, so that it doesn't 14 | /// conflict with the storage layout of the implementation behind the proxy. 15 | /// 16 | /// This is an extension of the OpenZeppelin beacon proxy, however differs in that it is initializeable, which means 17 | /// it is usable with Create2. 18 | contract HookBeaconProxy is Proxy, ERC1967Upgrade { 19 | /// @dev The constructor is empty in this case because the proxy is initializeable 20 | constructor() {} 21 | 22 | bytes32 constant _INITIALIZED_SLOT = bytes32(uint256(keccak256("initializeable.beacon.version")) - 1); 23 | bytes32 constant _INITIALIZING_SLOT = bytes32(uint256(keccak256("initializeable.beacon.initializing")) - 1); 24 | 25 | /// 26 | /// @dev Triggered when the contract has been initialized or reinitialized. 27 | /// 28 | event Initialized(uint8 version); 29 | 30 | /// @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope, 31 | /// `onlyInitializing` functions can be used to initialize parent contracts. Equivalent to `reinitializer(1)`. 32 | modifier initializer() { 33 | bool isTopLevelCall = _setInitializedVersion(1); 34 | if (isTopLevelCall) { 35 | StorageSlot.getBooleanSlot(_INITIALIZING_SLOT).value = true; 36 | } 37 | _; 38 | if (isTopLevelCall) { 39 | StorageSlot.getBooleanSlot(_INITIALIZING_SLOT).value = false; 40 | emit Initialized(1); 41 | } 42 | } 43 | 44 | function _setInitializedVersion(uint8 version) private returns (bool) { 45 | // If the contract is initializing we ignore whether _initialized is set in order to support multiple 46 | // inheritance patterns, but we only do this in the context of a constructor, and for the lowest level 47 | // of initializers, because in other contexts the contract may have been reentered. 48 | if (StorageSlot.getBooleanSlot(_INITIALIZING_SLOT).value) { 49 | require(version == 1 && !Address.isContract(address(this)), "contract is already initialized"); 50 | return false; 51 | } else { 52 | require(StorageSlot.getUint256Slot(_INITIALIZED_SLOT).value < version, "contract is already initialized"); 53 | StorageSlot.getUint256Slot(_INITIALIZED_SLOT).value = version; 54 | return true; 55 | } 56 | } 57 | 58 | /// @dev Initializes the proxy with `beacon`. 59 | /// 60 | /// If `data` is nonempty, it's used as data in a delegate call to the implementation returned by the beacon. This 61 | /// will typically be an encoded function call, and allows initializing the storage of the proxy like a Solidity 62 | /// constructor. 63 | /// 64 | /// Requirements: 65 | /// 66 | ///- `beacon` must be a contract with the interface {IBeacon}. 67 | /// 68 | function initializeBeacon(address beacon, bytes memory data) public initializer { 69 | assert(_BEACON_SLOT == bytes32(uint256(keccak256("eip1967.proxy.beacon")) - 1)); 70 | _upgradeBeaconToAndCall(beacon, data, false); 71 | } 72 | 73 | /// 74 | /// @dev Returns the current implementation address of the associated beacon. 75 | /// 76 | function _implementation() internal view virtual override returns (address) { 77 | return IBeacon(_getBeacon()).implementation(); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/HookCoveredCallFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // 3 | // █████████████▌ ▐█████████████ 4 | // █████████████▌ ▐█████████████ 5 | // █████████████▌ ▐█████████████ 6 | // █████████████▌ ▐█████████████ 7 | // █████████████▌ ▐█████████████ 8 | // █████████████▌ ▐█████████████ 9 | // █████████████▌ ▐█████████████ 10 | // █████████████▌ ▐█████████████ 11 | // ██████████████ ██████████████ 12 | // ██████████████ ▄▄████████████████▄▄ ▐█████████████▌ 13 | // ██████████████ ▄█████████████████████████████▄ ██████████████ 14 | // ██████████▀ ▄█████████████████████████████████ ██████████████▌ 15 | // ██████▀ ▄██████████████████████████████████▀ ▄███████████████ 16 | // ███▀ ██████████████████████████████████▀ ▄████████████████ 17 | // ▀▀ ████████████████████████████████▀▀ ▄█████████████████▌ 18 | // █████████████████████▀▀▀▀▀▀▀ ▄▄███████████████████▀ 19 | // ██████████████████▀ ▄▄▄█████████████████████████████▀ 20 | // ████████████████▀ ▄█████████████████████████████████▀ ██▄ 21 | // ▐███████████████▀ ▄██████████████████████████████████▀ █████▄ 22 | // ██████████████▀ ▄█████████████████████████████████▀ ▄████████ 23 | // ██████████████▀ ███████████████████████████████▀ ▄████████████ 24 | // ▐█████████████▌ ▀▀▀▀████████████████████▀▀▀▀ █████████████▌ 25 | // ██████████████ ██████████████ 26 | // █████████████▌ ██████████████ 27 | // █████████████▌ ██████████████ 28 | // █████████████▌ ██████████████ 29 | // █████████████▌ ██████████████ 30 | // █████████████▌ ██████████████ 31 | // █████████████▌ ██████████████ 32 | // █████████████▌ ██████████████ 33 | // █████████████▌ ██████████████ 34 | 35 | pragma solidity ^0.8.10; 36 | 37 | import "./interfaces/IHookCoveredCallFactory.sol"; 38 | import "./interfaces/IHookProtocol.sol"; 39 | 40 | import "./interfaces/IInitializeableBeacon.sol"; 41 | import "./HookBeaconProxy.sol"; 42 | 43 | import "./mixin/PermissionConstants.sol"; 44 | 45 | import "@openzeppelin/contracts/utils/Create2.sol"; 46 | 47 | /// @title Hook Covered Call Factory 48 | /// @author Jake Nyquist-j@hook.xyz 49 | /// @dev See {IHookCoveredCallFactory}. 50 | /// @dev The factory looks up certain roles by calling the {IHookProtocol} to verify 51 | // that the caller is allowed to take certain actions 52 | contract HookCoveredCallFactory is PermissionConstants, IHookCoveredCallFactory { 53 | /// @notice Registry of all of the active markets projects with supported call instruments 54 | mapping(address => address) public override getCallInstrument; 55 | 56 | /// @notice address of the beacon that contains the address of the current {IHookCoveredCall} implementation 57 | address private immutable _beacon; 58 | 59 | /// @notice the address of the protocol, which contains the rule 60 | IHookProtocol private immutable _protocol; 61 | 62 | /// @notice the address of an account that should automatically be approved to transfer the ERC-721 tokens 63 | /// created by the {IHookCoveredCall} to represent instruments. This value is not used by the factory directly, 64 | /// as this functionality is implemented by the {IHookCoveredCall} 65 | address private immutable _preApprovedMarketplace; 66 | 67 | /// @param hookProtocolAddress the address of the deployed {IHookProtocol} contract on this chain 68 | /// @param beaconAddress the address of the deployed beacon pointing to the current covered call implementation 69 | /// @param preApprovedMarketplace the address of an account approved to transfer instrument NFTs without owner approval 70 | constructor(address hookProtocolAddress, address beaconAddress, address preApprovedMarketplace) { 71 | require(Address.isContract(hookProtocolAddress), "hook protocol must be a contract"); 72 | require(Address.isContract(beaconAddress), "beacon address must be a contract"); 73 | require(Address.isContract(preApprovedMarketplace), "pre-approved marketplace must be a contract"); 74 | _beacon = beaconAddress; 75 | _protocol = IHookProtocol(hookProtocolAddress); 76 | _preApprovedMarketplace = preApprovedMarketplace; 77 | } 78 | 79 | /// @dev See {IHookCoveredCallFactory-makeCallInstrument}. 80 | /// @dev Only holders of the ALLOWLISTER_ROLE on the {IHookProtocol} can create these addresses. 81 | function makeCallInstrument(address assetAddress) external returns (address) { 82 | require(getCallInstrument[assetAddress] == address(0), "makeCallInstrument-a call instrument already exists"); 83 | // make sure new instruments created by admins or the role 84 | // has been burned 85 | require( 86 | _protocol.hasRole(ALLOWLISTER_ROLE, msg.sender) || _protocol.hasRole(ALLOWLISTER_ROLE, address(0)), 87 | "makeCallInstrument-Only admins can make instruments" 88 | ); 89 | 90 | IInitializeableBeacon bp = IInitializeableBeacon( 91 | Create2.deploy(0, _callInstrumentSalt(assetAddress), type(HookBeaconProxy).creationCode) 92 | ); 93 | 94 | bp.initializeBeacon( 95 | _beacon, 96 | /// This is the ABI encoded initializer on the IHookERC721Vault.sol 97 | abi.encodeWithSignature( 98 | "initialize(address,address,address,address)", 99 | _protocol, 100 | assetAddress, 101 | _protocol.vaultContract(), 102 | _preApprovedMarketplace 103 | ) 104 | ); 105 | 106 | // Persist the call instrument onto the hook protocol 107 | getCallInstrument[assetAddress] = address(bp); 108 | 109 | emit CoveredCallInstrumentCreated(assetAddress, address(bp)); 110 | 111 | return address(bp); 112 | } 113 | 114 | /// @dev generate a consistent create2 salt to be used when deploying a 115 | /// call instrument 116 | /// @param underlyingAddress the account for the call option salt 117 | function _callInstrumentSalt(address underlyingAddress) internal pure returns (bytes32) { 118 | return keccak256(abi.encode(underlyingAddress)); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/HookERC721VaultFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // 3 | // █████████████▌ ▐█████████████ 4 | // █████████████▌ ▐█████████████ 5 | // █████████████▌ ▐█████████████ 6 | // █████████████▌ ▐█████████████ 7 | // █████████████▌ ▐█████████████ 8 | // █████████████▌ ▐█████████████ 9 | // █████████████▌ ▐█████████████ 10 | // █████████████▌ ▐█████████████ 11 | // ██████████████ ██████████████ 12 | // ██████████████ ▄▄████████████████▄▄ ▐█████████████▌ 13 | // ██████████████ ▄█████████████████████████████▄ ██████████████ 14 | // ██████████▀ ▄█████████████████████████████████ ██████████████▌ 15 | // ██████▀ ▄██████████████████████████████████▀ ▄███████████████ 16 | // ███▀ ██████████████████████████████████▀ ▄████████████████ 17 | // ▀▀ ████████████████████████████████▀▀ ▄█████████████████▌ 18 | // █████████████████████▀▀▀▀▀▀▀ ▄▄███████████████████▀ 19 | // ██████████████████▀ ▄▄▄█████████████████████████████▀ 20 | // ████████████████▀ ▄█████████████████████████████████▀ ██▄ 21 | // ▐███████████████▀ ▄██████████████████████████████████▀ █████▄ 22 | // ██████████████▀ ▄█████████████████████████████████▀ ▄████████ 23 | // ██████████████▀ ███████████████████████████████▀ ▄████████████ 24 | // ▐█████████████▌ ▀▀▀▀████████████████████▀▀▀▀ █████████████▌ 25 | // ██████████████ ██████████████ 26 | // █████████████▌ ██████████████ 27 | // █████████████▌ ██████████████ 28 | // █████████████▌ ██████████████ 29 | // █████████████▌ ██████████████ 30 | // █████████████▌ ██████████████ 31 | // █████████████▌ ██████████████ 32 | // █████████████▌ ██████████████ 33 | // █████████████▌ ██████████████ 34 | 35 | pragma solidity ^0.8.10; 36 | 37 | import "@openzeppelin/contracts/utils/Create2.sol"; 38 | 39 | import "./HookBeaconProxy.sol"; 40 | 41 | import "./interfaces/IHookERC721VaultFactory.sol"; 42 | import "./interfaces/IHookERC721Vault.sol"; 43 | import "./interfaces/IHookProtocol.sol"; 44 | import "./interfaces/IInitializeableBeacon.sol"; 45 | 46 | import "./mixin/PermissionConstants.sol"; 47 | 48 | import "./lib/BeaconSalts.sol"; 49 | 50 | /// @title Hook Vault Factory 51 | /// @author Jake Nyquist-j@hook.xyz 52 | /// @dev See {IHookERC721VaultFactory}. 53 | /// @dev The factory itself is non-upgradeable; however, each vault is upgradeable (i.e. all vaults) 54 | /// created by this factory can be upgraded at one time via the beacon pattern. 55 | contract HookERC721VaultFactory is IHookERC721VaultFactory, PermissionConstants { 56 | /// @notice Registry of all of the active vaults within the protocol, allowing users to find vaults by 57 | /// project address and tokenId; 58 | /// @dev From this view, we do not know if a vault is empty or full 59 | mapping(address => mapping(uint256 => IHookERC721Vault)) public override getVault; 60 | 61 | /// @notice Registry of all of the active multi-vaults within the protocol 62 | mapping(address => IHookERC721Vault) public override getMultiVault; 63 | 64 | address private immutable _hookProtocol; 65 | address private immutable _beacon; 66 | address private immutable _multiBeacon; 67 | 68 | constructor(address hookProtocolAddress, address beaconAddress, address multiBeaconAddress) { 69 | require(Address.isContract(hookProtocolAddress), "hook protocol must be a contract"); 70 | require(Address.isContract(beaconAddress), "beacon address must be a contract"); 71 | require(Address.isContract(multiBeaconAddress), "multi beacon address must be a contract"); 72 | _hookProtocol = hookProtocolAddress; 73 | _beacon = beaconAddress; 74 | _multiBeacon = multiBeaconAddress; 75 | } 76 | 77 | /// @notice See {IHookERC721VaultFactory-makeMultiVault}. 78 | function makeMultiVault(address nftAddress) external returns (IHookERC721Vault) { 79 | require( 80 | IHookProtocol(_hookProtocol).hasRole(ALLOWLISTER_ROLE, msg.sender) 81 | || IHookProtocol(_hookProtocol).hasRole(ALLOWLISTER_ROLE, address(0)), 82 | "makeMultiVault-Only accounts with the ALLOWLISTER role can make new multiVaults" 83 | ); 84 | 85 | require(getMultiVault[nftAddress] == IHookERC721Vault(address(0)), "makeMultiVault-vault cannot already exist"); 86 | 87 | IInitializeableBeacon bp = IInitializeableBeacon( 88 | Create2.deploy(0, BeaconSalts.multiVaultSalt(nftAddress), type(HookBeaconProxy).creationCode) 89 | ); 90 | 91 | bp.initializeBeacon( 92 | _multiBeacon, 93 | /// This is the ABI encoded initializer on the IHookERC721Vault.sol 94 | abi.encodeWithSignature("initialize(address,address)", nftAddress, _hookProtocol) 95 | ); 96 | 97 | IHookERC721Vault vault = IHookERC721Vault(address(bp)); 98 | getMultiVault[nftAddress] = vault; 99 | emit ERC721MultiVaultCreated(nftAddress, address(bp)); 100 | 101 | return vault; 102 | } 103 | 104 | /// @notice See {IHookERC721VaultFactory-makeSoloVault}. 105 | function makeSoloVault(address nftAddress, uint256 tokenId) public override returns (IHookERC721Vault) { 106 | require(getVault[nftAddress][tokenId] == IHookERC721Vault(address(0)), "makeVault-a vault cannot already exist"); 107 | 108 | IInitializeableBeacon bp = IInitializeableBeacon( 109 | Create2.deploy(0, BeaconSalts.soloVaultSalt(nftAddress, tokenId), type(HookBeaconProxy).creationCode) 110 | ); 111 | 112 | bp.initializeBeacon( 113 | _beacon, 114 | /// This is the ABI encoded initializer on the IHookERC721MultiVault.sol 115 | abi.encodeWithSignature("initialize(address,uint256,address)", nftAddress, tokenId, _hookProtocol) 116 | ); 117 | IHookERC721Vault vault = IHookERC721Vault(address(bp)); 118 | getVault[nftAddress][tokenId] = vault; 119 | 120 | emit ERC721VaultCreated(nftAddress, tokenId, address(vault)); 121 | 122 | return vault; 123 | } 124 | 125 | /// @notice See {IHookERC721VaultFactory-findOrCreateVault}. 126 | function findOrCreateVault(address nftAddress, uint256 tokenId) external returns (IHookERC721Vault) { 127 | if (getMultiVault[nftAddress] != IHookERC721Vault(address(0))) { 128 | return getMultiVault[nftAddress]; 129 | } 130 | 131 | if (getVault[nftAddress][tokenId] != IHookERC721Vault(address(0))) { 132 | return getVault[nftAddress][tokenId]; 133 | } 134 | 135 | return makeSoloVault(nftAddress, tokenId); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/HookProtocol.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // 3 | // █████████████▌ ▐█████████████ 4 | // █████████████▌ ▐█████████████ 5 | // █████████████▌ ▐█████████████ 6 | // █████████████▌ ▐█████████████ 7 | // █████████████▌ ▐█████████████ 8 | // █████████████▌ ▐█████████████ 9 | // █████████████▌ ▐█████████████ 10 | // █████████████▌ ▐█████████████ 11 | // ██████████████ ██████████████ 12 | // ██████████████ ▄▄████████████████▄▄ ▐█████████████▌ 13 | // ██████████████ ▄█████████████████████████████▄ ██████████████ 14 | // ██████████▀ ▄█████████████████████████████████ ██████████████▌ 15 | // ██████▀ ▄██████████████████████████████████▀ ▄███████████████ 16 | // ███▀ ██████████████████████████████████▀ ▄████████████████ 17 | // ▀▀ ████████████████████████████████▀▀ ▄█████████████████▌ 18 | // █████████████████████▀▀▀▀▀▀▀ ▄▄███████████████████▀ 19 | // ██████████████████▀ ▄▄▄█████████████████████████████▀ 20 | // ████████████████▀ ▄█████████████████████████████████▀ ██▄ 21 | // ▐███████████████▀ ▄██████████████████████████████████▀ █████▄ 22 | // ██████████████▀ ▄█████████████████████████████████▀ ▄████████ 23 | // ██████████████▀ ███████████████████████████████▀ ▄████████████ 24 | // ▐█████████████▌ ▀▀▀▀████████████████████▀▀▀▀ █████████████▌ 25 | // ██████████████ ██████████████ 26 | // █████████████▌ ██████████████ 27 | // █████████████▌ ██████████████ 28 | // █████████████▌ ██████████████ 29 | // █████████████▌ ██████████████ 30 | // █████████████▌ ██████████████ 31 | // █████████████▌ ██████████████ 32 | // █████████████▌ ██████████████ 33 | // █████████████▌ ██████████████ 34 | 35 | pragma solidity ^0.8.10; 36 | 37 | import "@openzeppelin/contracts/access/AccessControl.sol"; 38 | import "@openzeppelin/contracts/security/Pausable.sol"; 39 | 40 | import "./interfaces/IHookProtocol.sol"; 41 | 42 | import "./mixin/PermissionConstants.sol"; 43 | 44 | import "@openzeppelin/contracts/utils/Address.sol"; 45 | 46 | /// @dev Other contracts in the protocol refer to this one to get configuration and pausing issues. 47 | /// to reduce attack surface area, this contract cannot be upgraded; however, additional roles can be 48 | /// added. 49 | /// 50 | /// This contract does not implement any specific timelocks or other safety measures. The roles are granted 51 | /// with the principal of least privilege. As the protocol matures, these additional measures can be layered 52 | /// by granting these roles to other contracts. In the extreme, the upgrade and other roles can be burned, 53 | /// which would effectively make the protocol static and non-upgradeable. 54 | contract HookProtocol is PermissionConstants, AccessControl, IHookProtocol, Pausable { 55 | address public override coveredCallContract; 56 | address public override vaultContract; 57 | address public immutable override getWETHAddress; 58 | mapping(address => mapping(bytes32 => bool)) collectionConfigs; 59 | 60 | constructor( 61 | address allowlister, 62 | address pauser, 63 | address vaultUpgrader, 64 | address callUpgrader, 65 | address marketConf, 66 | address collectionConf, 67 | address weth 68 | ) { 69 | require(Address.isContract(weth), "weth must be a contract"); 70 | require(allowlister != address(0), "allowlister address cannot be set to the zero address"); 71 | require(pauser != address(0), "pauser address cannot be set to the zero address"); 72 | require(vaultUpgrader != address(0), "admin address cannot be set to the zero address"); 73 | require(callUpgrader != address(0), "callUpgrader address cannot be set to the zero address"); 74 | require(marketConf != address(0), "marketConf address cannot be set to the zero address"); 75 | require(collectionConf != address(0), "collectionConf address cannot be set to the zero address"); 76 | _setupRole(ALLOWLISTER_ROLE, allowlister); 77 | _setupRole(PAUSER_ROLE, pauser); 78 | _setupRole(VAULT_UPGRADER, vaultUpgrader); 79 | _setupRole(CALL_UPGRADER, callUpgrader); 80 | _setupRole(MARKET_CONF, marketConf); 81 | _setupRole(COLLECTION_CONF, collectionConf); 82 | 83 | // allow the admin to add and remove other roles 84 | _setRoleAdmin(ALLOWLISTER_ROLE, ALLOWLISTER_ROLE); 85 | _setRoleAdmin(PAUSER_ROLE, PAUSER_ROLE); 86 | _setRoleAdmin(VAULT_UPGRADER, VAULT_UPGRADER); 87 | _setRoleAdmin(CALL_UPGRADER, CALL_UPGRADER); 88 | _setRoleAdmin(MARKET_CONF, MARKET_CONF); 89 | _setRoleAdmin(COLLECTION_CONF, COLLECTION_CONF); 90 | // set weth 91 | getWETHAddress = weth; 92 | } 93 | 94 | /// @notice allows an account with the COLLECTION_CONF role to set a boolean config 95 | /// value for a collection 96 | /// @dev the conf value can be read with getCollectionConfig 97 | /// @param collectionAddress the address for the collection 98 | /// @param config the configuration field to set 99 | /// @param value the value to set for the configuration 100 | function setCollectionConfig(address collectionAddress, bytes32 config, bool value) 101 | external 102 | onlyRole(COLLECTION_CONF) 103 | { 104 | collectionConfigs[collectionAddress][config] = value; 105 | } 106 | 107 | /// @dev See {IHookProtocol-getCollectionConfig}. 108 | function getCollectionConfig(address collectionAddress, bytes32 conf) external view returns (bool) { 109 | return collectionConfigs[collectionAddress][conf]; 110 | } 111 | 112 | modifier callUpgraderOnly() { 113 | require(hasRole(CALL_UPGRADER, msg.sender), "Caller is not a call upgrader"); 114 | _; 115 | } 116 | 117 | modifier vaultUpgraderOnly() { 118 | require(hasRole(VAULT_UPGRADER, msg.sender), "Caller is not a vault upgrader"); 119 | _; 120 | } 121 | 122 | /// @notice throws an exception when the protocol is paused 123 | function throwWhenPaused() external view whenNotPaused { 124 | // depend on the modifier to throw. 125 | return; 126 | } 127 | 128 | /// @notice unpauses the protocol if the protocol is already paused 129 | function unpause() external { 130 | require(hasRole(PAUSER_ROLE, msg.sender), "Caller is not an pauser"); 131 | require(paused() == true, "Protocol is already paused"); 132 | _unpause(); 133 | } 134 | 135 | /// @notice pauses the protocol if the protocol is currently unpaused 136 | function pause() external { 137 | require(hasRole(PAUSER_ROLE, msg.sender), "Caller is not an pauser`"); 138 | require(paused() == false, "Protocol is already paused"); 139 | _pause(); 140 | } 141 | 142 | /// @notice Allows an admin to set the address of the deployed covered call factory 143 | /// @dev This address is used by other protocols searching for the registry of 144 | /// protocols. 145 | /// @param coveredCallFactoryContract the address of the deployed covered call contract 146 | function setCoveredCallFactory(address coveredCallFactoryContract) external callUpgraderOnly { 147 | require( 148 | Address.isContract(coveredCallFactoryContract), "setCoveredCallFactory: implementation is not a contract" 149 | ); 150 | coveredCallContract = coveredCallFactoryContract; 151 | } 152 | 153 | /// @notice Allows an admin to set the address of the deployed vault factory 154 | /// @dev allows all protocol components, including the call factory, to look up the 155 | /// vault factory. 156 | /// @param vaultFactoryContract the deployed vault factory 157 | function setVaultFactory(address vaultFactoryContract) external vaultUpgraderOnly { 158 | require(Address.isContract(vaultFactoryContract), "setVaultFactory: implementation is not a contract"); 159 | vaultContract = vaultFactoryContract; 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/HookUpgradeableBeacon.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // 3 | // █████████████▌ ▐█████████████ 4 | // █████████████▌ ▐█████████████ 5 | // █████████████▌ ▐█████████████ 6 | // █████████████▌ ▐█████████████ 7 | // █████████████▌ ▐█████████████ 8 | // █████████████▌ ▐█████████████ 9 | // █████████████▌ ▐█████████████ 10 | // █████████████▌ ▐█████████████ 11 | // ██████████████ ██████████████ 12 | // ██████████████ ▄▄████████████████▄▄ ▐█████████████▌ 13 | // ██████████████ ▄█████████████████████████████▄ ██████████████ 14 | // ██████████▀ ▄█████████████████████████████████ ██████████████▌ 15 | // ██████▀ ▄██████████████████████████████████▀ ▄███████████████ 16 | // ███▀ ██████████████████████████████████▀ ▄████████████████ 17 | // ▀▀ ████████████████████████████████▀▀ ▄█████████████████▌ 18 | // █████████████████████▀▀▀▀▀▀▀ ▄▄███████████████████▀ 19 | // ██████████████████▀ ▄▄▄█████████████████████████████▀ 20 | // ████████████████▀ ▄█████████████████████████████████▀ ██▄ 21 | // ▐███████████████▀ ▄██████████████████████████████████▀ █████▄ 22 | // ██████████████▀ ▄█████████████████████████████████▀ ▄████████ 23 | // ██████████████▀ ███████████████████████████████▀ ▄████████████ 24 | // ▐█████████████▌ ▀▀▀▀████████████████████▀▀▀▀ █████████████▌ 25 | // ██████████████ ██████████████ 26 | // █████████████▌ ██████████████ 27 | // █████████████▌ ██████████████ 28 | // █████████████▌ ██████████████ 29 | // █████████████▌ ██████████████ 30 | // █████████████▌ ██████████████ 31 | // █████████████▌ ██████████████ 32 | // █████████████▌ ██████████████ 33 | // █████████████▌ ██████████████ 34 | 35 | pragma solidity ^0.8.10; 36 | 37 | import "@openzeppelin/contracts/proxy/beacon/IBeacon.sol"; 38 | import "@openzeppelin/contracts/utils/Address.sol"; 39 | 40 | import "./mixin/PermissionConstants.sol"; 41 | import "./interfaces/IHookProtocol.sol"; 42 | 43 | /// @dev This contract is used in conjunction with one or more instances of {BeaconProxy} to determine their 44 | /// implementation contract, which is where they will delegate all function calls. 45 | /// 46 | /// An owner is able to change the implementation the beacon points to, thus upgrading the proxies that use this beacon. 47 | /// Ownership is managed centrally on the Hook protocol level, where the owner is the holder of a specific permission. 48 | /// This permission should be used only for the purpose of upgrading the particular contract (i.e., the permissions 49 | /// should not be reused). 50 | /// 51 | /// This contract is deliberately simple and only has one non-view 52 | /// method - `upgrade`. Timelocks or other upgrade conditions will be managed by 53 | /// the owner of this contract. 54 | /// This contract is based on the UpgradeableBeaconContract from OZ and DharmaUpgradeBeaconController from Dharma 55 | contract HookUpgradeableBeacon is IBeacon, PermissionConstants { 56 | using Address for address; 57 | 58 | address private _implementation; 59 | IHookProtocol private _protocol; 60 | bytes32 private _role; 61 | 62 | /// @dev Emitted when the implementation returned by the beacon is changed. 63 | event Upgraded(address indexed implementation); 64 | 65 | /// @dev Sets the address of the initial implementation, and the deployer account as the owner who can upgrade the 66 | /// beacon. 67 | constructor(address implementation_, address hookProtocol, bytes32 upgraderRole) { 68 | require(Address.isContract(hookProtocol), "UpgradeableBeacon: hookProtocol is not a contract"); 69 | 70 | require( 71 | upgraderRole == VAULT_UPGRADER || upgraderRole == CALL_UPGRADER, 72 | "upgrader role must be vault or call upgrader" 73 | ); 74 | _setImplementation(implementation_); 75 | _protocol = IHookProtocol(hookProtocol); 76 | _role = upgraderRole; 77 | } 78 | 79 | /// @dev Throws if called by any account other than the owner. 80 | modifier onlyOwner() { 81 | require( 82 | _protocol.hasRole(_role, msg.sender), 83 | "HookUpgradeableBeacon: caller does not have the required upgrade permissions" 84 | ); 85 | _; 86 | } 87 | 88 | /// @dev Returns the current implementation address. 89 | function implementation() external view virtual override returns (address) { 90 | return _implementation; 91 | } 92 | 93 | /// @dev Upgrades the beacon to a new implementation. 94 | /// 95 | /// Emits an {Upgraded} event. 96 | /// 97 | /// Requirements: 98 | /// 99 | /// - msg.sender must be the owner of the contract. 100 | /// - `newImplementation` must be a contract. 101 | function upgradeTo(address newImplementation) external virtual onlyOwner { 102 | _setImplementation(newImplementation); 103 | emit Upgraded(newImplementation); 104 | } 105 | 106 | /// @dev Sets the implementation contract address for this beacon 107 | /// 108 | /// Requirements: 109 | /// 110 | /// - `newImplementation` must be a contract. 111 | function _setImplementation(address newImplementation) private { 112 | require(Address.isContract(newImplementation), "HookUpgradeableBeacon: implementation is not a contract"); 113 | _implementation = newImplementation; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/interfaces/IERC721FlashLoanReceiver.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // 3 | // █████████████▌ ▐█████████████ 4 | // █████████████▌ ▐█████████████ 5 | // █████████████▌ ▐█████████████ 6 | // █████████████▌ ▐█████████████ 7 | // █████████████▌ ▐█████████████ 8 | // █████████████▌ ▐█████████████ 9 | // █████████████▌ ▐█████████████ 10 | // █████████████▌ ▐█████████████ 11 | // ██████████████ ██████████████ 12 | // ██████████████ ▄▄████████████████▄▄ ▐█████████████▌ 13 | // ██████████████ ▄█████████████████████████████▄ ██████████████ 14 | // ██████████▀ ▄█████████████████████████████████ ██████████████▌ 15 | // ██████▀ ▄██████████████████████████████████▀ ▄███████████████ 16 | // ███▀ ██████████████████████████████████▀ ▄████████████████ 17 | // ▀▀ ████████████████████████████████▀▀ ▄█████████████████▌ 18 | // █████████████████████▀▀▀▀▀▀▀ ▄▄███████████████████▀ 19 | // ██████████████████▀ ▄▄▄█████████████████████████████▀ 20 | // ████████████████▀ ▄█████████████████████████████████▀ ██▄ 21 | // ▐███████████████▀ ▄██████████████████████████████████▀ █████▄ 22 | // ██████████████▀ ▄█████████████████████████████████▀ ▄████████ 23 | // ██████████████▀ ███████████████████████████████▀ ▄████████████ 24 | // ▐█████████████▌ ▀▀▀▀████████████████████▀▀▀▀ █████████████▌ 25 | // ██████████████ ██████████████ 26 | // █████████████▌ ██████████████ 27 | // █████████████▌ ██████████████ 28 | // █████████████▌ ██████████████ 29 | // █████████████▌ ██████████████ 30 | // █████████████▌ ██████████████ 31 | // █████████████▌ ██████████████ 32 | // █████████████▌ ██████████████ 33 | // █████████████▌ ██████████████ 34 | 35 | pragma solidity ^0.8.10; 36 | 37 | import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; 38 | import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; 39 | 40 | /// @title Flash Loan Operator Interface (ERC-721) 41 | /// @author Jake Nyquist-j@hook.xyz 42 | /// @dev contracts that will utilize vaulted assets in flash loans should implement this interface in order to 43 | /// receive the asset. Users may want to receive the asset within a single block to claim airdrops, participate 44 | /// in governance, and other things with their assets. 45 | /// 46 | /// The implementer may do whatever they like with the vaulted NFT within the executeOperation method, 47 | /// so long as they approve the vault (passed as a param) to operate the underlying NFT. The Vault 48 | /// will move the asset back into the vault after executionOperation returns, and also validate that 49 | /// it is the owner of the asset. 50 | /// 51 | /// The flashloan receiver is able to abort a flashloan by returning false from the executeOperation method. 52 | interface IERC721FlashLoanReceiver is IERC721Receiver { 53 | /// @notice the method that contains the operations to be performed with the loaned asset 54 | /// @dev executeOperation is called immediately after the asset is transferred to this contract. After return, 55 | /// the asset is returned to the vault by the vault contract. The executeOperation implementation MUST 56 | /// approve the {vault} to operate the transferred NFT 57 | /// i.e. `IERC721(nftContract).setApprovalForAll(vault, true);` 58 | /// 59 | /// @param nftContract the address of the underlying erc-721 asset 60 | /// @param tokenId the address of the received erc-721 asset 61 | /// @param beneficialOwner the current beneficialOwner of the vault, who initialized the flashLoan 62 | /// @param vault the address of the vault performing the flashloan (in most cases, equal to msg.sender) 63 | /// @param params additional params passed by the caller into the flashloan 64 | function executeOperation( 65 | address nftContract, 66 | uint256 tokenId, 67 | address beneficialOwner, 68 | address vault, 69 | bytes calldata params 70 | ) external returns (bool); 71 | } 72 | -------------------------------------------------------------------------------- /src/interfaces/IHookCoveredCallFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // 3 | // █████████████▌ ▐█████████████ 4 | // █████████████▌ ▐█████████████ 5 | // █████████████▌ ▐█████████████ 6 | // █████████████▌ ▐█████████████ 7 | // █████████████▌ ▐█████████████ 8 | // █████████████▌ ▐█████████████ 9 | // █████████████▌ ▐█████████████ 10 | // █████████████▌ ▐█████████████ 11 | // ██████████████ ██████████████ 12 | // ██████████████ ▄▄████████████████▄▄ ▐█████████████▌ 13 | // ██████████████ ▄█████████████████████████████▄ ██████████████ 14 | // ██████████▀ ▄█████████████████████████████████ ██████████████▌ 15 | // ██████▀ ▄██████████████████████████████████▀ ▄███████████████ 16 | // ███▀ ██████████████████████████████████▀ ▄████████████████ 17 | // ▀▀ ████████████████████████████████▀▀ ▄█████████████████▌ 18 | // █████████████████████▀▀▀▀▀▀▀ ▄▄███████████████████▀ 19 | // ██████████████████▀ ▄▄▄█████████████████████████████▀ 20 | // ████████████████▀ ▄█████████████████████████████████▀ ██▄ 21 | // ▐███████████████▀ ▄██████████████████████████████████▀ █████▄ 22 | // ██████████████▀ ▄█████████████████████████████████▀ ▄████████ 23 | // ██████████████▀ ███████████████████████████████▀ ▄████████████ 24 | // ▐█████████████▌ ▀▀▀▀████████████████████▀▀▀▀ █████████████▌ 25 | // ██████████████ ██████████████ 26 | // █████████████▌ ██████████████ 27 | // █████████████▌ ██████████████ 28 | // █████████████▌ ██████████████ 29 | // █████████████▌ ██████████████ 30 | // █████████████▌ ██████████████ 31 | // █████████████▌ ██████████████ 32 | // █████████████▌ ██████████████ 33 | // █████████████▌ ██████████████ 34 | 35 | pragma solidity ^0.8.10; 36 | 37 | /// @title HookCoveredCallFactory-factory for instances of the Covered Call contract 38 | /// @author Jake Nyquist-j@hook.xyz 39 | /// @custom:coauthor Regynald Augustin-regy@hook.xyz 40 | /// 41 | /// @notice The Factory creates covered call instruments that support specific ERC-721 contracts, and 42 | /// also tracks all of the existing active markets. 43 | interface IHookCoveredCallFactory { 44 | /// @dev emitted whenever a new call instrument instance is created 45 | /// @param assetAddress the address of the asset underlying the covered call 46 | /// @param instrumentAddress the address of the covered call instrument 47 | event CoveredCallInstrumentCreated(address assetAddress, address instrumentAddress); 48 | 49 | /// @notice Lookup the call instrument contract based on the asset address 50 | /// @param assetAddress the contract address for the underlying asset 51 | /// @return the address of the instrument contract or the null address if one does not exist 52 | function getCallInstrument(address assetAddress) external view returns (address); 53 | 54 | /// @notice Create a call option instrument for a specific underlying asset address 55 | /// @param assetAddress the address for the underling asset 56 | /// @return the address of the call option instrument contract (upgradeable) 57 | function makeCallInstrument(address assetAddress) external returns (address); 58 | } 59 | -------------------------------------------------------------------------------- /src/interfaces/IHookERC20Vault.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // 3 | // █████████████▌ ▐█████████████ 4 | // █████████████▌ ▐█████████████ 5 | // █████████████▌ ▐█████████████ 6 | // █████████████▌ ▐█████████████ 7 | // █████████████▌ ▐█████████████ 8 | // █████████████▌ ▐█████████████ 9 | // █████████████▌ ▐█████████████ 10 | // █████████████▌ ▐█████████████ 11 | // ██████████████ ██████████████ 12 | // ██████████████ ▄▄████████████████▄▄ ▐█████████████▌ 13 | // ██████████████ ▄█████████████████████████████▄ ██████████████ 14 | // ██████████▀ ▄█████████████████████████████████ ██████████████▌ 15 | // ██████▀ ▄██████████████████████████████████▀ ▄███████████████ 16 | // ███▀ ██████████████████████████████████▀ ▄████████████████ 17 | // ▀▀ ████████████████████████████████▀▀ ▄█████████████████▌ 18 | // █████████████████████▀▀▀▀▀▀▀ ▄▄███████████████████▀ 19 | // ██████████████████▀ ▄▄▄█████████████████████████████▀ 20 | // ████████████████▀ ▄█████████████████████████████████▀ ██▄ 21 | // ▐███████████████▀ ▄██████████████████████████████████▀ █████▄ 22 | // ██████████████▀ ▄█████████████████████████████████▀ ▄████████ 23 | // ██████████████▀ ███████████████████████████████▀ ▄████████████ 24 | // ▐█████████████▌ ▀▀▀▀████████████████████▀▀▀▀ █████████████▌ 25 | // ██████████████ ██████████████ 26 | // █████████████▌ ██████████████ 27 | // █████████████▌ ██████████████ 28 | // █████████████▌ ██████████████ 29 | // █████████████▌ ██████████████ 30 | // █████████████▌ ██████████████ 31 | // █████████████▌ ██████████████ 32 | // █████████████▌ ██████████████ 33 | // █████████████▌ ██████████████ 34 | 35 | pragma solidity ^0.8.10; 36 | 37 | import "./IHookVault.sol"; 38 | 39 | /// @title Hook ERC-20 Vault interface 40 | /// @author Jake Nyquist-j@hook.xyz 41 | /// @custom:coauthor Regynald Augustin-regy@hook.xyz 42 | /// 43 | /// @dev the IHookERC20 vault is an extension of the standard IHookVault 44 | /// specifically designed to hold and receive ERC20 Tokens. 45 | /// 46 | interface IHookERC20Vault is IHookVault { 47 | /// @notice returns the balance of the underlying ERC20 token 48 | function assetBalance(uint32 assetId) external view returns (uint256); 49 | } 50 | -------------------------------------------------------------------------------- /src/interfaces/IHookERC721Vault.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // 3 | // █████████████▌ ▐█████████████ 4 | // █████████████▌ ▐█████████████ 5 | // █████████████▌ ▐█████████████ 6 | // █████████████▌ ▐█████████████ 7 | // █████████████▌ ▐█████████████ 8 | // █████████████▌ ▐█████████████ 9 | // █████████████▌ ▐█████████████ 10 | // █████████████▌ ▐█████████████ 11 | // ██████████████ ██████████████ 12 | // ██████████████ ▄▄████████████████▄▄ ▐█████████████▌ 13 | // ██████████████ ▄█████████████████████████████▄ ██████████████ 14 | // ██████████▀ ▄█████████████████████████████████ ██████████████▌ 15 | // ██████▀ ▄██████████████████████████████████▀ ▄███████████████ 16 | // ███▀ ██████████████████████████████████▀ ▄████████████████ 17 | // ▀▀ ████████████████████████████████▀▀ ▄█████████████████▌ 18 | // █████████████████████▀▀▀▀▀▀▀ ▄▄███████████████████▀ 19 | // ██████████████████▀ ▄▄▄█████████████████████████████▀ 20 | // ████████████████▀ ▄█████████████████████████████████▀ ██▄ 21 | // ▐███████████████▀ ▄██████████████████████████████████▀ █████▄ 22 | // ██████████████▀ ▄█████████████████████████████████▀ ▄████████ 23 | // ██████████████▀ ███████████████████████████████▀ ▄████████████ 24 | // ▐█████████████▌ ▀▀▀▀████████████████████▀▀▀▀ █████████████▌ 25 | // ██████████████ ██████████████ 26 | // █████████████▌ ██████████████ 27 | // █████████████▌ ██████████████ 28 | // █████████████▌ ██████████████ 29 | // █████████████▌ ██████████████ 30 | // █████████████▌ ██████████████ 31 | // █████████████▌ ██████████████ 32 | // █████████████▌ ██████████████ 33 | // █████████████▌ ██████████████ 34 | 35 | pragma solidity ^0.8.10; 36 | 37 | import "./IHookVault.sol"; 38 | import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; 39 | 40 | /// @title Hook ERC-721 Vault interface 41 | /// @author Jake Nyquist-j@hook.xyz 42 | /// @custom:coauthor Regynald Augustin-regy@hook.xyz 43 | /// 44 | /// @dev the IHookERC721 vault is an extension of the standard IHookVault 45 | /// specifically designed to hold and receive ERC721 Tokens. 46 | /// 47 | /// FLASH LOAN - 48 | /// (1) beneficial owners are able to borrow the vaulted asset for a single function call 49 | /// (2) to borrow the asset, they must implement and deploy a {IERC721FlashLoanReceiver} 50 | /// contract, and then call the flashLoan method. 51 | /// (3) At the end of the flashLoan, we ensure the asset is still owned by the vault. 52 | interface IHookERC721Vault is IHookVault, IERC721Receiver { 53 | /// @notice emitted after an asset is flash loaned by its beneficial owner. 54 | /// @dev only one asset can be flash loaned at a time, and that asset is 55 | /// denoted by the tokenId emitted. 56 | event AssetFlashLoaned(address owner, uint256 tokenId, address flashLoanImpl); 57 | 58 | /// @notice the tokenID of the underlying ERC721 token; 59 | function assetTokenId(uint32 assetId) external view returns (uint256); 60 | 61 | /// @notice flashLoans the vaulted asset to another contract for use and return to the vault. Only the owner 62 | /// may perform the flashloan 63 | /// @dev the flashloan receiver can perform arbitrary logic, but must approve the vault as an operator 64 | /// before returning. 65 | /// @param receiverAddress the contract which implements the {IERC721FlashLoanReceiver} interface to utilize the 66 | /// asset while it is loaned out 67 | /// @param params calldata params to forward to the receiver 68 | function flashLoan(uint32 assetId, address receiverAddress, bytes calldata params) external; 69 | } 70 | -------------------------------------------------------------------------------- /src/interfaces/IHookERC721VaultFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // 3 | // █████████████▌ ▐█████████████ 4 | // █████████████▌ ▐█████████████ 5 | // █████████████▌ ▐█████████████ 6 | // █████████████▌ ▐█████████████ 7 | // █████████████▌ ▐█████████████ 8 | // █████████████▌ ▐█████████████ 9 | // █████████████▌ ▐█████████████ 10 | // █████████████▌ ▐█████████████ 11 | // ██████████████ ██████████████ 12 | // ██████████████ ▄▄████████████████▄▄ ▐█████████████▌ 13 | // ██████████████ ▄█████████████████████████████▄ ██████████████ 14 | // ██████████▀ ▄█████████████████████████████████ ██████████████▌ 15 | // ██████▀ ▄██████████████████████████████████▀ ▄███████████████ 16 | // ███▀ ██████████████████████████████████▀ ▄████████████████ 17 | // ▀▀ ████████████████████████████████▀▀ ▄█████████████████▌ 18 | // █████████████████████▀▀▀▀▀▀▀ ▄▄███████████████████▀ 19 | // ██████████████████▀ ▄▄▄█████████████████████████████▀ 20 | // ████████████████▀ ▄█████████████████████████████████▀ ██▄ 21 | // ▐███████████████▀ ▄██████████████████████████████████▀ █████▄ 22 | // ██████████████▀ ▄█████████████████████████████████▀ ▄████████ 23 | // ██████████████▀ ███████████████████████████████▀ ▄████████████ 24 | // ▐█████████████▌ ▀▀▀▀████████████████████▀▀▀▀ █████████████▌ 25 | // ██████████████ ██████████████ 26 | // █████████████▌ ██████████████ 27 | // █████████████▌ ██████████████ 28 | // █████████████▌ ██████████████ 29 | // █████████████▌ ██████████████ 30 | // █████████████▌ ██████████████ 31 | // █████████████▌ ██████████████ 32 | // █████████████▌ ██████████████ 33 | // █████████████▌ ██████████████ 34 | 35 | pragma solidity ^0.8.10; 36 | 37 | import "./IHookERC721Vault.sol"; 38 | 39 | /// @title HookERC721Factory-factory for instances of the hook vault 40 | /// @author Jake Nyquist-j@hook.xyz 41 | /// @custom:coauthor Regynald Augustin-regy@hook.xyz 42 | /// 43 | /// @notice The Factory creates a specific vault for ERC721s. 44 | interface IHookERC721VaultFactory { 45 | event ERC721VaultCreated(address nftAddress, uint256 tokenId, address vaultAddress); 46 | 47 | /// @notice emitted when a new MultiVault is deployed by the protocol 48 | /// @param nftAddress the address of the nft contract that may be deposited into the new vault 49 | /// @param vaultAddress address of the newly deployed vault 50 | event ERC721MultiVaultCreated(address nftAddress, address vaultAddress); 51 | 52 | /// @notice gets the address of a vault for a particular ERC-721 token 53 | /// @param nftAddress the contract address for the ERC-721 54 | /// @param tokenId the tokenId for the ERC-721 55 | /// @return the address of a {IERC721Vault} if one exists that supports the particular ERC-721, or the null address otherwise 56 | function getVault(address nftAddress, uint256 tokenId) external view returns (IHookERC721Vault); 57 | 58 | /// @notice gets the address of a multi-asset vault for a particular ERC-721 contract, if one exists 59 | /// @param nftAddress the contract address for the ERC-721 60 | /// @return the address of the {IERC721Vault} multi asset vault, or the null address if one does not exist 61 | function getMultiVault(address nftAddress) external view returns (IHookERC721Vault); 62 | 63 | /// @notice deploy a multi-asset vault if one has not already been deployed 64 | /// @param nftAddress the contract address for the ERC-721 to be supported by the vault 65 | /// @return the address of the newly deployed {IERC721Vault} multi asset vault 66 | function makeMultiVault(address nftAddress) external returns (IHookERC721Vault); 67 | 68 | /// @notice creates a vault for a specific tokenId. If there 69 | /// is a multi-vault in existence which supports that address 70 | /// the address for that vault is returned as a new one 71 | /// does not need to be made. 72 | /// @param nftAddress the contract address for the ERC-721 73 | /// @param tokenId the tokenId for the ERC-721 74 | function findOrCreateVault(address nftAddress, uint256 tokenId) external returns (IHookERC721Vault); 75 | 76 | /// @notice make a new vault that can contain a single asset only 77 | /// @dev the only valid asset id in this vault is = 0 78 | /// @param nftAddress the address of the underlying nft contract 79 | /// @param tokenId the individual token that can be deposited into this vault 80 | function makeSoloVault(address nftAddress, uint256 tokenId) external returns (IHookERC721Vault); 81 | } 82 | -------------------------------------------------------------------------------- /src/interfaces/IHookOption.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // 3 | // █████████████▌ ▐█████████████ 4 | // █████████████▌ ▐█████████████ 5 | // █████████████▌ ▐█████████████ 6 | // █████████████▌ ▐█████████████ 7 | // █████████████▌ ▐█████████████ 8 | // █████████████▌ ▐█████████████ 9 | // █████████████▌ ▐█████████████ 10 | // █████████████▌ ▐█████████████ 11 | // ██████████████ ██████████████ 12 | // ██████████████ ▄▄████████████████▄▄ ▐█████████████▌ 13 | // ██████████████ ▄█████████████████████████████▄ ██████████████ 14 | // ██████████▀ ▄█████████████████████████████████ ██████████████▌ 15 | // ██████▀ ▄██████████████████████████████████▀ ▄███████████████ 16 | // ███▀ ██████████████████████████████████▀ ▄████████████████ 17 | // ▀▀ ████████████████████████████████▀▀ ▄█████████████████▌ 18 | // █████████████████████▀▀▀▀▀▀▀ ▄▄███████████████████▀ 19 | // ██████████████████▀ ▄▄▄█████████████████████████████▀ 20 | // ████████████████▀ ▄█████████████████████████████████▀ ██▄ 21 | // ▐███████████████▀ ▄██████████████████████████████████▀ █████▄ 22 | // ██████████████▀ ▄█████████████████████████████████▀ ▄████████ 23 | // ██████████████▀ ███████████████████████████████▀ ▄████████████ 24 | // ▐█████████████▌ ▀▀▀▀████████████████████▀▀▀▀ █████████████▌ 25 | // ██████████████ ██████████████ 26 | // █████████████▌ ██████████████ 27 | // █████████████▌ ██████████████ 28 | // █████████████▌ ██████████████ 29 | // █████████████▌ ██████████████ 30 | // █████████████▌ ██████████████ 31 | // █████████████▌ ██████████████ 32 | // █████████████▌ ██████████████ 33 | // █████████████▌ ██████████████ 34 | 35 | pragma solidity ^0.8.10; 36 | 37 | interface IHookOption { 38 | enum OptionType { 39 | CALL, 40 | PUT 41 | } 42 | enum OptionClass { 43 | EUROPEAN, 44 | AMERICAN 45 | } 46 | 47 | function getStrikePrice(uint256 optionId) external view returns (uint256); 48 | function getExpiration(uint256 optionId) external view returns (uint256); 49 | } 50 | -------------------------------------------------------------------------------- /src/interfaces/IHookProtocol.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // 3 | // █████████████▌ ▐█████████████ 4 | // █████████████▌ ▐█████████████ 5 | // █████████████▌ ▐█████████████ 6 | // █████████████▌ ▐█████████████ 7 | // █████████████▌ ▐█████████████ 8 | // █████████████▌ ▐█████████████ 9 | // █████████████▌ ▐█████████████ 10 | // █████████████▌ ▐█████████████ 11 | // ██████████████ ██████████████ 12 | // ██████████████ ▄▄████████████████▄▄ ▐█████████████▌ 13 | // ██████████████ ▄█████████████████████████████▄ ██████████████ 14 | // ██████████▀ ▄█████████████████████████████████ ██████████████▌ 15 | // ██████▀ ▄██████████████████████████████████▀ ▄███████████████ 16 | // ███▀ ██████████████████████████████████▀ ▄████████████████ 17 | // ▀▀ ████████████████████████████████▀▀ ▄█████████████████▌ 18 | // █████████████████████▀▀▀▀▀▀▀ ▄▄███████████████████▀ 19 | // ██████████████████▀ ▄▄▄█████████████████████████████▀ 20 | // ████████████████▀ ▄█████████████████████████████████▀ ██▄ 21 | // ▐███████████████▀ ▄██████████████████████████████████▀ █████▄ 22 | // ██████████████▀ ▄█████████████████████████████████▀ ▄████████ 23 | // ██████████████▀ ███████████████████████████████▀ ▄████████████ 24 | // ▐█████████████▌ ▀▀▀▀████████████████████▀▀▀▀ █████████████▌ 25 | // ██████████████ ██████████████ 26 | // █████████████▌ ██████████████ 27 | // █████████████▌ ██████████████ 28 | // █████████████▌ ██████████████ 29 | // █████████████▌ ██████████████ 30 | // █████████████▌ ██████████████ 31 | // █████████████▌ ██████████████ 32 | // █████████████▌ ██████████████ 33 | // █████████████▌ ██████████████ 34 | 35 | pragma solidity ^0.8.10; 36 | 37 | import "@openzeppelin/contracts/access/IAccessControl.sol"; 38 | 39 | /// @title HookProtocol configuration and access control repository 40 | /// @author Jake Nyquist-j@hook.xyz 41 | /// @custom:coauthor Regynald Augustin-regy@hook.xyz 42 | /// 43 | /// @dev it is critically important that the particular protocol implementation 44 | /// is correct as, if it is not, all assets contained within protocol contracts 45 | /// can be easily compromised. 46 | interface IHookProtocol is IAccessControl { 47 | /// @notice the address of the deployed CoveredCallFactory used by the protocol 48 | function coveredCallContract() external view returns (address); 49 | 50 | /// @notice the address of the deployed VaultFactory used by the protocol 51 | function vaultContract() external view returns (address); 52 | 53 | /// @notice callable function that reverts when the protocol is paused 54 | function throwWhenPaused() external; 55 | 56 | /// @notice the standard weth address on this chain 57 | /// @dev these are values for popular chains: 58 | /// mainnet: 0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2 59 | /// kovan: 0xd0a1e359811322d97991e03f863a0c30c2cf029c 60 | /// ropsten: 0xc778417e063141139fce010982780140aa0cd5ab 61 | /// rinkeby: 0xc778417e063141139fce010982780140aa0cd5ab 62 | /// @return the weth address 63 | function getWETHAddress() external view returns (address); 64 | 65 | /// @notice get a configuration flag with a specific key for a collection 66 | /// @param collectionAddress the collection for which to lookup a configuration flag 67 | /// @param conf the config identifier for the configuration flag 68 | /// @return the true or false value of the config 69 | function getCollectionConfig(address collectionAddress, bytes32 conf) external view returns (bool); 70 | } 71 | -------------------------------------------------------------------------------- /src/interfaces/IHookVault.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // 3 | // █████████████▌ ▐█████████████ 4 | // █████████████▌ ▐█████████████ 5 | // █████████████▌ ▐█████████████ 6 | // █████████████▌ ▐█████████████ 7 | // █████████████▌ ▐█████████████ 8 | // █████████████▌ ▐█████████████ 9 | // █████████████▌ ▐█████████████ 10 | // █████████████▌ ▐█████████████ 11 | // ██████████████ ██████████████ 12 | // ██████████████ ▄▄████████████████▄▄ ▐█████████████▌ 13 | // ██████████████ ▄█████████████████████████████▄ ██████████████ 14 | // ██████████▀ ▄█████████████████████████████████ ██████████████▌ 15 | // ██████▀ ▄██████████████████████████████████▀ ▄███████████████ 16 | // ███▀ ██████████████████████████████████▀ ▄████████████████ 17 | // ▀▀ ████████████████████████████████▀▀ ▄█████████████████▌ 18 | // █████████████████████▀▀▀▀▀▀▀ ▄▄███████████████████▀ 19 | // ██████████████████▀ ▄▄▄█████████████████████████████▀ 20 | // ████████████████▀ ▄█████████████████████████████████▀ ██▄ 21 | // ▐███████████████▀ ▄██████████████████████████████████▀ █████▄ 22 | // ██████████████▀ ▄█████████████████████████████████▀ ▄████████ 23 | // ██████████████▀ ███████████████████████████████▀ ▄████████████ 24 | // ▐█████████████▌ ▀▀▀▀████████████████████▀▀▀▀ █████████████▌ 25 | // ██████████████ ██████████████ 26 | // █████████████▌ ██████████████ 27 | // █████████████▌ ██████████████ 28 | // █████████████▌ ██████████████ 29 | // █████████████▌ ██████████████ 30 | // █████████████▌ ██████████████ 31 | // █████████████▌ ██████████████ 32 | // █████████████▌ ██████████████ 33 | // █████████████▌ ██████████████ 34 | 35 | pragma solidity ^0.8.10; 36 | 37 | import "../lib/Entitlements.sol"; 38 | import "@openzeppelin/contracts/utils/introspection/IERC165.sol"; 39 | 40 | /// @title Generic Hook Vault-a vault designed to contain a single asset to be used as escrow. 41 | /// @author Jake Nyquist-j@hook.xyz 42 | /// @custom:coauthor Regynald Augustin-regy@hook.xyz 43 | /// 44 | /// @notice The Vault holds an asset on behalf of the owner. The owner is able to post this 45 | /// asset as collateral to other protocols by signing a message, called an "entitlement", that gives 46 | /// a specific account the ability to change the owner. 47 | /// 48 | /// The vault can work with multiple assets via the assetId, where the asset or set of assets covered by 49 | /// each segment is granted an individual id. 50 | /// Every asset must be identified by an assetId to comply with this interface, even if the vault only contains 51 | /// one asset. 52 | /// 53 | /// ENTITLEMENTS - 54 | /// (1) only one entitlement can be placed at a time. 55 | /// (2) entitlements must expire, but can also be cleared by the entitled party 56 | /// (3) if an entitlement expires, the current beneficial owner gains immediate sole control over the 57 | /// asset 58 | /// (4) the entitled entity can modify the beneficial owner of the asset, but cannot withdrawal. 59 | /// (5) the beneficial owner cannot modify the beneficial owner while an entitlement is in place 60 | /// 61 | interface IHookVault is IERC165 { 62 | /// @notice emitted when an entitlement is placed on an asset 63 | event EntitlementImposed(uint32 assetId, address entitledAccount, uint32 expiry, address beneficialOwner); 64 | 65 | /// @notice emitted when an entitlement is cleared from an asset 66 | event EntitlementCleared(uint256 assetId, address beneficialOwner); 67 | 68 | /// @notice emitted when the beneficial owner of an asset changes 69 | /// @dev it is not required that this event is emitted when an entitlement is 70 | /// imposed that also modifies the beneficial owner. 71 | event BeneficialOwnerSet(uint32 assetId, address beneficialOwner, address setBy); 72 | 73 | /// @notice emitted when an asset is added into the vault 74 | event AssetReceived(address owner, address sender, address contractAddress, uint32 assetId); 75 | 76 | /// @notice Emitted when `beneficialOwner` enables `approved` to manage the `assetId` asset. 77 | event Approval(address indexed beneficialOwner, address indexed approved, uint32 indexed assetId); 78 | 79 | /// @notice emitted when an asset is withdrawn from the vault 80 | event AssetWithdrawn(uint32 assetId, address to, address beneficialOwner); 81 | 82 | /// @notice Withdrawal an unencumbered asset from this vault 83 | /// @param assetId the asset to remove from the vault 84 | function withdrawalAsset(uint32 assetId) external; 85 | 86 | /// @notice setBeneficialOwner updates the current address that can claim the asset when it is free of entitlements. 87 | /// @param assetId the id of the subject asset to impose the entitlement 88 | /// @param newBeneficialOwner the account of the person who is able to withdrawal when there are no entitlements. 89 | function setBeneficialOwner(uint32 assetId, address newBeneficialOwner) external; 90 | 91 | /// @notice Add an entitlement claim to the asset held within the contract 92 | /// @param operator the operator to entitle 93 | /// @param expiry the duration of the entitlement 94 | /// @param assetId the id of the asset within the vault 95 | /// @param v sig v 96 | /// @param r sig r 97 | /// @param s sig s 98 | function imposeEntitlement(address operator, uint32 expiry, uint32 assetId, uint8 v, bytes32 r, bytes32 s) 99 | external; 100 | 101 | /// @notice Allows the beneficial owner to grant an entitlement to an asset within the contract 102 | /// @dev this function call is signed by the sender per the EVM, so we know the entitlement is authentic 103 | /// @param entitlement The entitlement to impose onto the contract 104 | function grantEntitlement(Entitlements.Entitlement calldata entitlement) external; 105 | 106 | /// @notice Allows the entitled address to release their claim on the asset 107 | /// @param assetId the id of the asset to clear 108 | function clearEntitlement(uint32 assetId) external; 109 | 110 | /// @notice Removes the active entitlement from a vault and returns the asset to the beneficial owner 111 | /// @param receiver the intended receiver of the asset 112 | /// @param assetId the Id of the asset to clear 113 | function clearEntitlementAndDistribute(uint32 assetId, address receiver) external; 114 | 115 | /// @notice looks up the current beneficial owner of the asset 116 | /// @param assetId the referenced asset 117 | /// @return the address of the beneficial owner of the asset 118 | function getBeneficialOwner(uint32 assetId) external view returns (address); 119 | 120 | /// @notice checks if the asset is currently stored in the vault 121 | /// @param assetId the referenced asset 122 | /// @return true if the asset is currently within the vault, false otherwise 123 | function getHoldsAsset(uint32 assetId) external view returns (bool); 124 | 125 | /// @notice the contract address of the vaulted asset 126 | /// @param assetId the referenced asset 127 | /// @return the contract address of the vaulted asset 128 | function assetAddress(uint32 assetId) external view returns (address); 129 | 130 | /// @notice looks up the current operator of an entitlement on an asset 131 | /// @param assetId the id of the underlying asset 132 | function getCurrentEntitlementOperator(uint32 assetId) external view returns (bool, address); 133 | 134 | /// @notice Looks up the expiration timestamp of the current entitlement 135 | /// @dev returns the 0 if no entitlement is set 136 | /// @return the block timestamp after which the entitlement expires 137 | function entitlementExpiration(uint32 assetId) external view returns (uint32); 138 | 139 | /// @notice Gives permission to `to` to impose an entitlement upon `assetId` 140 | /// 141 | /// @dev Only a single account can be approved at a time, so approving the zero address clears previous approvals. 142 | /// * Requirements: 143 | /// 144 | /// - The caller must be the beneficial owner 145 | /// - `tokenId` must exist. 146 | /// 147 | /// Emits an {Approval} event. 148 | function approveOperator(address to, uint32 assetId) external; 149 | 150 | /// @dev Returns the account approved for `tokenId` token. 151 | /// 152 | /// Requirements: 153 | /// 154 | /// - `assetId` must exist. 155 | /// 156 | function getApprovedOperator(uint32 assetId) external view returns (address); 157 | } 158 | -------------------------------------------------------------------------------- /src/interfaces/IInitializeableBeacon.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // 3 | // █████████████▌ ▐█████████████ 4 | // █████████████▌ ▐█████████████ 5 | // █████████████▌ ▐█████████████ 6 | // █████████████▌ ▐█████████████ 7 | // █████████████▌ ▐█████████████ 8 | // █████████████▌ ▐█████████████ 9 | // █████████████▌ ▐█████████████ 10 | // █████████████▌ ▐█████████████ 11 | // ██████████████ ██████████████ 12 | // ██████████████ ▄▄████████████████▄▄ ▐█████████████▌ 13 | // ██████████████ ▄█████████████████████████████▄ ██████████████ 14 | // ██████████▀ ▄█████████████████████████████████ ██████████████▌ 15 | // ██████▀ ▄██████████████████████████████████▀ ▄███████████████ 16 | // ███▀ ██████████████████████████████████▀ ▄████████████████ 17 | // ▀▀ ████████████████████████████████▀▀ ▄█████████████████▌ 18 | // █████████████████████▀▀▀▀▀▀▀ ▄▄███████████████████▀ 19 | // ██████████████████▀ ▄▄▄█████████████████████████████▀ 20 | // ████████████████▀ ▄█████████████████████████████████▀ ██▄ 21 | // ▐███████████████▀ ▄██████████████████████████████████▀ █████▄ 22 | // ██████████████▀ ▄█████████████████████████████████▀ ▄████████ 23 | // ██████████████▀ ███████████████████████████████▀ ▄████████████ 24 | // ▐█████████████▌ ▀▀▀▀████████████████████▀▀▀▀ █████████████▌ 25 | // ██████████████ ██████████████ 26 | // █████████████▌ ██████████████ 27 | // █████████████▌ ██████████████ 28 | // █████████████▌ ██████████████ 29 | // █████████████▌ ██████████████ 30 | // █████████████▌ ██████████████ 31 | // █████████████▌ ██████████████ 32 | // █████████████▌ ██████████████ 33 | // █████████████▌ ██████████████ 34 | 35 | pragma solidity ^0.8.10; 36 | 37 | /// @title Interface for a beacon with an initializer function 38 | /// @author Jake Nyquist-j@hook.xyz 39 | /// @custom:coauthor Regynald Augustin-regy@hook.xyz 40 | /// 41 | /// @dev the Hook Beacons conform to this interface, and can be called 42 | /// with this initializer in order to start a beacon 43 | interface IInitializeableBeacon { 44 | function initializeBeacon(address beacon, bytes memory data) external; 45 | } 46 | -------------------------------------------------------------------------------- /src/interfaces/IWETH.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | 3 | pragma solidity ^0.8.10; 4 | 5 | interface IWETH { 6 | function deposit() external payable; 7 | 8 | function withdraw(uint256 wad) external; 9 | 10 | function transfer(address to, uint256 value) external returns (bool); 11 | } 12 | -------------------------------------------------------------------------------- /src/interfaces/delegate-cash/IDelegationRegistry.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: CC0-1.0 2 | pragma solidity 0.8.10; 3 | 4 | /** 5 | * @title An immutable registry contract to be deployed as a standalone primitive 6 | * @dev See EIP-5639, new project launches can read previous cold wallet -> hot wallet delegations 7 | * from here and integrate those permissions into their flow 8 | */ 9 | interface IDelegationRegistry { 10 | /// @notice Delegation type 11 | enum DelegationType { 12 | NONE, 13 | ALL, 14 | CONTRACT, 15 | TOKEN 16 | } 17 | 18 | /// @notice Info about a single delegation, used for onchain enumeration 19 | struct DelegationInfo { 20 | DelegationType type_; 21 | address vault; 22 | address delegate; 23 | address contract_; 24 | uint256 tokenId; 25 | } 26 | 27 | /// @notice Info about a single contract-level delegation 28 | struct ContractDelegation { 29 | address contract_; 30 | address delegate; 31 | } 32 | 33 | /// @notice Info about a single token-level delegation 34 | struct TokenDelegation { 35 | address contract_; 36 | uint256 tokenId; 37 | address delegate; 38 | } 39 | 40 | /// @notice Emitted when a user delegates their entire wallet 41 | event DelegateForAll(address vault, address delegate, bool value); 42 | 43 | /// @notice Emitted when a user delegates a specific contract 44 | event DelegateForContract(address vault, address delegate, address contract_, bool value); 45 | 46 | /// @notice Emitted when a user delegates a specific token 47 | event DelegateForToken(address vault, address delegate, address contract_, uint256 tokenId, bool value); 48 | 49 | /// @notice Emitted when a user revokes all delegations 50 | event RevokeAllDelegates(address vault); 51 | 52 | /// @notice Emitted when a user revoes all delegations for a given delegate 53 | event RevokeDelegate(address vault, address delegate); 54 | 55 | /** 56 | * ----------- WRITE ----------- 57 | */ 58 | 59 | /** 60 | * @notice Allow the delegate to act on your behalf for all contracts 61 | * @param delegate The hotwallet to act on your behalf 62 | * @param value Whether to enable or disable delegation for this address, true for setting and false for revoking 63 | */ 64 | function delegateForAll(address delegate, bool value) external; 65 | 66 | /** 67 | * @notice Allow the delegate to act on your behalf for a specific contract 68 | * @param delegate The hotwallet to act on your behalf 69 | * @param contract_ The address for the contract you're delegating 70 | * @param value Whether to enable or disable delegation for this address, true for setting and false for revoking 71 | */ 72 | function delegateForContract(address delegate, address contract_, bool value) external; 73 | 74 | /** 75 | * @notice Allow the delegate to act on your behalf for a specific token 76 | * @param delegate The hotwallet to act on your behalf 77 | * @param contract_ The address for the contract you're delegating 78 | * @param tokenId The token id for the token you're delegating 79 | * @param value Whether to enable or disable delegation for this address, true for setting and false for revoking 80 | */ 81 | function delegateForToken(address delegate, address contract_, uint256 tokenId, bool value) external; 82 | 83 | /** 84 | * @notice Revoke all delegates 85 | */ 86 | function revokeAllDelegates() external; 87 | 88 | /** 89 | * @notice Revoke a specific delegate for all their permissions 90 | * @param delegate The hotwallet to revoke 91 | */ 92 | function revokeDelegate(address delegate) external; 93 | 94 | /** 95 | * @notice Remove yourself as a delegate for a specific vault 96 | * @param vault The vault which delegated to the msg.sender, and should be removed 97 | */ 98 | function revokeSelf(address vault) external; 99 | 100 | /** 101 | * ----------- READ ----------- 102 | */ 103 | 104 | /** 105 | * @notice Returns all active delegations a given delegate is able to claim on behalf of 106 | * @param delegate The delegate that you would like to retrieve delegations for 107 | * @return info Array of DelegationInfo structs 108 | */ 109 | function getDelegationsByDelegate(address delegate) external view returns (DelegationInfo[] memory); 110 | 111 | /** 112 | * @notice Returns an array of wallet-level delegates for a given vault 113 | * @param vault The cold wallet who issued the delegation 114 | * @return addresses Array of wallet-level delegates for a given vault 115 | */ 116 | function getDelegatesForAll(address vault) external view returns (address[] memory); 117 | 118 | /** 119 | * @notice Returns an array of contract-level delegates for a given vault and contract 120 | * @param vault The cold wallet who issued the delegation 121 | * @param contract_ The address for the contract you're delegating 122 | * @return addresses Array of contract-level delegates for a given vault and contract 123 | */ 124 | function getDelegatesForContract(address vault, address contract_) external view returns (address[] memory); 125 | 126 | /** 127 | * @notice Returns an array of contract-level delegates for a given vault's token 128 | * @param vault The cold wallet who issued the delegation 129 | * @param contract_ The address for the contract holding the token 130 | * @param tokenId The token id for the token you're delegating 131 | * @return addresses Array of contract-level delegates for a given vault's token 132 | */ 133 | function getDelegatesForToken(address vault, address contract_, uint256 tokenId) 134 | external 135 | view 136 | returns (address[] memory); 137 | 138 | /** 139 | * @notice Returns all contract-level delegations for a given vault 140 | * @param vault The cold wallet who issued the delegations 141 | * @return delegations Array of ContractDelegation structs 142 | */ 143 | function getContractLevelDelegations(address vault) 144 | external 145 | view 146 | returns (ContractDelegation[] memory delegations); 147 | 148 | /** 149 | * @notice Returns all token-level delegations for a given vault 150 | * @param vault The cold wallet who issued the delegations 151 | * @return delegations Array of TokenDelegation structs 152 | */ 153 | function getTokenLevelDelegations(address vault) external view returns (TokenDelegation[] memory delegations); 154 | 155 | /** 156 | * @notice Returns true if the address is delegated to act on the entire vault 157 | * @param delegate The hotwallet to act on your behalf 158 | * @param vault The cold wallet who issued the delegation 159 | */ 160 | function checkDelegateForAll(address delegate, address vault) external view returns (bool); 161 | 162 | /** 163 | * @notice Returns true if the address is delegated to act on your behalf for a token contract or an entire vault 164 | * @param delegate The hotwallet to act on your behalf 165 | * @param contract_ The address for the contract you're delegating 166 | * @param vault The cold wallet who issued the delegation 167 | */ 168 | function checkDelegateForContract(address delegate, address vault, address contract_) 169 | external 170 | view 171 | returns (bool); 172 | 173 | /** 174 | * @notice Returns true if the address is delegated to act on your behalf for a specific token, the token's contract or an entire vault 175 | * @param delegate The hotwallet to act on your behalf 176 | * @param contract_ The address for the contract you're delegating 177 | * @param tokenId The token id for the token you're delegating 178 | * @param vault The cold wallet who issued the delegation 179 | */ 180 | function checkDelegateForToken(address delegate, address vault, address contract_, uint256 tokenId) 181 | external 182 | view 183 | returns (bool); 184 | } 185 | -------------------------------------------------------------------------------- /src/interfaces/delegate-cash/README.md: -------------------------------------------------------------------------------- 1 | This folder contains the interfaces for the Delegate Cash contracts. 2 | 3 | They can be found here: 4 | https://github.com/delegatecash/delegation-registry/blob/main/src/IDelegationRegistry.sol 5 | 6 | The deployed address is 0x00000000000076A84feF008CDAbe6409d2FE638B on all chains. -------------------------------------------------------------------------------- /src/interfaces/zeroex-v4/IPropertyValidator.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | 3 | pragma solidity ^0.8.10; 4 | 5 | interface IPropertyValidator { 6 | /// @dev Checks that the given ERC721/ERC1155 asset satisfies the properties encoded in `propertyData`. 7 | /// Should revert if the asset does not satisfy the specified properties. 8 | /// @param tokenAddress The ERC721/ERC1155 token contract address. 9 | /// @param tokenId The ERC721/ERC1155 tokenId of the asset to check. 10 | /// @param propertyData Encoded properties or auxiliary data needed to perform the check. 11 | function validateProperty(address tokenAddress, uint256 tokenId, bytes calldata propertyData) external view; 12 | } 13 | -------------------------------------------------------------------------------- /src/lib/BeaconSalts.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // 3 | // █████████████▌ ▐█████████████ 4 | // █████████████▌ ▐█████████████ 5 | // █████████████▌ ▐█████████████ 6 | // █████████████▌ ▐█████████████ 7 | // █████████████▌ ▐█████████████ 8 | // █████████████▌ ▐█████████████ 9 | // █████████████▌ ▐█████████████ 10 | // █████████████▌ ▐█████████████ 11 | // ██████████████ ██████████████ 12 | // ██████████████ ▄▄████████████████▄▄ ▐█████████████▌ 13 | // ██████████████ ▄█████████████████████████████▄ ██████████████ 14 | // ██████████▀ ▄█████████████████████████████████ ██████████████▌ 15 | // ██████▀ ▄██████████████████████████████████▀ ▄███████████████ 16 | // ███▀ ██████████████████████████████████▀ ▄████████████████ 17 | // ▀▀ ████████████████████████████████▀▀ ▄█████████████████▌ 18 | // █████████████████████▀▀▀▀▀▀▀ ▄▄███████████████████▀ 19 | // ██████████████████▀ ▄▄▄█████████████████████████████▀ 20 | // ████████████████▀ ▄█████████████████████████████████▀ ██▄ 21 | // ▐███████████████▀ ▄██████████████████████████████████▀ █████▄ 22 | // ██████████████▀ ▄█████████████████████████████████▀ ▄████████ 23 | // ██████████████▀ ███████████████████████████████▀ ▄████████████ 24 | // ▐█████████████▌ ▀▀▀▀████████████████████▀▀▀▀ █████████████▌ 25 | // ██████████████ ██████████████ 26 | // █████████████▌ ██████████████ 27 | // █████████████▌ ██████████████ 28 | // █████████████▌ ██████████████ 29 | // █████████████▌ ██████████████ 30 | // █████████████▌ ██████████████ 31 | // █████████████▌ ██████████████ 32 | // █████████████▌ ██████████████ 33 | // █████████████▌ ██████████████ 34 | 35 | pragma solidity ^0.8.10; 36 | 37 | import "../HookBeaconProxy.sol"; 38 | 39 | library BeaconSalts { 40 | // keep functions internal to prevent the need for library linking 41 | // and to reduce gas costs 42 | // Specify the actually-deployed beacons on mainnet 43 | // bytes32 internal constant ByteCodeHash = 44 | // bytes32(0x9efc74de3a03a3f44d619e7f315880536876e16273d5fdee7b22fd4c1620f1d5); 45 | bytes32 internal constant ByteCodeHash = keccak256(type(HookBeaconProxy).creationCode); 46 | 47 | function soloVaultSalt(address nftAddress, uint256 tokenId) internal pure returns (bytes32) { 48 | return keccak256(abi.encode(nftAddress, tokenId)); 49 | } 50 | 51 | function multiVaultSalt(address nftAddress) internal pure returns (bytes32) { 52 | return keccak256(abi.encode(nftAddress)); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/lib/Entitlements.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // 3 | // █████████████▌ ▐█████████████ 4 | // █████████████▌ ▐█████████████ 5 | // █████████████▌ ▐█████████████ 6 | // █████████████▌ ▐█████████████ 7 | // █████████████▌ ▐█████████████ 8 | // █████████████▌ ▐█████████████ 9 | // █████████████▌ ▐█████████████ 10 | // █████████████▌ ▐█████████████ 11 | // ██████████████ ██████████████ 12 | // ██████████████ ▄▄████████████████▄▄ ▐█████████████▌ 13 | // ██████████████ ▄█████████████████████████████▄ ██████████████ 14 | // ██████████▀ ▄█████████████████████████████████ ██████████████▌ 15 | // ██████▀ ▄██████████████████████████████████▀ ▄███████████████ 16 | // ███▀ ██████████████████████████████████▀ ▄████████████████ 17 | // ▀▀ ████████████████████████████████▀▀ ▄█████████████████▌ 18 | // █████████████████████▀▀▀▀▀▀▀ ▄▄███████████████████▀ 19 | // ██████████████████▀ ▄▄▄█████████████████████████████▀ 20 | // ████████████████▀ ▄█████████████████████████████████▀ ██▄ 21 | // ▐███████████████▀ ▄██████████████████████████████████▀ █████▄ 22 | // ██████████████▀ ▄█████████████████████████████████▀ ▄████████ 23 | // ██████████████▀ ███████████████████████████████▀ ▄████████████ 24 | // ▐█████████████▌ ▀▀▀▀████████████████████▀▀▀▀ █████████████▌ 25 | // ██████████████ ██████████████ 26 | // █████████████▌ ██████████████ 27 | // █████████████▌ ██████████████ 28 | // █████████████▌ ██████████████ 29 | // █████████████▌ ██████████████ 30 | // █████████████▌ ██████████████ 31 | // █████████████▌ ██████████████ 32 | // █████████████▌ ██████████████ 33 | // █████████████▌ ██████████████ 34 | 35 | pragma solidity ^0.8.10; 36 | 37 | import "./Signatures.sol"; 38 | 39 | library Entitlements { 40 | uint256 private constant _ENTITLEMENT_TYPEHASH = uint256( 41 | keccak256( 42 | abi.encodePacked( 43 | "Entitlement(", 44 | "address beneficialOwner,", 45 | "address operator,", 46 | "address vaultAddress,", 47 | "uint32 assetId,", 48 | "uint32 expiry", 49 | ")" 50 | ) 51 | ) 52 | ); 53 | 54 | /// ---- STRUCTS ----- 55 | struct Entitlement { 56 | /// @notice the beneficial owner address this entitlement applies to. This address will also be the signer. 57 | address beneficialOwner; 58 | /// @notice the operating contract that can change ownership during the entitlement period. 59 | address operator; 60 | /// @notice the contract address for the vault that contains the underlying assets 61 | address vaultAddress; 62 | /// @notice the assetId of the asset or assets within the vault 63 | uint32 assetId; 64 | /// @notice the block timestamp after which the asset is free of the entitlement 65 | uint32 expiry; 66 | } 67 | 68 | function getEntitlementStructHash(Entitlement memory entitlement) internal pure returns (bytes32) { 69 | // TODO: Hash in place to save gas. 70 | return keccak256( 71 | abi.encode( 72 | _ENTITLEMENT_TYPEHASH, 73 | entitlement.beneficialOwner, 74 | entitlement.operator, 75 | entitlement.vaultAddress, 76 | entitlement.assetId, 77 | entitlement.expiry 78 | ) 79 | ); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/lib/HookStrings.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // 3 | // █████████████▌ ▐█████████████ 4 | // █████████████▌ ▐█████████████ 5 | // █████████████▌ ▐█████████████ 6 | // █████████████▌ ▐█████████████ 7 | // █████████████▌ ▐█████████████ 8 | // █████████████▌ ▐█████████████ 9 | // █████████████▌ ▐█████████████ 10 | // █████████████▌ ▐█████████████ 11 | // ██████████████ ██████████████ 12 | // ██████████████ ▄▄████████████████▄▄ ▐█████████████▌ 13 | // ██████████████ ▄█████████████████████████████▄ ██████████████ 14 | // ██████████▀ ▄█████████████████████████████████ ██████████████▌ 15 | // ██████▀ ▄██████████████████████████████████▀ ▄███████████████ 16 | // ███▀ ██████████████████████████████████▀ ▄████████████████ 17 | // ▀▀ ████████████████████████████████▀▀ ▄█████████████████▌ 18 | // █████████████████████▀▀▀▀▀▀▀ ▄▄███████████████████▀ 19 | // ██████████████████▀ ▄▄▄█████████████████████████████▀ 20 | // ████████████████▀ ▄█████████████████████████████████▀ ██▄ 21 | // ▐███████████████▀ ▄██████████████████████████████████▀ █████▄ 22 | // ██████████████▀ ▄█████████████████████████████████▀ ▄████████ 23 | // ██████████████▀ ███████████████████████████████▀ ▄████████████ 24 | // ▐█████████████▌ ▀▀▀▀████████████████████▀▀▀▀ █████████████▌ 25 | // ██████████████ ██████████████ 26 | // █████████████▌ ██████████████ 27 | // █████████████▌ ██████████████ 28 | // █████████████▌ ██████████████ 29 | // █████████████▌ ██████████████ 30 | // █████████████▌ ██████████████ 31 | // █████████████▌ ██████████████ 32 | // █████████████▌ ██████████████ 33 | // █████████████▌ ██████████████ 34 | 35 | pragma solidity ^0.8.10; 36 | 37 | library HookStrings { 38 | /// @dev toAsciiString creates a hex encoding of an 39 | /// address as a string to use in the preview NFT. 40 | function toAsciiString(address x) internal pure returns (string memory) { 41 | bytes memory s = new bytes(40); 42 | for (uint256 i = 0; i < 20; i++) { 43 | bytes1 b = bytes1(uint8(uint256(uint160(x)) / (2 ** (8 * (19 - i))))); 44 | bytes1 hi = bytes1(uint8(b) / 16); 45 | bytes1 lo = bytes1(uint8(b) - 16 * uint8(hi)); 46 | s[2 * i] = char(hi); 47 | s[2 * i + 1] = char(lo); 48 | } 49 | return string(s); 50 | } 51 | 52 | function char(bytes1 b) internal pure returns (bytes1) { 53 | if (uint8(b) < 10) return bytes1(uint8(b) + 0x30); 54 | else return bytes1(uint8(b) + 0x57); 55 | } 56 | 57 | function toString(uint256 value) internal pure returns (string memory) { 58 | // Inspired by OraclizeAPI's implementation - MIT license 59 | // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol 60 | 61 | if (value == 0) { 62 | return "0"; 63 | } 64 | uint256 temp = value; 65 | uint256 digits; 66 | while (temp != 0) { 67 | digits++; 68 | temp /= 10; 69 | } 70 | bytes memory buffer = new bytes(digits); 71 | while (value != 0) { 72 | digits -= 1; 73 | buffer[digits] = bytes1(uint8(48 + uint256(value % 10))); 74 | value /= 10; 75 | } 76 | return string(buffer); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/lib/PoolOrders.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // 3 | // █████████████▌ ▐█████████████ 4 | // █████████████▌ ▐█████████████ 5 | // █████████████▌ ▐█████████████ 6 | // █████████████▌ ▐█████████████ 7 | // █████████████▌ ▐█████████████ 8 | // █████████████▌ ▐█████████████ 9 | // █████████████▌ ▐█████████████ 10 | // █████████████▌ ▐█████████████ 11 | // ██████████████ ██████████████ 12 | // ██████████████ ▄▄████████████████▄▄ ▐█████████████▌ 13 | // ██████████████ ▄█████████████████████████████▄ ██████████████ 14 | // ██████████▀ ▄█████████████████████████████████ ██████████████▌ 15 | // ██████▀ ▄██████████████████████████████████▀ ▄███████████████ 16 | // ███▀ ██████████████████████████████████▀ ▄████████████████ 17 | // ▀▀ ████████████████████████████████▀▀ ▄█████████████████▌ 18 | // █████████████████████▀▀▀▀▀▀▀ ▄▄███████████████████▀ 19 | // ██████████████████▀ ▄▄▄█████████████████████████████▀ 20 | // ████████████████▀ ▄█████████████████████████████████▀ ██▄ 21 | // ▐███████████████▀ ▄██████████████████████████████████▀ █████▄ 22 | // ██████████████▀ ▄█████████████████████████████████▀ ▄████████ 23 | // ██████████████▀ ███████████████████████████████▀ ▄████████████ 24 | // ▐█████████████▌ ▀▀▀▀████████████████████▀▀▀▀ █████████████▌ 25 | // ██████████████ ██████████████ 26 | // █████████████▌ ██████████████ 27 | // █████████████▌ ██████████████ 28 | // █████████████▌ ██████████████ 29 | // █████████████▌ ██████████████ 30 | // █████████████▌ ██████████████ 31 | // █████████████▌ ██████████████ 32 | // █████████████▌ ██████████████ 33 | // █████████████▌ ██████████████ 34 | 35 | pragma solidity ^0.8.10; 36 | 37 | import "../interfaces/zeroex-v4/IPropertyValidator.sol"; 38 | 39 | library PoolOrders { 40 | uint256 private constant _PROPERTY_TYPEHASH = 41 | uint256(keccak256(abi.encodePacked("Property(", "address propertyValidator,", "bytes propertyData", ")"))); 42 | 43 | // uint256 private constant _ORDER_TYPEHASH = abi.encode( 44 | // "Order(", 45 | // "uint8 direction,", 46 | // "address maker,", 47 | // "uint256 orderExpiry,", 48 | // "uint256 nonce,", 49 | // "uint8 size,", 50 | // "uint8 optionType,", 51 | // "uint256 maxStrikePriceMultiple," 52 | // "uint64 minOptionDuration,", 53 | // "uint64 maxOptionDuration,", 54 | // "uint64 maxPriceSignalAge,", 55 | // "Property[] nftProperties,", 56 | // "address optionMarketAddress,", 57 | // "uint64 impliedVolBips,", 58 | // "uint256 skewDecimal,", 59 | // "uint64 riskFreeRateBips", 60 | // ")", 61 | // _PROPERTY_TYPEHASH 62 | // ); 63 | uint256 private constant _ORDER_TYPEHASH = 0xcf88a2fdf20e362d67310061df675df92f17bd55a872a02e14b7dc017475f705; 64 | 65 | /// ---- ENUMS ----- 66 | enum OptionType { 67 | CALL, 68 | PUT 69 | } 70 | 71 | enum OrderDirection { 72 | BUY, 73 | SELL 74 | } 75 | 76 | /// ---- STRUCTS ----- 77 | struct Property { 78 | IPropertyValidator propertyValidator; 79 | bytes propertyData; 80 | } 81 | 82 | struct Order { 83 | /// @notice the direction of the order. Only BUY orders are currently supported 84 | OrderDirection direction; 85 | /// @notice the address of the maker who must sign this order 86 | address maker; 87 | /// @notice the block timestamp at which this order can no longer be filled 88 | uint256 orderExpiry; 89 | /// @notice a cryptographic nonce used to make the order unique 90 | uint256 nonce; 91 | /// @notice the maximum number of times this order can be filled 92 | uint8 size; 93 | OptionType optionType; 94 | /// @notice decimal in the money or out of the money an option can be filled at. For example, 5e17 == 50% out of the money max for a call option. 0 means no max 95 | uint256 maxStrikePriceMultiple; 96 | /// @notice minimum time from the time the order is filled that the option could expire. 0 means no min 97 | uint64 minOptionDuration; 98 | /// @notice maximum time from the time the order is filled that the option could expire. 0 means no max 99 | uint64 maxOptionDuration; 100 | /// @notice maximum age of a price signal to accept as a valid floor price 101 | uint64 maxPriceSignalAge; 102 | /// @notice array of property validators if the filler would like more fine-grained control of the filling option instrument 103 | Property[] nftProperties; 104 | /// @notice address of Hook option market (and option instrument) that can fill this order. This address must be trusted by the orderer to deliver the correct type of call instrument. 105 | address optionMarketAddress; 106 | /// @notice impliedVolBips is the maximum implied volatility of the desired options in bips (1/100th of a percent). For example, 100 bips = 1%. 107 | uint64 impliedVolBips; 108 | /// @notice the decimal-described slope of the skew for the desired implied volatility 109 | uint256 skewDecimal; 110 | /// @notice riskFreeRateBips is the percentage risk free rate + carry costs (e.g. 100 = 1%). About 5% is typical. 111 | uint64 riskFreeRateBips; 112 | } 113 | 114 | function _propertiesHash(Property[] memory properties) private pure returns (bytes32 propertiesHash) { 115 | uint256 numProperties = properties.length; 116 | if (numProperties == 0) { 117 | return 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470; 118 | } 119 | bytes32[] memory propertyStructHashArray = new bytes32[](numProperties); 120 | for (uint256 i = 0; i < numProperties; i++) { 121 | propertyStructHashArray[i] = keccak256( 122 | abi.encode(_PROPERTY_TYPEHASH, properties[i].propertyValidator, keccak256(properties[i].propertyData)) 123 | ); 124 | } 125 | return keccak256(abi.encodePacked(propertyStructHashArray)); 126 | } 127 | 128 | /// @dev split the hash to resolve a stack too deep error 129 | function _hashPt1(Order memory poolOrder) private pure returns (bytes memory) { 130 | return abi.encode( 131 | _ORDER_TYPEHASH, 132 | poolOrder.direction, 133 | poolOrder.maker, 134 | poolOrder.orderExpiry, 135 | poolOrder.nonce, 136 | poolOrder.size, 137 | poolOrder.optionType, 138 | poolOrder.maxStrikePriceMultiple 139 | ); 140 | } 141 | 142 | /// @dev split the hash to resolve a stack too deep error 143 | function _hashPt2(Order memory poolOrder) private pure returns (bytes memory) { 144 | return abi.encode( 145 | poolOrder.minOptionDuration, 146 | poolOrder.maxOptionDuration, 147 | poolOrder.maxPriceSignalAge, 148 | _propertiesHash(poolOrder.nftProperties), 149 | poolOrder.optionMarketAddress, 150 | poolOrder.impliedVolBips, 151 | poolOrder.skewDecimal, 152 | poolOrder.riskFreeRateBips 153 | ); 154 | } 155 | 156 | function getPoolOrderStructHash(Order memory poolOrder) internal pure returns (bytes32) { 157 | return keccak256(abi.encodePacked(_hashPt1(poolOrder), _hashPt2(poolOrder))); 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/lib/Signatures.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // 3 | // █████████████▌ ▐█████████████ 4 | // █████████████▌ ▐█████████████ 5 | // █████████████▌ ▐█████████████ 6 | // █████████████▌ ▐█████████████ 7 | // █████████████▌ ▐█████████████ 8 | // █████████████▌ ▐█████████████ 9 | // █████████████▌ ▐█████████████ 10 | // █████████████▌ ▐█████████████ 11 | // ██████████████ ██████████████ 12 | // ██████████████ ▄▄████████████████▄▄ ▐█████████████▌ 13 | // ██████████████ ▄█████████████████████████████▄ ██████████████ 14 | // ██████████▀ ▄█████████████████████████████████ ██████████████▌ 15 | // ██████▀ ▄██████████████████████████████████▀ ▄███████████████ 16 | // ███▀ ██████████████████████████████████▀ ▄████████████████ 17 | // ▀▀ ████████████████████████████████▀▀ ▄█████████████████▌ 18 | // █████████████████████▀▀▀▀▀▀▀ ▄▄███████████████████▀ 19 | // ██████████████████▀ ▄▄▄█████████████████████████████▀ 20 | // ████████████████▀ ▄█████████████████████████████████▀ ██▄ 21 | // ▐███████████████▀ ▄██████████████████████████████████▀ █████▄ 22 | // ██████████████▀ ▄█████████████████████████████████▀ ▄████████ 23 | // ██████████████▀ ███████████████████████████████▀ ▄████████████ 24 | // ▐█████████████▌ ▀▀▀▀████████████████████▀▀▀▀ █████████████▌ 25 | // ██████████████ ██████████████ 26 | // █████████████▌ ██████████████ 27 | // █████████████▌ ██████████████ 28 | // █████████████▌ ██████████████ 29 | // █████████████▌ ██████████████ 30 | // █████████████▌ ██████████████ 31 | // █████████████▌ ██████████████ 32 | // █████████████▌ ██████████████ 33 | // █████████████▌ ██████████████ 34 | 35 | pragma solidity ^0.8.10; 36 | 37 | /// @dev A library for validating signatures from ZeroEx 38 | library Signatures { 39 | /// @dev Allowed signature types. 40 | enum SignatureType {EIP712} 41 | 42 | /// @dev Encoded EC signature. 43 | struct Signature { 44 | // How to validate the signature. 45 | SignatureType signatureType; 46 | // EC Signature data. 47 | uint8 v; 48 | // EC Signature data. 49 | bytes32 r; 50 | // EC Signature data. 51 | bytes32 s; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/lib/TokenURI.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // 3 | // █████████████▌ ▐█████████████ 4 | // █████████████▌ ▐█████████████ 5 | // █████████████▌ ▐█████████████ 6 | // █████████████▌ ▐█████████████ 7 | // █████████████▌ ▐█████████████ 8 | // █████████████▌ ▐█████████████ 9 | // █████████████▌ ▐█████████████ 10 | // █████████████▌ ▐█████████████ 11 | // ██████████████ ██████████████ 12 | // ██████████████ ▄▄████████████████▄▄ ▐█████████████▌ 13 | // ██████████████ ▄█████████████████████████████▄ ██████████████ 14 | // ██████████▀ ▄█████████████████████████████████ ██████████████▌ 15 | // ██████▀ ▄██████████████████████████████████▀ ▄███████████████ 16 | // ███▀ ██████████████████████████████████▀ ▄████████████████ 17 | // ▀▀ ████████████████████████████████▀▀ ▄█████████████████▌ 18 | // █████████████████████▀▀▀▀▀▀▀ ▄▄███████████████████▀ 19 | // ██████████████████▀ ▄▄▄█████████████████████████████▀ 20 | // ████████████████▀ ▄█████████████████████████████████▀ ██▄ 21 | // ▐███████████████▀ ▄██████████████████████████████████▀ █████▄ 22 | // ██████████████▀ ▄█████████████████████████████████▀ ▄████████ 23 | // ██████████████▀ ███████████████████████████████▀ ▄████████████ 24 | // ▐█████████████▌ ▀▀▀▀████████████████████▀▀▀▀ █████████████▌ 25 | // ██████████████ ██████████████ 26 | // █████████████▌ ██████████████ 27 | // █████████████▌ ██████████████ 28 | // █████████████▌ ██████████████ 29 | // █████████████▌ ██████████████ 30 | // █████████████▌ ██████████████ 31 | // █████████████▌ ██████████████ 32 | // █████████████▌ ██████████████ 33 | // █████████████▌ ██████████████ 34 | 35 | pragma solidity ^0.8.10; 36 | 37 | import "@openzeppelin/contracts/utils/Base64.sol"; 38 | 39 | import "./HookStrings.sol"; 40 | 41 | /// @dev This contract implements some ERC721 / for hook instruments. 42 | library TokenURI { 43 | function _generateMetadataERC721( 44 | address underlyingTokenAddress, 45 | uint256 underlyingTokenId, 46 | uint256 instrumentStrikePrice, 47 | uint256 instrumentExpiration, 48 | uint256 transfers 49 | ) internal pure returns (string memory) { 50 | return string( 51 | abi.encodePacked( 52 | '"expiration": ', 53 | HookStrings.toString(instrumentExpiration), 54 | ', "underlying_address": "', 55 | HookStrings.toAsciiString(underlyingTokenAddress), 56 | '", "underlying_tokenId": ', 57 | HookStrings.toString(underlyingTokenId), 58 | ', "strike_price": ', 59 | HookStrings.toString(instrumentStrikePrice), 60 | ', "transfer_index": ', 61 | HookStrings.toString(transfers) 62 | ) 63 | ); 64 | } 65 | 66 | /// @dev this is a basic tokenURI based on the loot contract for an ERC721 67 | function tokenURIERC721( 68 | uint256 instrumentId, 69 | address underlyingAddress, 70 | uint256 underlyingTokenId, 71 | uint256 instrumentExpiration, 72 | uint256 instrumentStrike, 73 | uint256 transfers 74 | ) public view returns (string memory) { 75 | string memory json = Base64.encode( 76 | bytes( 77 | string( 78 | abi.encodePacked( 79 | '{"name": "Option ID ', 80 | HookStrings.toString(instrumentId), 81 | '",', 82 | _generateMetadataERC721( 83 | underlyingAddress, underlyingTokenId, instrumentStrike, instrumentExpiration, transfers 84 | ), 85 | ', "description": "Option Instrument NFT on Hook: the NFT-native call options protocol. Learn more at https://hook.xyz", "image": "https://option-images-hook.s3.amazonaws.com/nft/live_0x', 86 | HookStrings.toAsciiString(address(this)), 87 | "_", 88 | HookStrings.toString(instrumentId), 89 | '.png" }' 90 | ) 91 | ) 92 | ) 93 | ); 94 | return string(abi.encodePacked("data:application/json;base64,", json)); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/lib/lyra/FixedPointMathLib.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: ISC 2 | pragma solidity 0.8.10; 3 | 4 | // Slightly modified version of: 5 | // - https://github.com/recmo/experiment-solexp/blob/605738f3ed72d6c67a414e992be58262fbc9bb80/src/FixedPointMathLib.sol 6 | library FixedPointMathLib { 7 | /// @dev Computes ln(x) for a 1e27 fixed point. Loses 9 last significant digits of precision. 8 | function lnPrecise(int256 x) internal pure returns (int256 r) { 9 | return ln(x / 1e9) * 1e9; 10 | } 11 | 12 | /// @dev Computes e ^ x for a 1e27 fixed point. Loses 9 last significant digits of precision. 13 | function expPrecise(int256 x) internal pure returns (uint256 r) { 14 | return exp(x / 1e9) * 1e9; 15 | } 16 | 17 | // Computes ln(x) in 1e18 fixed point. 18 | // Reverts if x is negative or zero. 19 | // Consumes 670 gas. 20 | function ln(int256 x) internal pure returns (int256 r) { 21 | unchecked { 22 | if (x < 1) { 23 | if (x < 0) revert LnNegativeUndefined(); 24 | revert Overflow(); 25 | } 26 | 27 | // We want to convert x from 10**18 fixed point to 2**96 fixed point. 28 | // We do this by multiplying by 2**96 / 10**18. 29 | // But since ln(x * C) = ln(x) + ln(C), we can simply do nothing here 30 | // and add ln(2**96 / 10**18) at the end. 31 | 32 | // Reduce range of x to (1, 2) * 2**96 33 | // ln(2^k * x) = k * ln(2) + ln(x) 34 | // Note: inlining ilog2 saves 8 gas. 35 | int256 k = int256(ilog2(uint256(x))) - 96; 36 | x <<= uint256(159 - k); 37 | x = int256(uint256(x) >> 159); 38 | 39 | // Evaluate using a (8, 8)-term rational approximation 40 | // p is made monic, we will multiply by a scale factor later 41 | int256 p = x + 3273285459638523848632254066296; 42 | p = ((p * x) >> 96) + 24828157081833163892658089445524; 43 | p = ((p * x) >> 96) + 43456485725739037958740375743393; 44 | p = ((p * x) >> 96) - 11111509109440967052023855526967; 45 | p = ((p * x) >> 96) - 45023709667254063763336534515857; 46 | p = ((p * x) >> 96) - 14706773417378608786704636184526; 47 | p = p * x - (795164235651350426258249787498 << 96); 48 | //emit log_named_int("p", p); 49 | // We leave p in 2**192 basis so we don't need to scale it back up for the division. 50 | // q is monic by convention 51 | int256 q = x + 5573035233440673466300451813936; 52 | q = ((q * x) >> 96) + 71694874799317883764090561454958; 53 | q = ((q * x) >> 96) + 283447036172924575727196451306956; 54 | q = ((q * x) >> 96) + 401686690394027663651624208769553; 55 | q = ((q * x) >> 96) + 204048457590392012362485061816622; 56 | q = ((q * x) >> 96) + 31853899698501571402653359427138; 57 | q = ((q * x) >> 96) + 909429971244387300277376558375; 58 | assembly { 59 | // Div in assembly because solidity adds a zero check despite the `unchecked`. 60 | // The q polynomial is known not to have zeros in the domain. (All roots are complex) 61 | // No scaling required because p is already 2**96 too large. 62 | r := sdiv(p, q) 63 | } 64 | // r is in the range (0, 0.125) * 2**96 65 | 66 | // Finalization, we need to 67 | // * multiply by the scale factor s = 5.549… 68 | // * add ln(2**96 / 10**18) 69 | // * add k * ln(2) 70 | // * multiply by 10**18 / 2**96 = 5**18 >> 78 71 | // mul s * 5e18 * 2**96, base is now 5**18 * 2**192 72 | r *= 1677202110996718588342820967067443963516166; 73 | // add ln(2) * k * 5e18 * 2**192 74 | r += 16597577552685614221487285958193947469193820559219878177908093499208371 * k; 75 | // add ln(2**96 / 10**18) * 5e18 * 2**192 76 | r += 600920179829731861736702779321621459595472258049074101567377883020018308; 77 | // base conversion: mul 2**18 / 2**192 78 | r >>= 174; 79 | } 80 | } 81 | 82 | // Integer log2 83 | // @returns floor(log2(x)) if x is nonzero, otherwise 0. This is the same 84 | // as the location of the highest set bit. 85 | // Consumes 232 gas. This could have been an 3 gas EVM opcode though. 86 | function ilog2(uint256 x) internal pure returns (uint256 r) { 87 | assembly { 88 | r := shl(7, lt(0xffffffffffffffffffffffffffffffff, x)) 89 | r := or(r, shl(6, lt(0xffffffffffffffff, shr(r, x)))) 90 | r := or(r, shl(5, lt(0xffffffff, shr(r, x)))) 91 | r := or(r, shl(4, lt(0xffff, shr(r, x)))) 92 | r := or(r, shl(3, lt(0xff, shr(r, x)))) 93 | r := or(r, shl(2, lt(0xf, shr(r, x)))) 94 | r := or(r, shl(1, lt(0x3, shr(r, x)))) 95 | r := or(r, lt(0x1, shr(r, x))) 96 | } 97 | } 98 | 99 | // Computes e^x in 1e18 fixed point. 100 | function exp(int256 x) internal pure returns (uint256 r) { 101 | unchecked { 102 | // Input x is in fixed point format, with scale factor 1/1e18. 103 | 104 | // When the result is < 0.5 we return zero. This happens when 105 | // x <= floor(log(0.5e18) * 1e18) ~ -42e18 106 | if (x <= -42139678854452767551) { 107 | return 0; 108 | } 109 | 110 | // When the result is > (2**255 - 1) / 1e18 we can not represent it 111 | // as an int256. This happens when x >= floor(log((2**255 -1) / 1e18) * 1e18) ~ 135. 112 | if (x >= 135305999368893231589) revert ExpOverflow(); 113 | 114 | // x is now in the range (-42, 136) * 1e18. Convert to (-42, 136) * 2**96 115 | // for more intermediate precision and a binary basis. This base conversion 116 | // is a multiplication by 1e18 / 2**96 = 5**18 / 2**78. 117 | x = (x << 78) / 5 ** 18; 118 | 119 | // Reduce range of x to (-½ ln 2, ½ ln 2) * 2**96 by factoring out powers of two 120 | // such that exp(x) = exp(x') * 2**k, where k is an integer. 121 | // Solving this gives k = round(x / log(2)) and x' = x - k * log(2). 122 | int256 k = ((x << 96) / 54916777467707473351141471128 + 2 ** 95) >> 96; 123 | x = x - k * 54916777467707473351141471128; 124 | // k is in the range [-61, 195]. 125 | 126 | // Evaluate using a (6, 7)-term rational approximation 127 | // p is made monic, we will multiply by a scale factor later 128 | int256 p = x + 2772001395605857295435445496992; 129 | p = ((p * x) >> 96) + 44335888930127919016834873520032; 130 | p = ((p * x) >> 96) + 398888492587501845352592340339721; 131 | p = ((p * x) >> 96) + 1993839819670624470859228494792842; 132 | p = p * x + (4385272521454847904632057985693276 << 96); 133 | // We leave p in 2**192 basis so we don't need to scale it back up for the division. 134 | // Evaluate using using Knuth's scheme from p. 491. 135 | int256 z = x + 750530180792738023273180420736; 136 | z = ((z * x) >> 96) + 32788456221302202726307501949080; 137 | int256 w = x - 2218138959503481824038194425854; 138 | w = ((w * z) >> 96) + 892943633302991980437332862907700; 139 | int256 q = z + w - 78174809823045304726920794422040; 140 | q = ((q * w) >> 96) + 4203224763890128580604056984195872; 141 | assembly { 142 | // Div in assembly because solidity adds a zero check despite the `unchecked`. 143 | // The q polynomial is known not to have zeros in the domain. (All roots are complex) 144 | // No scaling required because p is already 2**96 too large. 145 | r := sdiv(p, q) 146 | } 147 | // r should be in the range (0.09, 0.25) * 2**96. 148 | 149 | // We now need to multiply r by 150 | // * the scale factor s = ~6.031367120..., 151 | // * the 2**k factor from the range reduction, and 152 | // * the 1e18 / 2**96 factor for base converison. 153 | // We do all of this at once, with an intermediate result in 2**213 basis 154 | // so the final right shift is always by a positive amount. 155 | r = (uint256(r) * 3822833074963236453042738258902158003155416615667) >> uint256(195 - k); 156 | } 157 | } 158 | 159 | error Overflow(); 160 | error ExpOverflow(); 161 | error LnNegativeUndefined(); 162 | } 163 | -------------------------------------------------------------------------------- /src/lib/lyra/Math.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: ISC 2 | pragma solidity 0.8.10; 3 | 4 | /** 5 | * @title Math 6 | * @author Lyra 7 | * @dev Library to unify logic for common shared functions 8 | */ 9 | library Math { 10 | /// @dev Return the minimum value between the two inputs 11 | function min(uint256 x, uint256 y) internal pure returns (uint256) { 12 | return (x < y) ? x : y; 13 | } 14 | 15 | /// @dev Return the maximum value between the two inputs 16 | function max(uint256 x, uint256 y) internal pure returns (uint256) { 17 | return (x > y) ? x : y; 18 | } 19 | 20 | /// @dev Compute the absolute value of `val`. 21 | function abs(int256 val) internal pure returns (uint256) { 22 | return uint256(val < 0 ? -val : val); 23 | } 24 | 25 | /// @dev Takes ceiling of a to m precision 26 | /// @param m represents 1eX where X is the number of trailing 0's 27 | function ceil(uint256 a, uint256 m) internal pure returns (uint256) { 28 | return ((a + m - 1) / m) * m; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/lib/lyra/README.md: -------------------------------------------------------------------------------- 1 | ## LYRA 2 | The files in this directories are libraries from the NEWPORT release of Lyra's smart contract suite. 3 | 4 | The commit sha for the contracts is `604d383be4af85c614eded78688dc3ee8c9370c7` 5 | 6 | They are *out of scope* of the audit -------------------------------------------------------------------------------- /src/lib/synthetix/DecimalMath.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | // 3 | //Copyright (c) 2019 Synthetix 4 | // 5 | //Permission is hereby granted, free of charge, to any person obtaining a copy 6 | //of this software and associated documentation files (the "Software"), to deal 7 | //in the Software without restriction, including without limitation the rights 8 | //to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | //copies of the Software, and to permit persons to whom the Software is 10 | //furnished to do so, subject to the following conditions: 11 | // 12 | //The above copyright notice and this permission notice shall be included in all 13 | //copies or substantial portions of the Software. 14 | // 15 | //THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | //IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | //FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | //AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | //LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | //OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | //SOFTWARE. 22 | 23 | pragma solidity 0.8.10; 24 | 25 | /** 26 | * @title DecimalMath 27 | * @author Lyra 28 | * @dev Modified synthetix SafeDecimalMath to include internal arithmetic underflow/overflow. 29 | * @dev https://docs.synthetix.io/contracts/source/libraries/SafeDecimalMath/ 30 | */ 31 | 32 | library DecimalMath { 33 | /* Number of decimal places in the representations. */ 34 | uint8 public constant decimals = 18; 35 | uint8 public constant highPrecisionDecimals = 27; 36 | 37 | /* The number representing 1.0. */ 38 | uint256 public constant UNIT = 10 ** uint256(decimals); 39 | 40 | /* The number representing 1.0 for higher fidelity numbers. */ 41 | uint256 public constant PRECISE_UNIT = 10 ** uint256(highPrecisionDecimals); 42 | uint256 private constant UNIT_TO_HIGH_PRECISION_CONVERSION_FACTOR = 10 ** uint256(highPrecisionDecimals - decimals); 43 | 44 | /** 45 | * @return Provides an interface to UNIT. 46 | */ 47 | function unit() external pure returns (uint256) { 48 | return UNIT; 49 | } 50 | 51 | /** 52 | * @return Provides an interface to PRECISE_UNIT. 53 | */ 54 | function preciseUnit() external pure returns (uint256) { 55 | return PRECISE_UNIT; 56 | } 57 | 58 | /** 59 | * @return The result of multiplying x and y, interpreting the operands as fixed-point 60 | * decimals. 61 | * 62 | * @dev A unit factor is divided out after the product of x and y is evaluated, 63 | * so that product must be less than 2**256. As this is an integer division, 64 | * the internal division always rounds down. This helps save on gas. Rounding 65 | * is more expensive on gas. 66 | */ 67 | function multiplyDecimal(uint256 x, uint256 y) internal pure returns (uint256) { 68 | /* Divide by UNIT to remove the extra factor introduced by the product. */ 69 | return (x * y) / UNIT; 70 | } 71 | 72 | /** 73 | * @return The result of safely multiplying x and y, interpreting the operands 74 | * as fixed-point decimals of the specified precision unit. 75 | * 76 | * @dev The operands should be in the form of a the specified unit factor which will be 77 | * divided out after the product of x and y is evaluated, so that product must be 78 | * less than 2**256. 79 | * 80 | * Unlike multiplyDecimal, this function rounds the result to the nearest increment. 81 | * Rounding is useful when you need to retain fidelity for small decimal numbers 82 | * (eg. small fractions or percentages). 83 | */ 84 | function _multiplyDecimalRound(uint256 x, uint256 y, uint256 precisionUnit) private pure returns (uint256) { 85 | /* Divide by UNIT to remove the extra factor introduced by the product. */ 86 | uint256 quotientTimesTen = (x * y) / (precisionUnit / 10); 87 | 88 | if (quotientTimesTen % 10 >= 5) { 89 | quotientTimesTen += 10; 90 | } 91 | 92 | return quotientTimesTen / 10; 93 | } 94 | 95 | /** 96 | * @return The result of safely multiplying x and y, interpreting the operands 97 | * as fixed-point decimals of a precise unit. 98 | * 99 | * @dev The operands should be in the precise unit factor which will be 100 | * divided out after the product of x and y is evaluated, so that product must be 101 | * less than 2**256. 102 | * 103 | * Unlike multiplyDecimal, this function rounds the result to the nearest increment. 104 | * Rounding is useful when you need to retain fidelity for small decimal numbers 105 | * (eg. small fractions or percentages). 106 | */ 107 | function multiplyDecimalRoundPrecise(uint256 x, uint256 y) internal pure returns (uint256) { 108 | return _multiplyDecimalRound(x, y, PRECISE_UNIT); 109 | } 110 | 111 | /** 112 | * @return The result of safely multiplying x and y, interpreting the operands 113 | * as fixed-point decimals of a standard unit. 114 | * 115 | * @dev The operands should be in the standard unit factor which will be 116 | * divided out after the product of x and y is evaluated, so that product must be 117 | * less than 2**256. 118 | * 119 | * Unlike multiplyDecimal, this function rounds the result to the nearest increment. 120 | * Rounding is useful when you need to retain fidelity for small decimal numbers 121 | * (eg. small fractions or percentages). 122 | */ 123 | function multiplyDecimalRound(uint256 x, uint256 y) internal pure returns (uint256) { 124 | return _multiplyDecimalRound(x, y, UNIT); 125 | } 126 | 127 | /** 128 | * @return The result of safely dividing x and y. The return value is a high 129 | * precision decimal. 130 | * 131 | * @dev y is divided after the product of x and the standard precision unit 132 | * is evaluated, so the product of x and UNIT must be less than 2**256. As 133 | * this is an integer division, the result is always rounded down. 134 | * This helps save on gas. Rounding is more expensive on gas. 135 | */ 136 | function divideDecimal(uint256 x, uint256 y) internal pure returns (uint256) { 137 | /* Reintroduce the UNIT factor that will be divided out by y. */ 138 | return (x * UNIT) / y; 139 | } 140 | 141 | /** 142 | * @return The result of safely dividing x and y. The return value is as a rounded 143 | * decimal in the precision unit specified in the parameter. 144 | * 145 | * @dev y is divided after the product of x and the specified precision unit 146 | * is evaluated, so the product of x and the specified precision unit must 147 | * be less than 2**256. The result is rounded to the nearest increment. 148 | */ 149 | function _divideDecimalRound(uint256 x, uint256 y, uint256 precisionUnit) private pure returns (uint256) { 150 | uint256 resultTimesTen = (x * (precisionUnit * 10)) / y; 151 | 152 | if (resultTimesTen % 10 >= 5) { 153 | resultTimesTen += 10; 154 | } 155 | 156 | return resultTimesTen / 10; 157 | } 158 | 159 | /** 160 | * @return The result of safely dividing x and y. The return value is as a rounded 161 | * standard precision decimal. 162 | * 163 | * @dev y is divided after the product of x and the standard precision unit 164 | * is evaluated, so the product of x and the standard precision unit must 165 | * be less than 2**256. The result is rounded to the nearest increment. 166 | */ 167 | function divideDecimalRound(uint256 x, uint256 y) internal pure returns (uint256) { 168 | return _divideDecimalRound(x, y, UNIT); 169 | } 170 | 171 | /** 172 | * @return The result of safely dividing x and y. The return value is as a rounded 173 | * high precision decimal. 174 | * 175 | * @dev y is divided after the product of x and the high precision unit 176 | * is evaluated, so the product of x and the high precision unit must 177 | * be less than 2**256. The result is rounded to the nearest increment. 178 | */ 179 | function divideDecimalRoundPrecise(uint256 x, uint256 y) internal pure returns (uint256) { 180 | return _divideDecimalRound(x, y, PRECISE_UNIT); 181 | } 182 | 183 | /** 184 | * @dev Convert a standard decimal representation to a high precision one. 185 | */ 186 | function decimalToPreciseDecimal(uint256 i) internal pure returns (uint256) { 187 | return i * UNIT_TO_HIGH_PRECISION_CONVERSION_FACTOR; 188 | } 189 | 190 | /** 191 | * @dev Convert a high precision decimal to a standard decimal representation. 192 | */ 193 | function preciseDecimalToDecimal(uint256 i) internal pure returns (uint256) { 194 | uint256 quotientTimesTen = i / (UNIT_TO_HIGH_PRECISION_CONVERSION_FACTOR / 10); 195 | 196 | if (quotientTimesTen % 10 >= 5) { 197 | quotientTimesTen += 10; 198 | } 199 | 200 | return quotientTimesTen / 10; 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /src/lib/synthetix/SignedDecimalMath.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | // 3 | //Copyright (c) 2019 Synthetix 4 | // 5 | //Permission is hereby granted, free of charge, to any person obtaining a copy 6 | //of this software and associated documentation files (the "Software"), to deal 7 | //in the Software without restriction, including without limitation the rights 8 | //to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | //copies of the Software, and to permit persons to whom the Software is 10 | //furnished to do so, subject to the following conditions: 11 | // 12 | //The above copyright notice and this permission notice shall be included in all 13 | //copies or substantial portions of the Software. 14 | // 15 | //THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | //IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | //FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | //AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | //LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | //OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | //SOFTWARE. 22 | 23 | pragma solidity 0.8.10; 24 | 25 | /** 26 | * @title SignedDecimalMath 27 | * @author Lyra 28 | * @dev Modified synthetix SafeSignedDecimalMath to include internal arithmetic underflow/overflow. 29 | * @dev https://docs.synthetix.io/contracts/source/libraries/safedecimalmath 30 | */ 31 | library SignedDecimalMath { 32 | /* Number of decimal places in the representations. */ 33 | uint8 public constant decimals = 18; 34 | uint8 public constant highPrecisionDecimals = 27; 35 | 36 | /* The number representing 1.0. */ 37 | int256 public constant UNIT = int256(10 ** uint256(decimals)); 38 | 39 | /* The number representing 1.0 for higher fidelity numbers. */ 40 | int256 public constant PRECISE_UNIT = int256(10 ** uint256(highPrecisionDecimals)); 41 | int256 private constant UNIT_TO_HIGH_PRECISION_CONVERSION_FACTOR = 42 | int256(10 ** uint256(highPrecisionDecimals - decimals)); 43 | 44 | /** 45 | * @return Provides an interface to UNIT. 46 | */ 47 | function unit() external pure returns (int256) { 48 | return UNIT; 49 | } 50 | 51 | /** 52 | * @return Provides an interface to PRECISE_UNIT. 53 | */ 54 | function preciseUnit() external pure returns (int256) { 55 | return PRECISE_UNIT; 56 | } 57 | 58 | /** 59 | * @dev Rounds an input with an extra zero of precision, returning the result without the extra zero. 60 | * Half increments round away from zero; positive numbers at a half increment are rounded up, 61 | * while negative such numbers are rounded down. This behaviour is designed to be consistent with the 62 | * unsigned version of this library (SafeDecimalMath). 63 | */ 64 | function _roundDividingByTen(int256 valueTimesTen) private pure returns (int256) { 65 | int256 increment; 66 | if (valueTimesTen % 10 >= 5) { 67 | increment = 10; 68 | } else if (valueTimesTen % 10 <= -5) { 69 | increment = -10; 70 | } 71 | return (valueTimesTen + increment) / 10; 72 | } 73 | 74 | /** 75 | * @return The result of multiplying x and y, interpreting the operands as fixed-point 76 | * decimals. 77 | * 78 | * @dev A unit factor is divided out after the product of x and y is evaluated, 79 | * so that product must be less than 2**256. As this is an integer division, 80 | * the internal division always rounds down. This helps save on gas. Rounding 81 | * is more expensive on gas. 82 | */ 83 | function multiplyDecimal(int256 x, int256 y) internal pure returns (int256) { 84 | /* Divide by UNIT to remove the extra factor introduced by the product. */ 85 | return (x * y) / UNIT; 86 | } 87 | 88 | /** 89 | * @return The result of safely multiplying x and y, interpreting the operands 90 | * as fixed-point decimals of the specified precision unit. 91 | * 92 | * @dev The operands should be in the form of a the specified unit factor which will be 93 | * divided out after the product of x and y is evaluated, so that product must be 94 | * less than 2**256. 95 | * 96 | * Unlike multiplyDecimal, this function rounds the result to the nearest increment. 97 | * Rounding is useful when you need to retain fidelity for small decimal numbers 98 | * (eg. small fractions or percentages). 99 | */ 100 | function _multiplyDecimalRound(int256 x, int256 y, int256 precisionUnit) private pure returns (int256) { 101 | /* Divide by UNIT to remove the extra factor introduced by the product. */ 102 | int256 quotientTimesTen = (x * y) / (precisionUnit / 10); 103 | return _roundDividingByTen(quotientTimesTen); 104 | } 105 | 106 | /** 107 | * @return The result of safely multiplying x and y, interpreting the operands 108 | * as fixed-point decimals of a precise unit. 109 | * 110 | * @dev The operands should be in the precise unit factor which will be 111 | * divided out after the product of x and y is evaluated, so that product must be 112 | * less than 2**256. 113 | * 114 | * Unlike multiplyDecimal, this function rounds the result to the nearest increment. 115 | * Rounding is useful when you need to retain fidelity for small decimal numbers 116 | * (eg. small fractions or percentages). 117 | */ 118 | function multiplyDecimalRoundPrecise(int256 x, int256 y) internal pure returns (int256) { 119 | return _multiplyDecimalRound(x, y, PRECISE_UNIT); 120 | } 121 | 122 | /** 123 | * @return The result of safely multiplying x and y, interpreting the operands 124 | * as fixed-point decimals of a standard unit. 125 | * 126 | * @dev The operands should be in the standard unit factor which will be 127 | * divided out after the product of x and y is evaluated, so that product must be 128 | * less than 2**256. 129 | * 130 | * Unlike multiplyDecimal, this function rounds the result to the nearest increment. 131 | * Rounding is useful when you need to retain fidelity for small decimal numbers 132 | * (eg. small fractions or percentages). 133 | */ 134 | function multiplyDecimalRound(int256 x, int256 y) internal pure returns (int256) { 135 | return _multiplyDecimalRound(x, y, UNIT); 136 | } 137 | 138 | /** 139 | * @return The result of safely dividing x and y. The return value is a high 140 | * precision decimal. 141 | * 142 | * @dev y is divided after the product of x and the standard precision unit 143 | * is evaluated, so the product of x and UNIT must be less than 2**256. As 144 | * this is an integer division, the result is always rounded down. 145 | * This helps save on gas. Rounding is more expensive on gas. 146 | */ 147 | function divideDecimal(int256 x, int256 y) internal pure returns (int256) { 148 | /* Reintroduce the UNIT factor that will be divided out by y. */ 149 | return (x * UNIT) / y; 150 | } 151 | 152 | /** 153 | * @return The result of safely dividing x and y. The return value is as a rounded 154 | * decimal in the precision unit specified in the parameter. 155 | * 156 | * @dev y is divided after the product of x and the specified precision unit 157 | * is evaluated, so the product of x and the specified precision unit must 158 | * be less than 2**256. The result is rounded to the nearest increment. 159 | */ 160 | function _divideDecimalRound(int256 x, int256 y, int256 precisionUnit) private pure returns (int256) { 161 | int256 resultTimesTen = (x * (precisionUnit * 10)) / y; 162 | return _roundDividingByTen(resultTimesTen); 163 | } 164 | 165 | /** 166 | * @return The result of safely dividing x and y. The return value is as a rounded 167 | * standard precision decimal. 168 | * 169 | * @dev y is divided after the product of x and the standard precision unit 170 | * is evaluated, so the product of x and the standard precision unit must 171 | * be less than 2**256. The result is rounded to the nearest increment. 172 | */ 173 | function divideDecimalRound(int256 x, int256 y) internal pure returns (int256) { 174 | return _divideDecimalRound(x, y, UNIT); 175 | } 176 | 177 | /** 178 | * @return The result of safely dividing x and y. The return value is as a rounded 179 | * high precision decimal. 180 | * 181 | * @dev y is divided after the product of x and the high precision unit 182 | * is evaluated, so the product of x and the high precision unit must 183 | * be less than 2**256. The result is rounded to the nearest increment. 184 | */ 185 | function divideDecimalRoundPrecise(int256 x, int256 y) internal pure returns (int256) { 186 | return _divideDecimalRound(x, y, PRECISE_UNIT); 187 | } 188 | 189 | /** 190 | * @dev Convert a standard decimal representation to a high precision one. 191 | */ 192 | function decimalToPreciseDecimal(int256 i) internal pure returns (int256) { 193 | return i * UNIT_TO_HIGH_PRECISION_CONVERSION_FACTOR; 194 | } 195 | 196 | /** 197 | * @dev Convert a high precision decimal to a standard decimal representation. 198 | */ 199 | function preciseDecimalToDecimal(int256 i) internal pure returns (int256) { 200 | int256 quotientTimesTen = i / (UNIT_TO_HIGH_PRECISION_CONVERSION_FACTOR / 10); 201 | return _roundDividingByTen(quotientTimesTen); 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /src/mixin/EIP712.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // 3 | // █████████████▌ ▐█████████████ 4 | // █████████████▌ ▐█████████████ 5 | // █████████████▌ ▐█████████████ 6 | // █████████████▌ ▐█████████████ 7 | // █████████████▌ ▐█████████████ 8 | // █████████████▌ ▐█████████████ 9 | // █████████████▌ ▐█████████████ 10 | // █████████████▌ ▐█████████████ 11 | // ██████████████ ██████████████ 12 | // ██████████████ ▄▄████████████████▄▄ ▐█████████████▌ 13 | // ██████████████ ▄█████████████████████████████▄ ██████████████ 14 | // ██████████▀ ▄█████████████████████████████████ ██████████████▌ 15 | // ██████▀ ▄██████████████████████████████████▀ ▄███████████████ 16 | // ███▀ ██████████████████████████████████▀ ▄████████████████ 17 | // ▀▀ ████████████████████████████████▀▀ ▄█████████████████▌ 18 | // █████████████████████▀▀▀▀▀▀▀ ▄▄███████████████████▀ 19 | // ██████████████████▀ ▄▄▄█████████████████████████████▀ 20 | // ████████████████▀ ▄█████████████████████████████████▀ ██▄ 21 | // ▐███████████████▀ ▄██████████████████████████████████▀ █████▄ 22 | // ██████████████▀ ▄█████████████████████████████████▀ ▄████████ 23 | // ██████████████▀ ███████████████████████████████▀ ▄████████████ 24 | // ▐█████████████▌ ▀▀▀▀████████████████████▀▀▀▀ █████████████▌ 25 | // ██████████████ ██████████████ 26 | // █████████████▌ ██████████████ 27 | // █████████████▌ ██████████████ 28 | // █████████████▌ ██████████████ 29 | // █████████████▌ ██████████████ 30 | // █████████████▌ ██████████████ 31 | // █████████████▌ ██████████████ 32 | // █████████████▌ ██████████████ 33 | // █████████████▌ ██████████████ 34 | 35 | pragma solidity ^0.8.10; 36 | 37 | /// @dev EIP712 helpers for features. 38 | abstract contract EIP712 { 39 | /// @dev The domain hash separator for the entire call option protocol 40 | bytes32 public EIP712_DOMAIN_SEPARATOR; 41 | 42 | function setAddressForEipDomain(address hookAddress) internal { 43 | // Compute `EIP712_DOMAIN_SEPARATOR` 44 | { 45 | uint256 chainId; 46 | assembly { 47 | chainId := chainid() 48 | } 49 | EIP712_DOMAIN_SEPARATOR = keccak256( 50 | abi.encode( 51 | keccak256( 52 | "EIP712Domain(" "string name," "string version," "uint256 chainId," "address verifyingContract" 53 | ")" 54 | ), 55 | keccak256("Hook"), 56 | keccak256("1.0.0"), 57 | chainId, 58 | hookAddress 59 | ) 60 | ); 61 | } 62 | } 63 | 64 | function _getEIP712Hash(bytes32 structHash) internal view returns (bytes32) { 65 | return keccak256(abi.encodePacked(hex"1901", EIP712_DOMAIN_SEPARATOR, structHash)); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/mixin/HookInstrumentERC721.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // 3 | // █████████████▌ ▐█████████████ 4 | // █████████████▌ ▐█████████████ 5 | // █████████████▌ ▐█████████████ 6 | // █████████████▌ ▐█████████████ 7 | // █████████████▌ ▐█████████████ 8 | // █████████████▌ ▐█████████████ 9 | // █████████████▌ ▐█████████████ 10 | // █████████████▌ ▐█████████████ 11 | // ██████████████ ██████████████ 12 | // ██████████████ ▄▄████████████████▄▄ ▐█████████████▌ 13 | // ██████████████ ▄█████████████████████████████▄ ██████████████ 14 | // ██████████▀ ▄█████████████████████████████████ ██████████████▌ 15 | // ██████▀ ▄██████████████████████████████████▀ ▄███████████████ 16 | // ███▀ ██████████████████████████████████▀ ▄████████████████ 17 | // ▀▀ ████████████████████████████████▀▀ ▄█████████████████▌ 18 | // █████████████████████▀▀▀▀▀▀▀ ▄▄███████████████████▀ 19 | // ██████████████████▀ ▄▄▄█████████████████████████████▀ 20 | // ████████████████▀ ▄█████████████████████████████████▀ ██▄ 21 | // ▐███████████████▀ ▄██████████████████████████████████▀ █████▄ 22 | // ██████████████▀ ▄█████████████████████████████████▀ ▄████████ 23 | // ██████████████▀ ███████████████████████████████▀ ▄████████████ 24 | // ▐█████████████▌ ▀▀▀▀████████████████████▀▀▀▀ █████████████▌ 25 | // ██████████████ ██████████████ 26 | // █████████████▌ ██████████████ 27 | // █████████████▌ ██████████████ 28 | // █████████████▌ ██████████████ 29 | // █████████████▌ ██████████████ 30 | // █████████████▌ ██████████████ 31 | // █████████████▌ ██████████████ 32 | // █████████████▌ ██████████████ 33 | // █████████████▌ ██████████████ 34 | 35 | pragma solidity ^0.8.10; 36 | 37 | import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Burnable.sol"; 38 | import "@openzeppelin/contracts/utils/Base64.sol"; 39 | import "@openzeppelin/contracts/utils/Counters.sol"; 40 | import "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; 41 | 42 | import "../interfaces/IHookERC721Vault.sol"; 43 | 44 | import "../lib/HookStrings.sol"; 45 | import "../lib/TokenURI.sol"; 46 | 47 | /// @dev This contract implements some ERC721 / for hook instruments. 48 | abstract contract HookInstrumentERC721 is ERC721Burnable { 49 | using Counters for Counters.Counter; 50 | 51 | mapping(uint256 => Counters.Counter) private _transfers; 52 | bytes4 private constant ERC_721 = bytes4(keccak256("ERC721")); 53 | 54 | /// @dev the contact address for a marketplace to pre-approve 55 | address public _preApprovedMarketplace = address(0); 56 | 57 | /// @dev hook called after the ERC721 is transferred, 58 | /// which allows us to increment the counters. 59 | function _afterTokenTransfer( 60 | address, // from 61 | address, // to 62 | uint256 tokenId 63 | ) internal override { 64 | // increment the counter for the token 65 | _transfers[tokenId].increment(); 66 | } 67 | 68 | /// 69 | /// @dev See {IERC721-isApprovedForAll}. 70 | /// this extension ensures that any operator contract located 71 | /// at {_approvedMarketpace} is considered approved internally 72 | /// in the ERC721 contract 73 | /// 74 | function isApprovedForAll(address owner, address operator) public view virtual override returns (bool) { 75 | return operator == _preApprovedMarketplace || super.isApprovedForAll(owner, operator); 76 | } 77 | 78 | constructor(string memory instrumentType) ERC721(makeInstrumentName(instrumentType), "INST") {} 79 | 80 | function makeInstrumentName(string memory z) internal pure returns (string memory) { 81 | return string(abi.encodePacked("Hook ", z, " instrument")); 82 | } 83 | 84 | /// @notice the number of times the token has been transferred 85 | /// @dev this count can be used by overbooks to invalidate orders after a 86 | /// token has been transferred, preventing stale order execution by 87 | /// malicious parties 88 | function getTransferCount(uint256 optionId) external view returns (uint256) { 89 | return _transfers[optionId].current(); 90 | } 91 | 92 | /// @notice getter for the address holding the underlying asset 93 | function getVaultAddress(uint256 optionId) public view virtual returns (address); 94 | 95 | /// @notice getter for the assetId of the underlying asset within a vault 96 | function getAssetId(uint256 optionId) public view virtual returns (uint32); 97 | 98 | /// @notice getter for the option strike price 99 | function getStrikePrice(uint256 optionId) external view virtual returns (uint256); 100 | 101 | /// @notice getter for the options expiration. After this time the 102 | /// option is invalid 103 | function getExpiration(uint256 optionId) external view virtual returns (uint256); 104 | 105 | /// @dev this is the OpenSea compatible collection - level metadata URI. 106 | function contractUri(uint256 optionId) external view returns (string memory) { 107 | return string( 108 | abi.encodePacked( 109 | "token.hook.xyz/option-contract/", 110 | HookStrings.toAsciiString(address(this)), 111 | "/", 112 | HookStrings.toString(optionId) 113 | ) 114 | ); 115 | } 116 | 117 | /// 118 | /// @dev See {IERC721-tokenURI}. 119 | /// 120 | function tokenURI(uint256 tokenId) public view override returns (string memory) { 121 | bytes4 class = _underlyingClass(tokenId); 122 | if (class == ERC_721) { 123 | IHookERC721Vault vault = IHookERC721Vault(getVaultAddress(tokenId)); 124 | uint32 assetId = getAssetId(tokenId); 125 | address underlyingAddress = vault.assetAddress(assetId); 126 | uint256 underlyingTokenId = vault.assetTokenId(assetId); 127 | // currently nothing in the contract depends on the actual underlying metadata uri 128 | // IERC721 underlyingContract = IERC721(underlyingAddress); 129 | uint256 instrumentStrikePrice = this.getStrikePrice(tokenId); 130 | uint256 instrumentExpiration = this.getExpiration(tokenId); 131 | uint256 transfers = _transfers[tokenId].current(); 132 | return TokenURI.tokenURIERC721( 133 | tokenId, underlyingAddress, underlyingTokenId, instrumentExpiration, instrumentStrikePrice, transfers 134 | ); 135 | } 136 | return "Invalid underlying asset"; 137 | } 138 | 139 | /// @dev returns an internal identifier for the underlying type contained within 140 | /// the vault to determine what the instrument is on 141 | /// 142 | /// this class evaluation relies on the interfaceId of the underlying asset 143 | /// 144 | function _underlyingClass(uint256 optionId) internal view returns (bytes4) { 145 | if (ERC165Checker.supportsInterface(getVaultAddress(optionId), type(IHookERC721Vault).interfaceId)) { 146 | return ERC_721; 147 | } else { 148 | revert("_underlying-class: Unsupported underlying type"); 149 | } 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/mixin/PermissionConstants.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // 3 | // █████████████▌ ▐█████████████ 4 | // █████████████▌ ▐█████████████ 5 | // █████████████▌ ▐█████████████ 6 | // █████████████▌ ▐█████████████ 7 | // █████████████▌ ▐█████████████ 8 | // █████████████▌ ▐█████████████ 9 | // █████████████▌ ▐█████████████ 10 | // █████████████▌ ▐█████████████ 11 | // ██████████████ ██████████████ 12 | // ██████████████ ▄▄████████████████▄▄ ▐█████████████▌ 13 | // ██████████████ ▄█████████████████████████████▄ ██████████████ 14 | // ██████████▀ ▄█████████████████████████████████ ██████████████▌ 15 | // ██████▀ ▄██████████████████████████████████▀ ▄███████████████ 16 | // ███▀ ██████████████████████████████████▀ ▄████████████████ 17 | // ▀▀ ████████████████████████████████▀▀ ▄█████████████████▌ 18 | // █████████████████████▀▀▀▀▀▀▀ ▄▄███████████████████▀ 19 | // ██████████████████▀ ▄▄▄█████████████████████████████▀ 20 | // ████████████████▀ ▄█████████████████████████████████▀ ██▄ 21 | // ▐███████████████▀ ▄██████████████████████████████████▀ █████▄ 22 | // ██████████████▀ ▄█████████████████████████████████▀ ▄████████ 23 | // ██████████████▀ ███████████████████████████████▀ ▄████████████ 24 | // ▐█████████████▌ ▀▀▀▀████████████████████▀▀▀▀ █████████████▌ 25 | // ██████████████ ██████████████ 26 | // █████████████▌ ██████████████ 27 | // █████████████▌ ██████████████ 28 | // █████████████▌ ██████████████ 29 | // █████████████▌ ██████████████ 30 | // █████████████▌ ██████████████ 31 | // █████████████▌ ██████████████ 32 | // █████████████▌ ██████████████ 33 | // █████████████▌ ██████████████ 34 | 35 | pragma solidity ^0.8.10; 36 | 37 | /// @notice roles on the hook protocol that can be read by other contract 38 | /// @dev new roles here should be initialized in the constructor of the protocol 39 | abstract contract PermissionConstants { 40 | /// ----- ROLES -------- 41 | 42 | /// @notice the allowlister is able to enable and disable projects to mint instruments 43 | bytes32 public constant ALLOWLISTER_ROLE = keccak256("ALLOWLISTER_ROLE"); 44 | 45 | /// @notice the pauser is able to start and pause various components of the protocol 46 | bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE"); 47 | 48 | /// @notice the vault upgrader role is able to upgrade the implementation for all vaults 49 | bytes32 public constant VAULT_UPGRADER = keccak256("VAULT_UPGRADER"); 50 | 51 | /// @notice the call upgrader role is able to upgrade the implementation of the covered call options 52 | bytes32 public constant CALL_UPGRADER = keccak256("CALL_UPGRADER"); 53 | 54 | /// @notice the market configuration role allows the actor to make changes to how the market operates 55 | bytes32 public constant MARKET_CONF = keccak256("MARKET_CONF"); 56 | 57 | /// @notice the collection configuration role allows the actor to make changes the collection 58 | /// configs on the protocol contract 59 | bytes32 public constant COLLECTION_CONF = keccak256("COLLECTION_CONF"); 60 | } 61 | -------------------------------------------------------------------------------- /src/test/HookCoveredCallBiddingRevertTests.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.10; 3 | 4 | import "./utils/base.t.sol"; 5 | import "./utils/mocks/MaliciousBidder.sol"; 6 | 7 | /// @dev these tests try cases where a bidder maliciously reverts on save. 8 | /// @author Jake Nyquist-j@hook.xyz 9 | contract HookCoveredCallBiddingRevertTests is HookProtocolTest { 10 | function setUp() public { 11 | setUpAddresses(); 12 | setUpFullProtocol(); 13 | 14 | // add address to the allowlist for minting 15 | vm.prank(address(admin)); 16 | vaultFactory.makeMultiVault(address(token)); 17 | 18 | // Set user balances 19 | vm.deal(address(buyer), 100 ether); 20 | 21 | // Mint underlying token 22 | underlyingTokenId = 0; 23 | token.mint(address(writer), underlyingTokenId); 24 | 25 | // Buyer swap 50 ETH <> 50 WETH 26 | vm.prank(address(buyer)); 27 | weth.deposit{value: 50 ether}(); 28 | 29 | // Seller approve ERC721TransferHelper 30 | vm.prank(address(writer)); 31 | token.setApprovalForAll(address(calls), true); 32 | 33 | // Buyer approve covered call 34 | vm.prank(address(buyer)); 35 | weth.approve(address(calls), 50 ether); 36 | } 37 | 38 | function test_SuccessfulAuctionAndSettlement() public { 39 | // create the call option 40 | vm.startPrank(address(writer)); 41 | uint256 writerStartBalance = writer.balance; 42 | uint256 baseTime = block.timestamp; 43 | uint32 expiration = uint32(baseTime) + 3 days; 44 | uint256 optionId = calls.mintWithErc721(address(token), underlyingTokenId, 1000, expiration); 45 | 46 | // assume that the writer somehow sold to the buyer, outside the scope of this test 47 | calls.safeTransferFrom(writer, buyer, optionId); 48 | uint256 buyerStartBalance = buyer.balance; 49 | 50 | vm.stopPrank(); 51 | // create some bidders 52 | MaliciousBidder bidder1 = new MaliciousBidder(address(calls)); 53 | address mbcaller = address(6969420); 54 | address bidder2 = address(33456463); 55 | 56 | // made a bid 57 | vm.warp(baseTime + 2.1 days); 58 | vm.deal(mbcaller, 1100); 59 | vm.prank(mbcaller); 60 | bidder1.bid{value: 1050}(optionId); 61 | 62 | // validate that bid is updated 63 | assertTrue(calls.currentBid(optionId) == 1050, "contract should update the current high bid for the option"); 64 | assertTrue(calls.currentBidder(optionId) == address(bidder1), "bidder1 should be in the lead"); 65 | assertTrue(address(calls).balance == 1050, "bidder1 should have deposited money into escrow"); 66 | 67 | // make a competing bid 68 | vm.deal(bidder2, 1100); 69 | vm.prank(bidder2); 70 | calls.bid{value: 1100}(optionId); 71 | 72 | // validate that bid is updated 73 | assertTrue(calls.currentBid(optionId) == 1100, "contract should update the current high bid for the option"); 74 | assertTrue(calls.currentBidder(optionId) == bidder2, "bidder2 should be in the lead"); 75 | assertTrue(bidder2.balance == 0, "bidder2 should have funds in escrow"); 76 | 77 | // settle the auction 78 | // assertTrue(token.ownerOf(underlyingTokenId) == address(calls), "call contract should own the token"); 79 | vm.warp(expiration + 3 seconds); 80 | vm.prank(buyer); 81 | calls.settleOption(optionId); 82 | 83 | // verify the balances are correct 84 | uint256 writerEndBalance = writer.balance; 85 | uint256 buyerEndBalance = buyer.balance; 86 | 87 | assertTrue(writerEndBalance - writerStartBalance == 1000, "the writer gets the strike price"); 88 | assertTrue(buyerEndBalance - buyerStartBalance == 100, "the call owner gets the spread"); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/test/pnm/HookCoveredCallBidTests.t.sol.skip: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.10; 3 | 4 | import "./base.t.sol"; 5 | 6 | contract HookCoveredCallBidTests is HookProtocolTest { 7 | address bidder; 8 | 9 | function setUp() public { 10 | setUpAddresses(); 11 | setUpFullProtocol(); 12 | 13 | // Set buyer balances and give weth 14 | vm.deal(address(buyer), 100 ether); 15 | vm.prank(address(buyer)); 16 | weth.deposit{value: 50 ether}(); 17 | 18 | // Mint underlying token for writer 19 | underlyingTokenId = 0; 20 | token.mint(address(writer), underlyingTokenId); 21 | 22 | setUpMintOption(); 23 | 24 | address operator = address(10); 25 | vm.label(operator, "additional token operator"); 26 | 27 | vm.startPrank(address(writer)); 28 | uint256 underlyingTokenId2 = 1; 29 | token.mint(address(writer), underlyingTokenId2); 30 | 31 | // Writer approve operator and covered call 32 | token.setApprovalForAll(operator, true); 33 | token.setApprovalForAll(address(calls), true); 34 | vm.stopPrank(); 35 | 36 | startHoax(operator); 37 | uint32 expiration = uint32(block.timestamp) + 3 days; 38 | 39 | calls.mintWithErc721(address(token), underlyingTokenId2, 1000, expiration); 40 | 41 | vm.warp(block.timestamp + 2.1 days); 42 | // stopHoax(operator); 43 | 44 | vm.stopPrank(); 45 | bidder = getAgent(); 46 | hoax(bidder, 1 ether); 47 | calls.bid{value: 1 ether}(optionTokenId); 48 | } 49 | 50 | // verify that the original bidder gets their money returned regardless of what happens (once outbid) 51 | function invariantBidderTest() public virtual { 52 | address outbidder = address(3); 53 | // start with the current bid in case some other outbidding happened in between 54 | uint256 bid = calls.currentBid(optionTokenId); 55 | hoax(outbidder, bid + 1 ether); 56 | calls.bid{value: bid + 1 ether}(optionTokenId); 57 | 58 | require( 59 | calls.currentBid(optionTokenId) == bid + 1 ether, 60 | "bid should be increased by 1 eth" 61 | ); 62 | require( 63 | calls.currentBidder(optionTokenId) == outbidder, 64 | "outbidder should be winning" 65 | ); 66 | require( 67 | address(bidder).balance == 1 ether, 68 | "original bidder should have money still" 69 | ); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/test/pnm/HookVaultStorageTest.t.sol.skip: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.10; 2 | 3 | import "./base.t.sol"; 4 | import "../../interfaces/IHookERC721Vault.sol"; 5 | import "@openzeppelin/contracts/proxy/beacon/IBeacon.sol"; 6 | 7 | contract HookVaultStorageTest is HookProtocolTest { 8 | uint32 tokenStartIndex = 300; 9 | 10 | address vaultProxy; 11 | uint256 initTokenId; 12 | 13 | function setUp() public { 14 | setUpAddresses(); 15 | setUpFullProtocol(); 16 | 17 | (vaultProxy, ) = createVaultandAsset(); 18 | initTokenId = getTokenId(); 19 | } 20 | 21 | function createVaultandAsset() internal returns (address, uint32) { 22 | vm.startPrank(admin); 23 | tokenStartIndex += 1; 24 | uint32 tokenId = tokenStartIndex; 25 | token.mint(address(writer), tokenId); 26 | address vaultAddress = address( 27 | vaultFactory.findOrCreateVault(address(token), tokenId) 28 | ); 29 | vm.stopPrank(); 30 | return (vaultAddress, tokenId); 31 | } 32 | 33 | function getTokenId() internal returns (uint256 tokenId) { 34 | uint256 tokenIdSlot = vmEx.getVarSlotIndex(address(vaultImpl), "_tokenId"); 35 | tokenId = vmEx.readUintBySlot(vaultProxy, tokenIdSlot); 36 | } 37 | 38 | function testTokenId() public { 39 | uint256 tokenId = getTokenId(); 40 | require(tokenId == tokenStartIndex, "_tokenId is wrong"); 41 | } 42 | 43 | function invariantTokenIdNotChanged() public { 44 | uint256 tokenId = getTokenId(); 45 | require(tokenId == initTokenId, "_tokenId is changed"); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/test/pnm/base.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.10; 3 | 4 | import "../utils/tokens/TestERC721.sol"; 5 | import "../utils/tokens/WETH.sol"; 6 | import "../../HookUpgradeableBeacon.sol"; 7 | import "../../HookCoveredCallFactory.sol"; 8 | import "../../HookCoveredCallImplV1.sol"; 9 | import "../../HookUpgradeableBeacon.sol"; 10 | import "../../HookERC721VaultFactory.sol"; 11 | import "../../HookERC721VaultImplV1.sol"; 12 | import "../../HookERC721MultiVaultImplV1.sol"; 13 | import "../../HookUpgradeableBeacon.sol"; 14 | import "../../HookProtocol.sol"; 15 | 16 | import "../../lib/Entitlements.sol"; 17 | import "../../lib/Signatures.sol"; 18 | import "../../mixin/EIP712.sol"; 19 | import "../../mixin/PermissionConstants.sol"; 20 | 21 | import "../../interfaces/IHookProtocol.sol"; 22 | import "../../interfaces/IHookCoveredCall.sol"; 23 | 24 | import {PTest, console} from "@narya-ai/contracts/PTest.sol"; 25 | 26 | /// @notice Utils to setup the protocol to build various test cases 27 | /// @author Regynald Augustin-regy@hook.xyz 28 | contract HookProtocolTest is PTest, EIP712, PermissionConstants { 29 | address internal admin; 30 | address internal buyer; 31 | uint256 internal writerpkey; 32 | address internal writer; 33 | address internal firstBidder; 34 | address internal secondBidder; 35 | IHookCoveredCall calls; 36 | // can use this identifier to call fns not on the interface 37 | HookCoveredCallImplV1 callInternal; 38 | TestERC721 internal token; 39 | WETH internal weth; 40 | uint256 internal underlyingTokenId; 41 | address internal protocolAddress; 42 | HookProtocol protocol; 43 | uint256 internal optionTokenId; 44 | address internal preApprovedOperator; 45 | HookERC721VaultFactory vaultFactory; 46 | 47 | HookERC721VaultImplV1 vaultImpl; 48 | HookERC721MultiVaultImplV1 multiVaultImpl; 49 | 50 | event CallCreated( 51 | address writer, address vaultAddress, uint256 assetId, uint256 optionId, uint256 strikePrice, uint256 expiration 52 | ); 53 | 54 | event CallSettled(uint256 optionId); 55 | 56 | event CallReclaimed(uint256 optionId); 57 | 58 | event ExpiredCallBurned(uint256 optionId); 59 | 60 | function setUpAddresses() public { 61 | token = new TestERC721(); 62 | weth = new WETH(); 63 | 64 | buyer = address(4); 65 | vm.label(buyer, "option buyer"); 66 | 67 | writerpkey = uint256(0xBDCE); 68 | writer = vm.addr(writerpkey); 69 | vm.label(writer, "option writer"); 70 | 71 | admin = address(69); 72 | vm.label(admin, "contract admin"); 73 | 74 | firstBidder = address(37); 75 | vm.label(firstBidder, "First option bidder"); 76 | 77 | secondBidder = address(38); 78 | vm.label(secondBidder, "Second option bidder"); 79 | } 80 | 81 | function setUpFullProtocol() public { 82 | weth = new WETH(); 83 | protocol = new HookProtocol( 84 | admin, 85 | admin, 86 | admin, 87 | admin, 88 | admin, 89 | admin, 90 | address(weth) 91 | ); 92 | protocolAddress = address(protocol); 93 | // set the operator to a new protocol to make it a contract 94 | preApprovedOperator = address(weth); 95 | setAddressForEipDomain(protocolAddress); 96 | 97 | // Deploy new vault factory 98 | vaultImpl = new HookERC721VaultImplV1(); 99 | 100 | HookUpgradeableBeacon vaultBeacon = new HookUpgradeableBeacon( 101 | address(vaultImpl), 102 | address(protocol), 103 | PermissionConstants.VAULT_UPGRADER 104 | ); 105 | 106 | multiVaultImpl = new HookERC721MultiVaultImplV1(); 107 | 108 | HookUpgradeableBeacon multiVaultBeacon = new HookUpgradeableBeacon( 109 | address(multiVaultImpl), 110 | address(protocol), 111 | PermissionConstants.VAULT_UPGRADER 112 | ); 113 | 114 | vaultFactory = new HookERC721VaultFactory( 115 | protocolAddress, 116 | address(vaultBeacon), 117 | address(multiVaultBeacon) 118 | ); 119 | vm.prank(address(admin)); 120 | protocol.setVaultFactory(address(vaultFactory)); 121 | 122 | // Deploy a new Covered Call Factory 123 | HookCoveredCallImplV1 callImpl = new HookCoveredCallImplV1(); 124 | HookUpgradeableBeacon callBeacon = new HookUpgradeableBeacon( 125 | address(callImpl), 126 | address(protocol), 127 | PermissionConstants.CALL_UPGRADER 128 | ); 129 | HookCoveredCallFactory callFactory = new HookCoveredCallFactory( 130 | protocolAddress, 131 | address(callBeacon), 132 | preApprovedOperator 133 | ); 134 | vm.prank(address(admin)); 135 | protocol.setCoveredCallFactory(address(callFactory)); 136 | vm.prank(address(admin)); 137 | 138 | // make a call insturment for our token 139 | calls = IHookCoveredCall(callFactory.makeCallInstrument(address(token))); 140 | callInternal = HookCoveredCallImplV1(address(calls)); 141 | } 142 | 143 | function setUpMintOption() public { 144 | vm.startPrank(address(writer)); 145 | 146 | // Writer approve covered call 147 | token.setApprovalForAll(address(calls), true); 148 | 149 | uint32 expiration = uint32(block.timestamp) + 3 days; 150 | 151 | vm.expectEmit(true, true, true, false); 152 | emit CallCreated( 153 | address(writer), 154 | address(token), 155 | 0, 156 | 1, // This would be the first option id. 157 | 1000, 158 | expiration 159 | ); 160 | optionTokenId = calls.mintWithErc721(address(token), underlyingTokenId, 1000, expiration); 161 | 162 | // Assume that the writer somehow sold the option NFT to the buyer. 163 | // Outside of the scope of these tests. 164 | calls.safeTransferFrom(writer, buyer, optionTokenId); 165 | vm.stopPrank(); 166 | } 167 | 168 | function setUpOptionBids() public { 169 | vm.deal(address(firstBidder), 1 ether); 170 | 171 | vm.deal(address(secondBidder), 1 ether); 172 | 173 | vm.warp(block.timestamp + 2.1 days); 174 | 175 | vm.prank(firstBidder); 176 | calls.bid{value: 0.1 ether}(optionTokenId); 177 | 178 | vm.prank(secondBidder); 179 | calls.bid{value: 0.2 ether}(optionTokenId); 180 | 181 | // Fast forward to beyond the expiration date. 182 | vm.warp(block.timestamp + 3.1 days); 183 | } 184 | 185 | function makeSignature(uint256 tokenId, uint32 expiry, address _writer) 186 | internal 187 | returns (Signatures.Signature memory) 188 | { 189 | address va = address(vaultFactory.findOrCreateVault(address(token), tokenId)); 190 | 191 | uint32 assetId = 0; 192 | if ( 193 | va 194 | == Create2.computeAddress( 195 | BeaconSalts.multiVaultSalt(address(token)), BeaconSalts.ByteCodeHash, address(vaultFactory) 196 | ) 197 | ) { 198 | // If the vault is a multi-vault, it requires that the assetId matches the 199 | // tokenId, instead of having a standard assetI of 0 200 | assetId = uint32(tokenId); 201 | } 202 | 203 | bytes32 structHash = Entitlements.getEntitlementStructHash( 204 | Entitlements.Entitlement({ 205 | beneficialOwner: address(_writer), 206 | operator: address(calls), 207 | vaultAddress: va, 208 | assetId: assetId, 209 | expiry: expiry 210 | }) 211 | ); 212 | 213 | (uint8 v, bytes32 r, bytes32 s) = vm.sign(writerpkey, _getEIP712Hash(structHash)); 214 | Signatures.Signature memory sig = 215 | Signatures.Signature({signatureType: Signatures.SignatureType.EIP712, v: v, r: r, s: s}); 216 | return sig; 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /src/test/utils/base.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.10; 3 | 4 | import "forge-std/Test.sol"; 5 | 6 | import "./tokens/TestERC721.sol"; 7 | import "./tokens/WETH.sol"; 8 | import "../../HookUpgradeableBeacon.sol"; 9 | import "../../HookCoveredCallFactory.sol"; 10 | import "../../HookCoveredCallImplV1.sol"; 11 | import "../../HookUpgradeableBeacon.sol"; 12 | import "../../HookERC721VaultFactory.sol"; 13 | import "../../HookERC721VaultImplV1.sol"; 14 | import "../../HookERC721MultiVaultImplV1.sol"; 15 | import "../../HookUpgradeableBeacon.sol"; 16 | import "../../HookProtocol.sol"; 17 | 18 | import "../../lib/Entitlements.sol"; 19 | import "../../lib/Signatures.sol"; 20 | import "../../mixin/EIP712.sol"; 21 | import "../../mixin/PermissionConstants.sol"; 22 | 23 | import "../../interfaces/IHookProtocol.sol"; 24 | import "../../interfaces/IHookCoveredCall.sol"; 25 | 26 | /// @notice Utils to setup the protocol to build various test cases 27 | /// @author Regynald Augustin-regy@hook.xyz 28 | contract HookProtocolTest is Test, EIP712, PermissionConstants { 29 | address internal admin; 30 | address internal buyer; 31 | uint256 internal writerpkey; 32 | address internal writer; 33 | address internal firstBidder; 34 | address internal secondBidder; 35 | IHookCoveredCall calls; 36 | // can use this identifier to call fns not on the interface 37 | HookCoveredCallImplV1 callInternal; 38 | TestERC721 internal token; 39 | WETH internal weth; 40 | uint256 internal underlyingTokenId; 41 | address internal protocolAddress; 42 | HookProtocol protocol; 43 | uint256 internal optionTokenId; 44 | address internal preApprovedOperator; 45 | HookERC721VaultFactory vaultFactory; 46 | 47 | event CallCreated( 48 | address writer, address vaultAddress, uint256 assetId, uint256 optionId, uint256 strikePrice, uint256 expiration 49 | ); 50 | 51 | event CallSettled(uint256 optionId); 52 | 53 | event CallReclaimed(uint256 optionId); 54 | 55 | event ExpiredCallBurned(uint256 optionId); 56 | 57 | function setUpAddresses() public { 58 | token = new TestERC721(); 59 | weth = new WETH(); 60 | 61 | buyer = address(4); 62 | vm.label(buyer, "option buyer"); 63 | 64 | writerpkey = uint256(0xBDCE); 65 | writer = vm.addr(writerpkey); 66 | vm.label(writer, "option writer"); 67 | 68 | admin = address(69); 69 | vm.label(admin, "contract admin"); 70 | 71 | firstBidder = address(37); 72 | vm.label(firstBidder, "First option bidder"); 73 | 74 | secondBidder = address(38); 75 | vm.label(secondBidder, "Second option bidder"); 76 | } 77 | 78 | function setUpFullProtocol() public { 79 | weth = new WETH(); 80 | protocol = new HookProtocol( 81 | admin, 82 | admin, 83 | admin, 84 | admin, 85 | admin, 86 | admin, 87 | address(weth) 88 | ); 89 | protocolAddress = address(protocol); 90 | // set the operator to a new protocol to make it a contract 91 | preApprovedOperator = address(weth); 92 | setAddressForEipDomain(protocolAddress); 93 | 94 | // Deploy new vault factory 95 | HookERC721VaultImplV1 vaultImpl = new HookERC721VaultImplV1(); 96 | 97 | HookUpgradeableBeacon vaultBeacon = new HookUpgradeableBeacon( 98 | address(vaultImpl), 99 | address(protocol), 100 | PermissionConstants.VAULT_UPGRADER 101 | ); 102 | 103 | HookERC721MultiVaultImplV1 multiVaultImpl = new HookERC721MultiVaultImplV1(); 104 | 105 | HookUpgradeableBeacon multiVaultBeacon = new HookUpgradeableBeacon( 106 | address(multiVaultImpl), 107 | address(protocol), 108 | PermissionConstants.VAULT_UPGRADER 109 | ); 110 | 111 | vaultFactory = new HookERC721VaultFactory( 112 | protocolAddress, 113 | address(vaultBeacon), 114 | address(multiVaultBeacon) 115 | ); 116 | vm.prank(address(admin)); 117 | protocol.setVaultFactory(address(vaultFactory)); 118 | 119 | // Deploy a new Covered Call Factory 120 | HookCoveredCallImplV1 callImpl = new HookCoveredCallImplV1(); 121 | HookUpgradeableBeacon callBeacon = new HookUpgradeableBeacon( 122 | address(callImpl), 123 | address(protocol), 124 | PermissionConstants.CALL_UPGRADER 125 | ); 126 | HookCoveredCallFactory callFactory = new HookCoveredCallFactory( 127 | protocolAddress, 128 | address(callBeacon), 129 | preApprovedOperator 130 | ); 131 | vm.prank(address(admin)); 132 | protocol.setCoveredCallFactory(address(callFactory)); 133 | vm.prank(address(admin)); 134 | 135 | // make a call insturment for our token 136 | calls = IHookCoveredCall(callFactory.makeCallInstrument(address(token))); 137 | callInternal = HookCoveredCallImplV1(address(calls)); 138 | } 139 | 140 | function setUpMintOption() public { 141 | vm.startPrank(address(writer)); 142 | 143 | // Writer approve covered call 144 | token.setApprovalForAll(address(calls), true); 145 | 146 | uint32 expiration = uint32(block.timestamp) + 3 days; 147 | 148 | vm.expectEmit(true, true, true, false); 149 | emit CallCreated( 150 | address(writer), 151 | address(token), 152 | 0, 153 | 1, // This would be the first option id. 154 | 1000, 155 | expiration 156 | ); 157 | optionTokenId = calls.mintWithErc721(address(token), underlyingTokenId, 1000, expiration); 158 | 159 | // Assume that the writer somehow sold the option NFT to the buyer. 160 | // Outside of the scope of these tests. 161 | calls.safeTransferFrom(writer, buyer, optionTokenId); 162 | vm.stopPrank(); 163 | } 164 | 165 | function setUpOptionBids() public { 166 | vm.deal(address(firstBidder), 1 ether); 167 | 168 | vm.deal(address(secondBidder), 1 ether); 169 | 170 | vm.warp(block.timestamp + 2.1 days); 171 | 172 | vm.prank(firstBidder); 173 | calls.bid{value: 0.1 ether}(optionTokenId); 174 | 175 | vm.prank(secondBidder); 176 | calls.bid{value: 0.2 ether}(optionTokenId); 177 | 178 | // Fast forward to beyond the expiration date. 179 | vm.warp(block.timestamp + 3.1 days); 180 | } 181 | 182 | function makeSignature(uint256 tokenId, uint32 expiry, address _writer) 183 | internal 184 | returns (Signatures.Signature memory) 185 | { 186 | address va = address(vaultFactory.findOrCreateVault(address(token), tokenId)); 187 | 188 | uint32 assetId = 0; 189 | if ( 190 | va 191 | == Create2.computeAddress( 192 | BeaconSalts.multiVaultSalt(address(token)), BeaconSalts.ByteCodeHash, address(vaultFactory) 193 | ) 194 | ) { 195 | // If the vault is a multi-vault, it requires that the assetId matches the 196 | // tokenId, instead of having a standard assetI of 0 197 | assetId = uint32(tokenId); 198 | } 199 | 200 | bytes32 structHash = Entitlements.getEntitlementStructHash( 201 | Entitlements.Entitlement({ 202 | beneficialOwner: address(_writer), 203 | operator: address(calls), 204 | vaultAddress: va, 205 | assetId: assetId, 206 | expiry: expiry 207 | }) 208 | ); 209 | 210 | (uint8 v, bytes32 r, bytes32 s) = vm.sign(writerpkey, _getEIP712Hash(structHash)); 211 | Signatures.Signature memory sig = 212 | Signatures.Signature({signatureType: Signatures.SignatureType.EIP712, v: v, r: r, s: s}); 213 | return sig; 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /src/test/utils/mocks/FlashLoan.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.10; 2 | 3 | import "src/interfaces/IERC721FlashLoanReceiver.sol"; 4 | import "../tokens/TestERC721.sol"; 5 | import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; 6 | 7 | contract FlashLoanSuccess is IERC721FlashLoanReceiver { 8 | constructor() {} 9 | 10 | function executeOperation(address nftContract, uint256 tokenId, address, address vault, bytes calldata) 11 | external 12 | returns (bool) 13 | { 14 | IERC721(nftContract).approve(vault, tokenId); 15 | return IERC721(nftContract).ownerOf(tokenId) == address(this); 16 | } 17 | 18 | function onERC721Received(address, address, uint256, bytes calldata) public pure override returns (bytes4) { 19 | return IERC721Receiver.onERC721Received.selector; 20 | } 21 | } 22 | 23 | contract FlashLoanDoesNotApprove is IERC721FlashLoanReceiver { 24 | constructor() {} 25 | 26 | function executeOperation(address nftContract, uint256 tokenId, address, address, bytes calldata) 27 | external 28 | view 29 | returns (bool) 30 | { 31 | // skip this: 32 | // IERC721(nftContract).approve(vault, tokenId); 33 | return IERC721(nftContract).ownerOf(tokenId) == address(this); 34 | } 35 | 36 | function onERC721Received(address, address, uint256, bytes calldata) public pure override returns (bytes4) { 37 | return IERC721Receiver.onERC721Received.selector; 38 | } 39 | } 40 | 41 | contract FlashLoanReturnsFalse is IERC721FlashLoanReceiver { 42 | constructor() {} 43 | 44 | function executeOperation(address nftContract, uint256 tokenId, address, address vault, bytes calldata) 45 | external 46 | returns (bool) 47 | { 48 | IERC721(nftContract).approve(vault, tokenId); 49 | return false; 50 | } 51 | 52 | function onERC721Received(address, address, uint256, bytes calldata) public pure override returns (bytes4) { 53 | return IERC721Receiver.onERC721Received.selector; 54 | } 55 | } 56 | 57 | contract FlashLoanApproveForAll is IERC721FlashLoanReceiver { 58 | constructor() {} 59 | 60 | function executeOperation(address nftContract, uint256 tokenId, address, address vault, bytes calldata) 61 | external 62 | returns (bool) 63 | { 64 | IERC721(nftContract).setApprovalForAll(vault, true); 65 | return IERC721(nftContract).ownerOf(tokenId) == address(this); 66 | } 67 | 68 | function onERC721Received(address, address, uint256, bytes calldata) public pure override returns (bytes4) { 69 | return IERC721Receiver.onERC721Received.selector; 70 | } 71 | } 72 | 73 | contract FlashLoanBurnsAsset is IERC721FlashLoanReceiver { 74 | constructor() {} 75 | 76 | function executeOperation(address nftContract, uint256 tokenId, address, address vault, bytes calldata) 77 | external 78 | returns (bool) 79 | { 80 | IERC721(nftContract).setApprovalForAll(vault, true); 81 | TestERC721(nftContract).burn(tokenId); 82 | return true; 83 | } 84 | 85 | function onERC721Received(address, address, uint256, bytes calldata) public pure override returns (bytes4) { 86 | return IERC721Receiver.onERC721Received.selector; 87 | } 88 | } 89 | 90 | contract FlashLoanVerifyCalldata is IERC721FlashLoanReceiver { 91 | constructor() {} 92 | 93 | function executeOperation(address nftContract, uint256, address, address vault, bytes calldata params) 94 | external 95 | returns (bool) 96 | { 97 | require(keccak256(params) == keccak256("hello world"), "should check helloworld"); 98 | IERC721(nftContract).setApprovalForAll(vault, true); 99 | return true; 100 | } 101 | 102 | function onERC721Received(address, address, uint256, bytes calldata) public pure override returns (bytes4) { 103 | return IERC721Receiver.onERC721Received.selector; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/test/utils/mocks/MaliciousBidder.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.10; 2 | 3 | import "../../../interfaces/IHookCoveredCall.sol"; 4 | 5 | // @dev a smart contract that reverts upon receiveing funds 6 | // and allows a bid to be mocked to a specific covered call option. 7 | // this can be used to write tests that fail if a contract reverting 8 | // prevents new bids. 9 | contract MaliciousBidder { 10 | IHookCoveredCall private callOption; 11 | bool private throwOnReceive; 12 | 13 | constructor(address _callOption) { 14 | callOption = IHookCoveredCall(_callOption); 15 | throwOnReceive = true; 16 | } 17 | 18 | function bid(uint256 optionId) public payable { 19 | callOption.bid{value: msg.value}(optionId); 20 | } 21 | 22 | receive() external payable { 23 | require(!throwOnReceive, "ha ha ha gotcha"); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/test/utils/mocks/PropertyValidator1.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.10; 2 | 3 | import "../../../HookCoveredCallImplV1.sol"; 4 | import "../../../interfaces/IHookERC721Vault.sol"; 5 | import "../../../interfaces/zeroex-v4/IPropertyValidator.sol"; 6 | 7 | library Types { 8 | enum Operation { 9 | Ignore, 10 | LessThanOrEqualTo, 11 | GreaterThanOrEqualTo, 12 | Equal 13 | } 14 | } 15 | 16 | contract PropertyValidator1 is IPropertyValidator { 17 | function validateProperty(address tokenAddress, uint256 tokenId, bytes calldata propertyData) 18 | external 19 | view 20 | override 21 | { 22 | ( 23 | uint256 strikePrice, 24 | Types.Operation strikePriceOperation, 25 | uint256 expiry, 26 | Types.Operation expiryOperation, 27 | bool withinRange, 28 | uint256 tokenIdLow, 29 | uint256 tokenIdHigh 30 | ) = abi.decode(propertyData, (uint256, Types.Operation, uint256, Types.Operation, bool, uint256, uint256)); 31 | 32 | HookCoveredCallImplV1 optionContract = HookCoveredCallImplV1(tokenAddress); 33 | 34 | compare(optionContract.getStrikePrice(tokenId), strikePrice, strikePriceOperation); 35 | 36 | if (withinRange) { 37 | uint32 assetId = optionContract.getAssetId(tokenId); 38 | if (assetId > 0) { 39 | /// if the assetId is non-zero, we know that this asset is 40 | /// within a multivault and we can simply get the data from the call 41 | ensureInRange(assetId, tokenIdLow, tokenIdHigh); 42 | } else { 43 | IHookERC721Vault vault = IHookERC721Vault(optionContract.getVaultAddress(tokenId)); 44 | ensureInRange(vault.assetTokenId(assetId), tokenIdLow, tokenIdHigh); 45 | } 46 | } 47 | 48 | compare(optionContract.getExpiration(tokenId), expiry, expiryOperation); 49 | } 50 | 51 | function ensureInRange(uint256 tokenId, uint256 lowerBound, uint256 upperBound) internal pure { 52 | require(tokenId >= lowerBound, "tokenId must be above the lower bound"); 53 | require(tokenId <= upperBound, "tokenId must be below the upper bound"); 54 | } 55 | 56 | function compare(uint256 actual, uint256 comparingTo, Types.Operation operation) internal pure { 57 | if (operation == Types.Operation.Equal) { 58 | require(actual == comparingTo, "values are not equal"); 59 | } else if (operation == Types.Operation.LessThanOrEqualTo) { 60 | require(actual <= comparingTo, "actual value is not <= comparison value"); 61 | } else if (operation == Types.Operation.GreaterThanOrEqualTo) { 62 | require(actual >= comparingTo, "actual value is not >= comparison value"); 63 | } 64 | } 65 | 66 | function encode( 67 | uint256 strikePrice, 68 | Types.Operation strikePriceOperation, 69 | uint256 expiry, 70 | Types.Operation expiryOperation, 71 | bool withinRange, 72 | uint256 tokenIdLow, 73 | uint256 tokenIdHigh 74 | ) external pure returns (bytes memory) { 75 | return abi.encode( 76 | strikePrice, strikePriceOperation, expiry, expiryOperation, withinRange, tokenIdLow, tokenIdHigh 77 | ); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/test/utils/mocks/PropertyValidatorReverts.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.10; 2 | 3 | import "../../../interfaces/zeroex-v4/IPropertyValidator.sol"; 4 | 5 | contract PropertyValidatorReverts is IPropertyValidator { 6 | function validateProperty(address tokenAddress, uint256 tokenId, bytes calldata propertyData) 7 | external 8 | view 9 | override 10 | { 11 | revert("PropertyValidator: BAD PROPERTIES"); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/test/utils/tokens/TestERC721.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity ^0.8.10; 3 | 4 | import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; 5 | import "@openzeppelin/contracts/access/Ownable.sol"; 6 | 7 | /// @title TestERC721 8 | /// @notice FOR TEST PURPOSES ONLY. 9 | contract TestERC721 is ERC721, Ownable { 10 | constructor() ERC721("TestERC721", "TEST") {} 11 | 12 | function mint(address to, uint256 tokenId) public { 13 | _safeMint(to, tokenId); 14 | } 15 | 16 | function burn(uint256 tokenId) public { 17 | _burn(tokenId); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/test/utils/tokens/WETH.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity ^0.8.10; 3 | 4 | /// @title WETH 5 | /// @notice FOR TEST PURPOSES ONLY. 6 | /// Source: https://github.com/gnosis/canonical-weth/blob/0dd1ea3e295eef916d0c6223ec63141137d22d67/contracts/WETH9.sol 7 | contract WETH { 8 | string public name = "Wrapped Ether"; 9 | string public symbol = "WETH"; 10 | uint8 public decimals = 18; 11 | 12 | event Approval(address indexed src, address indexed guy, uint256 wad); 13 | event Transfer(address indexed src, address indexed dst, uint256 wad); 14 | event Deposit(address indexed dst, uint256 wad); 15 | event Withdrawal(address indexed src, uint256 wad); 16 | 17 | mapping(address => uint256) public balanceOf; 18 | mapping(address => mapping(address => uint256)) public allowance; 19 | 20 | fallback() external payable { 21 | deposit(); 22 | } 23 | 24 | receive() external payable { 25 | deposit(); 26 | } 27 | 28 | function deposit() public payable { 29 | balanceOf[msg.sender] += msg.value; 30 | emit Deposit(msg.sender, msg.value); 31 | } 32 | 33 | function withdraw(uint256 wad) public { 34 | require(balanceOf[msg.sender] >= wad); 35 | balanceOf[msg.sender] -= wad; 36 | payable(msg.sender).transfer(wad); 37 | emit Withdrawal(msg.sender, wad); 38 | } 39 | 40 | function totalSupply() public view returns (uint256) { 41 | return address(this).balance; 42 | } 43 | 44 | function approve(address guy, uint256 wad) public returns (bool) { 45 | allowance[msg.sender][guy] = wad; 46 | emit Approval(msg.sender, guy, wad); 47 | return true; 48 | } 49 | 50 | function transfer(address dst, uint256 wad) public returns (bool) { 51 | return transferFrom(msg.sender, dst, wad); 52 | } 53 | 54 | function transferFrom(address src, address dst, uint256 wad) public returns (bool) { 55 | require(balanceOf[src] >= wad); 56 | 57 | if (src != msg.sender && allowance[src][msg.sender] != type(uint128).max) { 58 | require(allowance[src][msg.sender] >= wad); 59 | allowance[src][msg.sender] -= wad; 60 | } 61 | 62 | balanceOf[src] -= wad; 63 | balanceOf[dst] += wad; 64 | 65 | emit Transfer(src, dst, wad); 66 | 67 | return true; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2018", 4 | "module": "commonjs", 5 | "strict": true, 6 | "esModuleInterop": true, 7 | "outDir": "dist", 8 | "declaration": true 9 | }, 10 | "include": ["./scripts", "./integration", "./typechain", "./deploy"], 11 | "files": ["./hardhat.config.ts"] 12 | } 13 | --------------------------------------------------------------------------------