├── .commitlintrc ├── .eslintignore ├── .eslintrc ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ ├── checks.yaml │ └── tests.yaml ├── .gitignore ├── .gitmodules ├── .husky ├── commit-msg └── pre-commit ├── .npmignore ├── .prettierignore ├── .prettierrc ├── .release-it.js ├── .solcover.js ├── .solhint.json ├── .solhintignore ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── SECURITY.md ├── contracts ├── AffiliateManager.sol ├── BatchOrderTypehashRegistry.sol ├── CreatorFeeManagerWithRebates.sol ├── CreatorFeeManagerWithRoyalties.sol ├── CurrencyManager.sol ├── ExecutionManager.sol ├── InheritedStrategy.sol ├── LooksRareProtocol.sol ├── NonceManager.sol ├── ProtocolFeeRecipient.sol ├── StrategyManager.sol ├── TransferManager.sol ├── TransferSelectorNFT.sol ├── constants │ ├── AssemblyConstants.sol │ ├── NumericConstants.sol │ └── ValidationCodeConstants.sol ├── enums │ ├── CollectionType.sol │ └── QuoteType.sol ├── errors │ ├── ChainlinkErrors.sol │ ├── ReservoirErrors.sol │ └── SharedErrors.sol ├── executionStrategies │ ├── BaseStrategy.sol │ ├── Chainlink │ │ ├── BaseStrategyChainlinkMultiplePriceFeeds.sol │ │ ├── BaseStrategyChainlinkPriceLatency.sol │ │ ├── StrategyChainlinkFloor.sol │ │ └── StrategyChainlinkUSDDynamicAsk.sol │ ├── Reservoir │ │ └── StrategyReservoirCollectionOffer.sol │ ├── StrategyCollectionOffer.sol │ ├── StrategyDutchAuction.sol │ └── StrategyItemIdsRange.sol ├── helpers │ ├── OrderValidatorV2A.sol │ └── ProtocolHelpers.sol ├── interfaces │ ├── IAffiliateManager.sol │ ├── ICreatorFeeManager.sol │ ├── ICurrencyManager.sol │ ├── IExecutionManager.sol │ ├── IImmutableCreate2Factory.sol │ ├── ILooksRareProtocol.sol │ ├── INonceManager.sol │ ├── IRoyaltyFeeRegistry.sol │ ├── IStrategy.sol │ ├── IStrategyManager.sol │ └── ITransferManager.sol └── libraries │ ├── CurrencyValidator.sol │ ├── OpenZeppelin │ ├── MerkleProofCalldataWithNodes.sol │ └── MerkleProofMemory.sol │ └── OrderStructs.sol ├── foundry.toml ├── hardhat.config.ts ├── inheritance-contracts.svg ├── package.json ├── remappings.txt ├── scripts ├── AddAffiliate.s.sol └── deployment │ ├── Deployment.s.sol │ └── ProtocolFeeRecipientDeployment.s.sol ├── test ├── foundry │ ├── AffiliateManagerAndOrders.t.sol │ ├── BatchMakerCollectionOrders.t.sol │ ├── BatchMakerOrders.t.sol │ ├── BatchOrderTypehashRegistry.t.sol │ ├── BundleTransactions.t.sol │ ├── CreatorFeeManagerWithRebates.t.sol │ ├── CreatorFeeManagerWithRoyalties.t.sol │ ├── CurrencyManager.t.sol │ ├── DelegationRecipientsTaker.t.sol │ ├── DomainSeparatorUpdates.t.sol │ ├── ExecutionManager.t.sol │ ├── GasGriefing.t.sol │ ├── InitialStates.t.sol │ ├── LooksRareProtocol.t.sol │ ├── NonceInvalidation.t.sol │ ├── OrderValidatorV2A.t.sol │ ├── ProtocolBase.t.sol │ ├── ProtocolFeeRecipient.t.sol │ ├── Sandbox.t.sol │ ├── SignaturesEIP2098.t.sol │ ├── SignaturesERC1271WalletForERC1155.t.sol │ ├── SignaturesERC1271WalletForERC721.t.sol │ ├── SignaturesRevertions.t.sol │ ├── StandardTransactions.t.sol │ ├── StrategyManager.t.sol │ ├── TransferManager.t.sol │ ├── assembly │ │ └── VerifyOrderTimestampValidityEquivalence.t.sol │ ├── executionStrategies │ │ ├── Chainlink │ │ │ ├── FloorFromChainlinkDiscountBasisPointsOrders.t.sol │ │ │ ├── FloorFromChainlinkDiscountFixedAmountOrders.t.sol │ │ │ ├── FloorFromChainlinkDiscountOrders.t.sol │ │ │ ├── FloorFromChainlinkOrders.t.sol │ │ │ ├── FloorFromChainlinkPremiumBasisPointsOrders.t.sol │ │ │ ├── FloorFromChainlinkPremiumFixedAmountOrders.t.sol │ │ │ ├── FloorFromChainlinkPremiumOrders.t.sol │ │ │ └── USDDynamicAskOrders.t.sol │ │ ├── CollectionOffers.t.sol │ │ ├── DutchAuctionOrders.t.sol │ │ ├── ItemIdsRangeOrders.t.sol │ │ ├── MultiFillCollectionOrders.t.sol │ │ └── Reservoir │ │ │ └── StrategyReservoirCollectionOffer.t.sol │ └── utils │ │ ├── BytesLib.sol │ │ ├── EIP712MerkleTree.sol │ │ ├── ERC1271Wallet.sol │ │ ├── GasGriefer.sol │ │ ├── MaliciousERC1271Wallet.sol │ │ ├── MaliciousIsValidSignatureERC1271Wallet.sol │ │ ├── MaliciousOnERC1155ReceivedERC1271Wallet.sol │ │ ├── MaliciousOnERC1155ReceivedTheThirdTimeERC1271Wallet.sol │ │ ├── MathLib.sol │ │ ├── MerkleWithPosition.sol │ │ ├── MockOrderGenerator.sol │ │ ├── ProtocolHelpers.sol │ │ ├── StrategyTestMultiFillCollectionOrder.sol │ │ ├── TestHelpers.sol │ │ └── TestParameters.sol └── mock │ ├── MockChainlinkAggregator.sol │ ├── MockERC1155.sol │ ├── MockERC1155SupportsNoInterface.sol │ ├── MockERC1155WithoutAnyBalanceOf.sol │ ├── MockERC1155WithoutBalanceOfBatch.sol │ ├── MockERC1155WithoutIsApprovedForAll.sol │ ├── MockERC20.sol │ ├── MockERC721.sol │ ├── MockERC721SupportsNoInterface.sol │ ├── MockERC721WithRoyalties.sol │ ├── MockRoyaltyFeeRegistry.sol │ └── MockSmartWallet.sol ├── tsconfig.json └── yarn.lock /.commitlintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "@commitlint/config-conventional" 4 | ], 5 | "rules": { 6 | "subject-case": [ 7 | 2, 8 | "always", 9 | "sentence-case" 10 | ], 11 | "type-enum": [ 12 | 2, 13 | "always", 14 | [ 15 | "build", 16 | "ci", 17 | "chore", 18 | "docs", 19 | "feat", 20 | "fix", 21 | "perf", 22 | "refactor", 23 | "revert", 24 | "style", 25 | "test" 26 | ] 27 | ] 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | artifacts 3 | cache 4 | coverage* 5 | gasReporterOutput.json 6 | typechain 7 | lib* 8 | .gas-snapshot 9 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 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:@typescript-eslint/recommended", 12 | "plugin:prettier/recommended", 13 | "plugin:node/recommended" 14 | ], 15 | "parser": "@typescript-eslint/parser", 16 | "parserOptions": { 17 | "ecmaVersion": 12 18 | }, 19 | "rules": { 20 | "node/no-unsupported-features/es-syntax": ["error", { "ignores": ["modules"] }] 21 | }, 22 | "settings": { 23 | "node": { 24 | "tryExtensions": [".js", ".json", ".ts", ".d.ts"] 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners 2 | 3 | @0xShisui @0xJurassicPunk @0xhiroshi 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help improve this repo 4 | title: "" 5 | labels: "" 6 | assignees: "" 7 | --- 8 | 9 | - **Describe the bug** 10 | A clear and concise description of what the bug is. 11 | 12 | - **To reproduce** 13 | Steps to reproduce the behavior: 14 | 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | - **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | - **Additional context** 24 | Add any other context about the problem here. 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this repo 4 | title: "" 5 | labels: "" 6 | assignees: "" 7 | --- 8 | 9 | - **Is your feature request related to a problem? Please describe.** 10 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 11 | 12 | - **Describe the solution you'd like** 13 | A clear and concise description of what you want to happen. 14 | 15 | - **Describe alternatives you've considered** 16 | A clear and concise description of any alternative solutions or features you've considered. 17 | 18 | - **Additional context** 19 | Add any other context or screenshots about the feature request here. 20 | -------------------------------------------------------------------------------- /.github/workflows/checks.yaml: -------------------------------------------------------------------------------- 1 | name: Format and lint checks 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - master 8 | 9 | jobs: 10 | format: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@v2 15 | with: 16 | submodules: recursive 17 | - name: Setup Node 18 | uses: actions/setup-node@v3 19 | with: 20 | node-version: 14.x 21 | - name: Get yarn cache directory path 22 | id: yarn-cache-dir-path 23 | run: echo "::set-output name=dir::$(yarn cache dir)" 24 | - uses: actions/cache@v2 25 | id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) 26 | with: 27 | path: ${{ steps.yarn-cache-dir-path.outputs.dir }} 28 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 29 | restore-keys: | 30 | ${{ runner.os }}-yarn- 31 | - name: Install dependencies 32 | run: yarn install --frozen-lockfile 33 | - name: Run format checks 34 | run: yarn format:check 35 | - name: Run lint 36 | run: yarn lint 37 | -------------------------------------------------------------------------------- /.github/workflows/tests.yaml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - master 8 | 9 | env: 10 | MAINNET_RPC_URL: "https://rpc.ankr.com/eth" 11 | GOERLI_RPC_URL: "https://goerli.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161" 12 | 13 | jobs: 14 | tests: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout code 18 | uses: actions/checkout@v2 19 | with: 20 | submodules: recursive 21 | - name: Setup Node 22 | uses: actions/setup-node@v3 23 | with: 24 | node-version: 14.x 25 | - name: Get yarn cache directory path 26 | id: yarn-cache-dir-path 27 | run: echo "::set-output name=dir::$(yarn cache dir)" 28 | - uses: actions/cache@v2 29 | id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) 30 | with: 31 | path: ${{ steps.yarn-cache-dir-path.outputs.dir }} 32 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 33 | restore-keys: | 34 | ${{ runner.os }}-yarn- 35 | - name: Install dev dependencies 36 | run: yarn install --frozen-lockfile 37 | - name: Install Foundry 38 | uses: onbjerg/foundry-toolchain@v1 39 | with: 40 | version: nightly 41 | - name: Compile code (Hardhat) 42 | run: yarn compile:hardhat 43 | - name: Run TypeScript/Waffle tests 44 | run: yarn test:hardhat 45 | - name: Run Solidity/Forge tests 46 | run: yarn test:forge 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | .env* 4 | coverage 5 | coverage.json 6 | typechain 7 | .DS_Store 8 | 9 | # Hardhat files 10 | cache 11 | artifacts 12 | abis/ 13 | docs/ 14 | 15 | # Forge coverage and gas 16 | .gas-snapshot 17 | lcov* 18 | out/ 19 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/forge-std"] 2 | path = lib/forge-std 3 | url = https://github.com/foundry-rs/forge-std 4 | [submodule "lib/murky"] 5 | path = lib/murky 6 | url = https://github.com/dmfxyz/murky 7 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn commitlint --edit $1 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn lint 5 | yarn format:check 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | .env* 4 | coverage 5 | coverage.json 6 | typechain 7 | 8 | # Hardhat files 9 | cache 10 | artifacts 11 | hardhat.config.ts 12 | contracts/test 13 | test/ 14 | docs/ 15 | 16 | # Others 17 | lib/ 18 | lib/murky/ 19 | .gas-snapshot 20 | .DS_Store 21 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | artifacts 3 | cache 4 | coverage* 5 | gasReporterOutput.json 6 | typechain 7 | lib* 8 | docs* 9 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120 3 | } 4 | -------------------------------------------------------------------------------- /.release-it.js: -------------------------------------------------------------------------------- 1 | require("dotenv").config(); 2 | 3 | // Doc: https://github.com/release-it/release-it 4 | module.exports = { 5 | git: { 6 | commitMessage: "build: Release v${version}", 7 | requireUpstream: false, 8 | pushRepo: "upstream", // Push tags and commit to the remote `upstream` (fails if doesn't exist) 9 | requireBranch: "master", // Push commit to the branch `master` (fail if on other branch) 10 | requireCommits: true, // Require new commits since latest tag 11 | }, 12 | github: { 13 | release: true, 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /.solcover.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | silent: true, 3 | measureStatementCoverage: true, 4 | measureFunctionCoverage: true, 5 | skipFiles: ["interfaces", "test"], 6 | configureYulOptimizer: true, 7 | }; 8 | -------------------------------------------------------------------------------- /.solhint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solhint:recommended", 3 | "rules": { 4 | "code-complexity": ["warn", 7], 5 | "compiler-version": ["error", "^0.8.17"], 6 | "func-visibility": ["warn", { "ignoreConstructors": true }], 7 | "func-name-mixedcase": "off", 8 | "max-line-length": ["error", 120], 9 | "ordering": "warn", 10 | "reason-string": "off", 11 | "var-name-mixedcase": "off" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.solhintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | test 3 | demo.sol 4 | *.t.sol 5 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true 3 | } 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 LooksRare 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @looksrare/contracts-exchange-v2 2 | 3 | [![Tests](https://github.com/LooksRare/contracts-exchange-v2/actions/workflows/tests.yaml/badge.svg)](https://github.com/LooksRare/contracts-exchange-v2/actions/workflows/tests.yaml) 4 | [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) 5 | [![SDK](https://img.shields.io/badge/SDK-library-red)](https://github.com/LooksRare/sdk-v2) 6 | 7 | ## Description 8 | 9 | This project contains core smart contracts used for the LooksRare exchange protocol ("v2"). 10 | 11 | It also contains a peripheral contract (`OrderValidatorV2A`) that is used to verify the validity of (maker) orders. 12 | 13 | ## Installation 14 | 15 | ```shell 16 | # Yarn 17 | yarn add @looksrare/contracts-exchange-v2 18 | 19 | # NPM 20 | npm install @looksrare/contracts-exchange-v2 21 | ``` 22 | 23 | ## Deployments 24 | 25 | | Network | LooksRareProtocol | TransferManager | OrderV2AValidator | 26 | | :------- | :--------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------- | 27 | | Ethereum | [0x0000000000E655fAe4d56241588680F86E3b2377](https://etherscan.io/address/0x0000000000E655fAe4d56241588680F86E3b2377#code) | [0x000000000060C4Ca14CfC4325359062ace33Fe3D](https://etherscan.io/address/0x000000000060C4Ca14CfC4325359062ace33Fe3D#code) | [0x2a784a5b5C8AE0bd738FBc67E4C069dB4F4961B7](https://etherscan.io/address/0x2a784a5b5C8AE0bd738FBc67E4C069dB4F4961B7#code) | 28 | | Goerli | [0x35C2215F2FFe8917B06454eEEaba189877F200cf](https://goerli.etherscan.io/address/0x35C2215F2FFe8917B06454eEEaba189877F200cf#code) | [0xC20E0CeAD98abBBEb626B77efb8Dc1E5D781f90c](https://goerli.etherscan.io/address/0xC20E0CeAD98abBBEb626B77efb8Dc1E5D781f90c#code) | [0x7454Cc9AEB024bcE6A2CDC49ad4733B4D8215fb8](https://goerli.etherscan.io/address/0x7454Cc9AEB024bcE6A2CDC49ad4733B4D8215fb8#code) | 29 | | Sepolia | [0x34098cc15a8a48Da9d3f31CC0F63F01f9aa3D9F3](https://sepolia.etherscan.io/address/0x34098cc15a8a48Da9d3f31CC0F63F01f9aa3D9F3#code) | [0xb46f116ecBa8451E661189F4b2B63aC60a618092](https://sepolia.etherscan.io/address/0xb46f116ecBa8451E661189F4b2B63aC60a618092#code) | [0x0bc129E4c1f8D7b5583eAbAeb1F7468935B6ec0C](https://sepolia.etherscan.io/address/0x0bc129E4c1f8D7b5583eAbAeb1F7468935B6ec0C#code) | 30 | 31 | ## NPM package 32 | 33 | The NPM package contains the following: 34 | 35 | - Solidity smart contracts (_".sol"_) 36 | - ABIs (_".json"_) 37 | 38 | ABIs are also distributed in the `@looksrare/sdk-v2` package. 39 | 40 | ## Documentation 41 | 42 | The documentation for the exchange smart contracts will be available [here](https://docs.looksrare.org/developers/welcome). 43 | 44 | ## Architecture 45 | 46 | ![inheritance](./inheritance-contracts.svg) 47 | 48 | ## Setup 49 | 50 | - Run `yarn` 51 | - Install [Forge](https://book.getfoundry.sh/getting-started/installation.html) 52 | - Run `forge update` 53 | - Run `git submodule update` 54 | 55 | ## About this repo 56 | 57 | ### Structure 58 | 59 | It is a hybrid [Hardhat](https://hardhat.org/) repo that also requires [Foundry](https://book.getfoundry.sh/index.html) to run Solidity tests powered by the [ds-test library](https://github.com/dapphub/ds-test/). 60 | 61 | > To install Foundry, please follow the instructions [here](https://book.getfoundry.sh/getting-started/installation.html). 62 | 63 | ### Run tests 64 | 65 | - Solidity tests are included in the `foundry` folder in the `test` folder at the root of the repo. 66 | 67 | ### Example of Foundry/Forge commands 68 | 69 | ```shell 70 | forge build 71 | forge test 72 | forge test -vv 73 | forge tree 74 | ``` 75 | 76 | ### Example of shell commands 77 | 78 | ```shell 79 | npx eslint '**/*.{js,ts}' 80 | npx eslint '**/*.{js,ts}' --fix 81 | npx prettier '**/*.{json,sol,md}' --check 82 | npx prettier '**/*.{json,sol,md}' --write 83 | npx solhint 'contracts/**/*.sol' 84 | npx solhint 'contracts/**/*.sol' --fix 85 | ``` 86 | 87 | ### Coverage 88 | 89 | It is required to install lcov. 90 | 91 | ```shell 92 | brew install lcov 93 | ``` 94 | 95 | To run the coverage report, the below command can be executed. 96 | 97 | ``` 98 | forge coverage --report lcov 99 | LCOV_EXCLUDE=("test/*" "contracts/libraries/*" "contracts/helpers/ProtocolHelpers.sol") 100 | echo $LCOV_EXCLUDE | xargs lcov --output-file lcov-filtered.info --remove lcov.info 101 | genhtml lcov-filtered.info --output-directory out 102 | open out/index.html 103 | ``` 104 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security policy 2 | 3 | ## Supported versions 4 | 5 | | Version | Supported | 6 | | ------- | ------------------ | 7 | | 1.0.x | :white_check_mark: | 8 | 9 | ## Reporting a vulnerability 10 | 11 | Vulnerabilities can be reported using [Immunefi](https://immunefi.com/). The process is secure and vulnerabilities can be disclosed anonymously. 12 | 13 | To learn more, [please visit the Immunefi bug bounty page](https://immunefi.com/bounty/looksrare/). 14 | 15 | ## Current audits 16 | 17 | Third-party audit reports are available [here](https://docs.looksrare.org/about/audits). 18 | -------------------------------------------------------------------------------- /contracts/AffiliateManager.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | // LooksRare unopinionated libraries 5 | import {OwnableTwoSteps} from "@looksrare/contracts-libs/contracts/OwnableTwoSteps.sol"; 6 | 7 | // Interfaces 8 | import {IAffiliateManager} from "./interfaces/IAffiliateManager.sol"; 9 | 10 | // Constants 11 | import {ONE_HUNDRED_PERCENT_IN_BP} from "./constants/NumericConstants.sol"; 12 | 13 | /** 14 | * @title AffiliateManager 15 | * @notice This contract handles the management of affiliates for the LooksRare protocol. 16 | * @author LooksRare protocol team (👀,💎) 17 | */ 18 | contract AffiliateManager is IAffiliateManager, OwnableTwoSteps { 19 | /** 20 | * @notice Whether the affiliate program is active. 21 | */ 22 | bool public isAffiliateProgramActive; 23 | 24 | /** 25 | * @notice Address of the affiliate controller. 26 | */ 27 | address public affiliateController; 28 | 29 | /** 30 | * @notice It tracks the affiliate rate (in basis point) for a given affiliate address. 31 | * The basis point represents how much of the protocol fee will be shared to the affiliate. 32 | */ 33 | mapping(address => uint256) public affiliateRates; 34 | 35 | /** 36 | * @notice Constructor 37 | * @param _owner Owner address 38 | */ 39 | constructor(address _owner) OwnableTwoSteps(_owner) {} 40 | 41 | /** 42 | * @notice This function allows the affiliate controller to update the affiliate rate (in basis point). 43 | * @param affiliate Affiliate address 44 | * @param bp Rate (in basis point) to collect (e.g. 100 = 1%) per referred transaction 45 | */ 46 | function updateAffiliateRate(address affiliate, uint256 bp) external { 47 | if (msg.sender != affiliateController) { 48 | revert NotAffiliateController(); 49 | } 50 | 51 | if (bp > ONE_HUNDRED_PERCENT_IN_BP) { 52 | revert PercentageTooHigh(); 53 | } 54 | 55 | affiliateRates[affiliate] = bp; 56 | emit NewAffiliateRate(affiliate, bp); 57 | } 58 | 59 | /** 60 | * @notice This function allows the owner to update the affiliate controller address. 61 | * @param newAffiliateController New affiliate controller address 62 | * @dev Only callable by owner. 63 | */ 64 | function updateAffiliateController(address newAffiliateController) external onlyOwner { 65 | affiliateController = newAffiliateController; 66 | emit NewAffiliateController(newAffiliateController); 67 | } 68 | 69 | /** 70 | * @notice This function allows the owner to update the affiliate program status. 71 | * @param isActive Whether the affiliate program is active 72 | * @dev Only callable by owner. 73 | */ 74 | function updateAffiliateProgramStatus(bool isActive) external onlyOwner { 75 | isAffiliateProgramActive = isActive; 76 | emit NewAffiliateProgramStatus(isActive); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /contracts/BatchOrderTypehashRegistry.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | // Shared errors 5 | import {MerkleProofTooLarge} from "./errors/SharedErrors.sol"; 6 | 7 | /** 8 | * @title BatchOrderTypehashRegistry 9 | * @notice The contract generates the batch order hash that is used to compute the digest for signature verification. 10 | * @author LooksRare protocol team (👀,💎) 11 | */ 12 | contract BatchOrderTypehashRegistry { 13 | /** 14 | * @notice This function returns the hash of the concatenation of batch order type hash and merkle root. 15 | * @param root Merkle root 16 | * @param proofLength Merkle proof length 17 | * @return batchOrderHash The batch order hash 18 | */ 19 | function hashBatchOrder(bytes32 root, uint256 proofLength) public pure returns (bytes32 batchOrderHash) { 20 | batchOrderHash = keccak256(abi.encode(_getBatchOrderTypehash(proofLength), root)); 21 | } 22 | 23 | /** 24 | * @dev It looks like this for each height 25 | * height == 1: BatchOrder(Maker[2] tree)Maker(uint8 quoteType,uint256 globalNonce,uint256 subsetNonce,uint256 orderNonce,uint256 strategyId,uint8 collectionType,address collection,address currency,address signer,uint256 startTime,uint256 endTime,uint256 price,uint256[] itemIds,uint256[] amounts,bytes additionalParameters) 26 | * height == 2: BatchOrder(Maker[2][2] tree)Maker(uint8 quoteType,uint256 globalNonce,uint256 subsetNonce,uint256 orderNonce,uint256 strategyId,uint8 collectionType,address collection,address currency,address signer,uint256 startTime,uint256 endTime,uint256 price,uint256[] itemIds,uint256[] amounts,bytes additionalParameters) 27 | * height == n: BatchOrder(Maker[2]...[2] tree)Maker(uint8 quoteType,uint256 globalNonce,uint256 subsetNonce,uint256 orderNonce,uint256 strategyId,uint8 collectionType,address collection,address currency,address signer,uint256 startTime,uint256 endTime,uint256 price,uint256[] itemIds,uint256[] amounts,bytes additionalParameters) 28 | */ 29 | function _getBatchOrderTypehash(uint256 height) internal pure returns (bytes32 typehash) { 30 | if (height == 1) { 31 | typehash = hex"9661287f7a4aa4867db46a2453ee15bebac4e8fc25667a58718da658f15de643"; 32 | } else if (height == 2) { 33 | typehash = hex"a54ab330ea9e1dfccee2b86f3666989e7fbd479704416c757c8de8e820142a08"; 34 | } else if (height == 3) { 35 | typehash = hex"93390f5d45ede9dea305f16aec86b2472af4f823851637f1b7019ad0775cea49"; 36 | } else if (height == 4) { 37 | typehash = hex"9dda2c8358da895e43d574bb15954ce5727b22e923a2d8f28261f297bce42f0b"; 38 | } else if (height == 5) { 39 | typehash = hex"92dc717124e161262f9d10c7079e7d54dc51271893fba54aa4a0f270fecdcc98"; 40 | } else if (height == 6) { 41 | typehash = hex"ce02aee5a7a35d40d974463c4c6e5534954fb07a7e7bc966fee268a15337bfd8"; 42 | } else if (height == 7) { 43 | typehash = hex"f7a65efd167a18f7091b2bb929d687dd94503cf0a43620487055ed7d6b727559"; 44 | } else if (height == 8) { 45 | typehash = hex"def24acacad1318b664520f7c10e8bc6d1e7f6f6f7c8b031e70624ceb42266a6"; 46 | } else if (height == 9) { 47 | typehash = hex"4cb4080dc4e7bae88b4dc4307ad5117fa4f26195998a1b5f40368809d7f4c7f2"; 48 | } else if (height == 10) { 49 | typehash = hex"f8b1f864164d8d6e0b45f1399bd711223117a4ab0b057a9c2d7779e86a7c88db"; 50 | } else { 51 | revert MerkleProofTooLarge(height); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /contracts/CreatorFeeManagerWithRebates.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | // LooksRare unopinionated libraries 5 | import {IERC2981} from "@looksrare/contracts-libs/contracts/interfaces/generic/IERC2981.sol"; 6 | 7 | // Interfaces 8 | import {ICreatorFeeManager} from "./interfaces/ICreatorFeeManager.sol"; 9 | import {IRoyaltyFeeRegistry} from "./interfaces/IRoyaltyFeeRegistry.sol"; 10 | 11 | // Constants 12 | import {ONE_HUNDRED_PERCENT_IN_BP} from "./constants/NumericConstants.sol"; 13 | 14 | /** 15 | * @title CreatorFeeManagerWithRebates 16 | * @notice This contract returns the creator fee address and the creator rebate amount. 17 | * @author LooksRare protocol team (👀,💎) 18 | */ 19 | contract CreatorFeeManagerWithRebates is ICreatorFeeManager { 20 | /** 21 | * @notice Standard royalty fee (in basis point). 22 | */ 23 | uint256 public constant STANDARD_ROYALTY_FEE_BP = 50; 24 | 25 | /** 26 | * @notice Royalty fee registry interface. 27 | */ 28 | IRoyaltyFeeRegistry public immutable royaltyFeeRegistry; 29 | 30 | /** 31 | * @notice Constructor 32 | * @param _royaltyFeeRegistry Royalty fee registry address. 33 | */ 34 | constructor(address _royaltyFeeRegistry) { 35 | royaltyFeeRegistry = IRoyaltyFeeRegistry(_royaltyFeeRegistry); 36 | } 37 | 38 | /** 39 | * @inheritdoc ICreatorFeeManager 40 | */ 41 | function viewCreatorFeeInfo( 42 | address collection, 43 | uint256 price, 44 | uint256[] memory itemIds 45 | ) external view returns (address creator, uint256 creatorFeeAmount) { 46 | // Check if there is a royalty info in the system 47 | (creator, ) = royaltyFeeRegistry.royaltyInfo(collection, price); 48 | 49 | if (creator == address(0)) { 50 | if (IERC2981(collection).supportsInterface(IERC2981.royaltyInfo.selector)) { 51 | uint256 length = itemIds.length; 52 | 53 | for (uint256 i; i < length; ) { 54 | try IERC2981(collection).royaltyInfo(itemIds[i], price) returns ( 55 | address newCreator, 56 | uint256 /* newCreatorFeeAmount */ 57 | ) { 58 | if (i == 0) { 59 | creator = newCreator; 60 | 61 | unchecked { 62 | ++i; 63 | } 64 | continue; 65 | } 66 | 67 | if (newCreator != creator) { 68 | revert BundleEIP2981NotAllowed(collection); 69 | } 70 | } catch { 71 | // If creator address is not 0, that means there was at least 1 72 | // successful call. If all royaltyInfo calls fail, it should assume 73 | // 0 royalty. 74 | // If the first call reverts, even if creator is address(0), subsequent 75 | // successful calls will still revert above with BundleEIP2981NotAllowed 76 | // because newCreator will be different from creator. 77 | if (creator != address(0)) { 78 | revert BundleEIP2981NotAllowed(collection); 79 | } 80 | } 81 | 82 | unchecked { 83 | ++i; 84 | } 85 | } 86 | } 87 | } 88 | 89 | // A fixed royalty fee is applied 90 | if (creator != address(0)) { 91 | creatorFeeAmount = (STANDARD_ROYALTY_FEE_BP * price) / ONE_HUNDRED_PERCENT_IN_BP; 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /contracts/CreatorFeeManagerWithRoyalties.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | // LooksRare unopinionated libraries 5 | import {IERC2981} from "@looksrare/contracts-libs/contracts/interfaces/generic/IERC2981.sol"; 6 | 7 | // Interfaces 8 | import {ICreatorFeeManager} from "./interfaces/ICreatorFeeManager.sol"; 9 | import {IRoyaltyFeeRegistry} from "./interfaces/IRoyaltyFeeRegistry.sol"; 10 | 11 | /** 12 | * @title CreatorFeeManagerWithRoyalties 13 | * @notice This contract returns the creator fee address and the creator fee amount. 14 | * @author LooksRare protocol team (👀,💎) 15 | */ 16 | contract CreatorFeeManagerWithRoyalties is ICreatorFeeManager { 17 | /** 18 | * @notice Royalty fee registry interface. 19 | */ 20 | IRoyaltyFeeRegistry public immutable royaltyFeeRegistry; 21 | 22 | /** 23 | * @notice Constructor 24 | * @param _royaltyFeeRegistry Royalty fee registry address. 25 | */ 26 | constructor(address _royaltyFeeRegistry) { 27 | royaltyFeeRegistry = IRoyaltyFeeRegistry(_royaltyFeeRegistry); 28 | } 29 | 30 | /** 31 | * @inheritdoc ICreatorFeeManager 32 | * @dev There are two on-chain sources for the royalty fee to distribute. 33 | * 1. RoyaltyFeeRegistry: It is an on-chain registry where creator fee is defined 34 | for all items of a collection. 35 | * 2. ERC2981: The NFT Royalty Standard where royalty fee is defined at a itemId level in a collection. 36 | * The on-chain logic looks up the registry first. If it does not find anything, 37 | * it checks if a collection is ERC2981. If so, it fetches the proper royalty information for the itemId. 38 | * For a bundle that contains multiple itemIds (for a collection using ERC2981), if the royalty fee/recipient 39 | * differ among the itemIds part of the bundle, the trade reverts. 40 | * This contract DOES NOT enforce any restriction for extremely high creator fee, 41 | * nor verifies the creator fee fetched is inferior to the total price. 42 | * If any contract relies on it to build an on-chain royalty logic, 43 | * it should implement protection against: 44 | * (1) high royalties 45 | * (2) potential unexpected royalty changes that can occur after the creation of the order. 46 | */ 47 | function viewCreatorFeeInfo( 48 | address collection, 49 | uint256 price, 50 | uint256[] memory itemIds 51 | ) external view returns (address creator, uint256 creatorFeeAmount) { 52 | // Check if there is a royalty info in the system 53 | (creator, creatorFeeAmount) = royaltyFeeRegistry.royaltyInfo(collection, price); 54 | 55 | if (creator == address(0)) { 56 | if (IERC2981(collection).supportsInterface(IERC2981.royaltyInfo.selector)) { 57 | uint256 length = itemIds.length; 58 | 59 | for (uint256 i; i < length; ) { 60 | try IERC2981(collection).royaltyInfo(itemIds[i], price) returns ( 61 | address newCreator, 62 | uint256 newCreatorFeeAmount 63 | ) { 64 | if (i == 0) { 65 | creator = newCreator; 66 | creatorFeeAmount = newCreatorFeeAmount; 67 | 68 | unchecked { 69 | ++i; 70 | } 71 | continue; 72 | } 73 | 74 | if (newCreator != creator || newCreatorFeeAmount != creatorFeeAmount) { 75 | revert BundleEIP2981NotAllowed(collection); 76 | } 77 | } catch { 78 | // If creator address is not 0, that means there was at least 1 79 | // successful call. If all royaltyInfo calls fail, it should assume 80 | // 0 royalty. 81 | // If the first call reverts, even if creator is address(0), subsequent 82 | // successful calls will still revert above with BundleEIP2981NotAllowed 83 | // because newCreator/newCreatorFeeAmount will be different from creator/creatorFeeAmount. 84 | if (creator != address(0)) { 85 | revert BundleEIP2981NotAllowed(collection); 86 | } 87 | } 88 | 89 | unchecked { 90 | ++i; 91 | } 92 | } 93 | } 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /contracts/CurrencyManager.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | // Interfaces 5 | import {ICurrencyManager} from "./interfaces/ICurrencyManager.sol"; 6 | 7 | // Dependencies 8 | import {AffiliateManager} from "./AffiliateManager.sol"; 9 | 10 | /** 11 | * @title CurrencyManager 12 | * @notice This contract manages the list of valid fungible currencies. 13 | * @author LooksRare protocol team (👀,💎) 14 | */ 15 | contract CurrencyManager is ICurrencyManager, AffiliateManager { 16 | /** 17 | * @notice It checks whether the currency is allowed for transacting. 18 | */ 19 | mapping(address => bool) public isCurrencyAllowed; 20 | 21 | /** 22 | * @notice Constructor 23 | * @param _owner Owner address 24 | */ 25 | constructor(address _owner) AffiliateManager(_owner) {} 26 | 27 | /** 28 | * @notice This function allows the owner to update the status of a currency. 29 | * @param currency Currency address (address(0) for ETH) 30 | * @param isAllowed Whether the currency should be allowed for trading 31 | * @dev Only callable by owner. 32 | */ 33 | function updateCurrencyStatus(address currency, bool isAllowed) external onlyOwner { 34 | isCurrencyAllowed[currency] = isAllowed; 35 | emit CurrencyStatusUpdated(currency, isAllowed); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /contracts/InheritedStrategy.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | // Libraries 5 | import {OrderStructs} from "./libraries/OrderStructs.sol"; 6 | 7 | // Shared errors 8 | import {OrderInvalid} from "./errors/SharedErrors.sol"; 9 | 10 | // Assembly 11 | import {OrderInvalid_error_selector, OrderInvalid_error_length, Error_selector_offset, OneWord} from "./constants/AssemblyConstants.sol"; 12 | 13 | /** 14 | * @title InheritedStrategy 15 | * @notice This contract handles the verification of parameters for standard transactions. 16 | * It does not verify the taker struct's itemIds and amounts array as well as 17 | * minPrice (taker ask) / maxPrice (taker bid) because before the taker executes the 18 | * transaction and the maker itemIds/amounts/price should have already been confirmed off-chain. 19 | * @dev A standard transaction (bid or ask) is mapped to strategyId = 0. 20 | * @author LooksRare protocol team (👀,💎) 21 | */ 22 | contract InheritedStrategy { 23 | /** 24 | * @notice This function is internal and is used to validate the parameters for a standard sale strategy 25 | * when the standard transaction is initiated by a taker bid. 26 | * @param amounts Array of amounts 27 | * @param itemIds Array of item ids 28 | */ 29 | function _verifyItemIdsAndAmountsEqualLengthsAndValidAmounts( 30 | uint256[] calldata amounts, 31 | uint256[] calldata itemIds 32 | ) internal pure { 33 | assembly { 34 | let end 35 | { 36 | /* 37 | * @dev If A == B, then A XOR B == 0. 38 | * 39 | * if (amountsLength == 0 || amountsLength != itemIdsLength) { 40 | * revert OrderInvalid(); 41 | * } 42 | */ 43 | let amountsLength := amounts.length 44 | let itemIdsLength := itemIds.length 45 | 46 | if or(iszero(amountsLength), xor(amountsLength, itemIdsLength)) { 47 | mstore(0x00, OrderInvalid_error_selector) 48 | revert(Error_selector_offset, OrderInvalid_error_length) 49 | } 50 | 51 | /** 52 | * @dev Shifting left 5 times is equivalent to amountsLength * 32 bytes 53 | */ 54 | end := shl(5, amountsLength) 55 | } 56 | 57 | let amountsOffset := amounts.offset 58 | 59 | for { 60 | 61 | } end { 62 | 63 | } { 64 | /** 65 | * @dev Starting from the end of the array minus 32 bytes to load the last item, 66 | * ending with `end` equal to 0 to load the first item 67 | * 68 | * uint256 end = amountsLength; 69 | * 70 | * for (uint256 i = end - 1; i >= 0; i--) { 71 | * uint256 amount = amounts[i]; 72 | * if (amount == 0) { 73 | * revert OrderInvalid(); 74 | * } 75 | * } 76 | */ 77 | end := sub(end, OneWord) 78 | 79 | let amount := calldataload(add(amountsOffset, end)) 80 | 81 | if iszero(amount) { 82 | mstore(0x00, OrderInvalid_error_selector) 83 | revert(Error_selector_offset, OrderInvalid_error_length) 84 | } 85 | } 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /contracts/NonceManager.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | // Interfaces and errors 5 | import {INonceManager} from "./interfaces/INonceManager.sol"; 6 | import {LengthsInvalid} from "./errors/SharedErrors.sol"; 7 | 8 | /** 9 | * @title NonceManager 10 | * @notice This contract handles the nonce logic that is used for invalidating maker orders that exist off-chain. 11 | * The nonce logic revolves around three parts at the user level: 12 | * - order nonce (orders sharing an order nonce are conditional, OCO-like) 13 | * - subset (orders can be grouped under a same subset) 14 | * - bid/ask (all orders can be executed only if the bid/ask nonce matches the user's one on-chain) 15 | * Only the order nonce is invalidated at the time of the execution of a maker order that contains it. 16 | * @author LooksRare protocol team (👀,💎) 17 | */ 18 | contract NonceManager is INonceManager { 19 | /** 20 | * @notice Magic value nonce returned if executed (or cancelled). 21 | */ 22 | bytes32 public constant MAGIC_VALUE_ORDER_NONCE_EXECUTED = keccak256("ORDER_NONCE_EXECUTED"); 23 | 24 | /** 25 | * @notice This tracks the bid and ask nonces for a user address. 26 | */ 27 | mapping(address => UserBidAskNonces) public userBidAskNonces; 28 | 29 | /** 30 | * @notice This checks whether the order nonce for a user was executed or cancelled. 31 | */ 32 | mapping(address => mapping(uint256 => bytes32)) public userOrderNonce; 33 | 34 | /** 35 | * @notice This checks whether the subset nonce for a user was cancelled. 36 | */ 37 | mapping(address => mapping(uint256 => bool)) public userSubsetNonce; 38 | 39 | /** 40 | * @notice This function allows a user to cancel an array of order nonces. 41 | * @param orderNonces Array of order nonces 42 | * @dev It does not check the status of the nonces to save gas 43 | * and to prevent revertion if one of the orders is filled in the same 44 | * block. 45 | */ 46 | function cancelOrderNonces(uint256[] calldata orderNonces) external { 47 | uint256 length = orderNonces.length; 48 | if (length == 0) { 49 | revert LengthsInvalid(); 50 | } 51 | 52 | for (uint256 i; i < length; ) { 53 | userOrderNonce[msg.sender][orderNonces[i]] = MAGIC_VALUE_ORDER_NONCE_EXECUTED; 54 | unchecked { 55 | ++i; 56 | } 57 | } 58 | 59 | emit OrderNoncesCancelled(msg.sender, orderNonces); 60 | } 61 | 62 | /** 63 | * @notice This function allows a user to cancel an array of subset nonces. 64 | * @param subsetNonces Array of subset nonces 65 | * @dev It does not check the status of the nonces to save gas. 66 | */ 67 | function cancelSubsetNonces(uint256[] calldata subsetNonces) external { 68 | uint256 length = subsetNonces.length; 69 | 70 | if (length == 0) { 71 | revert LengthsInvalid(); 72 | } 73 | 74 | for (uint256 i; i < length; ) { 75 | userSubsetNonce[msg.sender][subsetNonces[i]] = true; 76 | unchecked { 77 | ++i; 78 | } 79 | } 80 | 81 | emit SubsetNoncesCancelled(msg.sender, subsetNonces); 82 | } 83 | 84 | /** 85 | * @notice This function increments a user's bid/ask nonces. 86 | * @param bid Whether to increment the user bid nonce 87 | * @param ask Whether to increment the user ask nonce 88 | * @dev The logic for computing the quasi-random number is inspired by Seaport v1.2. 89 | * The pseudo-randomness allows non-deterministic computation of the next ask/bid nonce. 90 | * A deterministic increment would make the cancel-all process non-effective in certain cases 91 | * (orders signed with a greater ask/bid nonce). 92 | * The same quasi-random number is used for incrementing both the bid and ask nonces if both values 93 | * are incremented in the same transaction. 94 | * If this function is used twice in the same block, it will return the same quasiRandomNumber 95 | * but this will not impact the overall business logic. 96 | */ 97 | function incrementBidAskNonces(bool bid, bool ask) external { 98 | // Use second half of the previous block hash as a quasi-random number 99 | uint256 quasiRandomNumber = uint256(blockhash(block.number - 1) >> 128); 100 | uint256 newBidNonce = userBidAskNonces[msg.sender].bidNonce; 101 | uint256 newAskNonce = userBidAskNonces[msg.sender].askNonce; 102 | 103 | if (bid) { 104 | newBidNonce += quasiRandomNumber; 105 | userBidAskNonces[msg.sender].bidNonce = newBidNonce; 106 | } 107 | 108 | if (ask) { 109 | newAskNonce += quasiRandomNumber; 110 | userBidAskNonces[msg.sender].askNonce = newAskNonce; 111 | } 112 | 113 | emit NewBidAskNonces(msg.sender, newBidNonce, newAskNonce); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /contracts/ProtocolFeeRecipient.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | // LooksRare unopinionated libraries 5 | import {LowLevelERC20Transfer} from "@looksrare/contracts-libs/contracts/lowLevelCallers/LowLevelERC20Transfer.sol"; 6 | import {IWETH} from "@looksrare/contracts-libs/contracts/interfaces/generic/IWETH.sol"; 7 | import {IERC20} from "@looksrare/contracts-libs/contracts/interfaces/generic/IERC20.sol"; 8 | 9 | /** 10 | * @title ProtocolFeeRecipient 11 | * @notice This contract is used to receive protocol fees and transfer them to the fee sharing setter. 12 | * Fee sharing setter cannot receive ETH directly, so we need to use this contract as a middleman 13 | * to convert ETH into WETH before sending it. 14 | * @author LooksRare protocol team (👀,💎) 15 | */ 16 | contract ProtocolFeeRecipient is LowLevelERC20Transfer { 17 | address public immutable FEE_SHARING_SETTER; 18 | IWETH public immutable WETH; 19 | 20 | error NothingToTransfer(); 21 | 22 | constructor(address _feeSharingSetter, address _weth) { 23 | FEE_SHARING_SETTER = _feeSharingSetter; 24 | WETH = IWETH(_weth); 25 | } 26 | 27 | function transferETH() external { 28 | uint256 ethBalance = address(this).balance; 29 | 30 | if (ethBalance != 0) { 31 | WETH.deposit{value: ethBalance}(); 32 | } 33 | 34 | uint256 wethBalance = IERC20(address(WETH)).balanceOf(address(this)); 35 | 36 | if (wethBalance == 0) { 37 | revert NothingToTransfer(); 38 | } 39 | _executeERC20DirectTransfer(address(WETH), FEE_SHARING_SETTER, wethBalance); 40 | } 41 | 42 | /** 43 | * @param currency ERC20 currency address 44 | */ 45 | function transferERC20(address currency) external { 46 | uint256 balance = IERC20(currency).balanceOf(address(this)); 47 | if (balance == 0) { 48 | revert NothingToTransfer(); 49 | } 50 | _executeERC20DirectTransfer(currency, FEE_SHARING_SETTER, balance); 51 | } 52 | 53 | receive() external payable {} 54 | } 55 | -------------------------------------------------------------------------------- /contracts/StrategyManager.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | // LooksRare unopinionated libraries 5 | import {CurrencyManager} from "./CurrencyManager.sol"; 6 | 7 | // Interfaces 8 | import {IStrategy} from "./interfaces/IStrategy.sol"; 9 | import {IStrategyManager} from "./interfaces/IStrategyManager.sol"; 10 | 11 | /** 12 | * @title StrategyManager 13 | * @notice This contract handles the addition and the update of execution strategies. 14 | * @author LooksRare protocol team (👀,💎) 15 | */ 16 | contract StrategyManager is IStrategyManager, CurrencyManager { 17 | /** 18 | * @notice This variable keeps the count of how many strategies exist. 19 | * It includes strategies that have been removed. 20 | */ 21 | uint256 private _countStrategies = 1; 22 | 23 | /** 24 | * @notice This returns the strategy information for a strategy id. 25 | */ 26 | mapping(uint256 => Strategy) public strategyInfo; 27 | 28 | /** 29 | * @notice Constructor 30 | * @param _owner Owner address 31 | */ 32 | constructor(address _owner) CurrencyManager(_owner) { 33 | strategyInfo[0] = Strategy({ 34 | isActive: true, 35 | standardProtocolFeeBp: 50, 36 | minTotalFeeBp: 50, 37 | maxProtocolFeeBp: 200, 38 | selector: bytes4(0), 39 | isMakerBid: false, 40 | implementation: address(0) 41 | }); 42 | 43 | emit NewStrategy(0, 50, 50, 200, bytes4(0), false, address(0)); 44 | } 45 | 46 | /** 47 | * @notice This function allows the owner to add a new execution strategy to the protocol. 48 | * @param standardProtocolFeeBp Standard protocol fee (in basis point) 49 | * @param minTotalFeeBp Minimum total fee (in basis point) 50 | * @param maxProtocolFeeBp Maximum protocol fee (in basis point) 51 | * @param selector Function selector for the strategy 52 | * @param isMakerBid Whether the function selector is for maker bid 53 | * @param implementation Implementation address 54 | * @dev Strategies have an id that is incremental. 55 | * Only callable by owner. 56 | */ 57 | function addStrategy( 58 | uint16 standardProtocolFeeBp, 59 | uint16 minTotalFeeBp, 60 | uint16 maxProtocolFeeBp, 61 | bytes4 selector, 62 | bool isMakerBid, 63 | address implementation 64 | ) external onlyOwner { 65 | if (minTotalFeeBp > maxProtocolFeeBp || standardProtocolFeeBp > minTotalFeeBp || maxProtocolFeeBp > 500) { 66 | revert StrategyProtocolFeeTooHigh(); 67 | } 68 | 69 | if (selector == bytes4(0)) { 70 | revert StrategyHasNoSelector(); 71 | } 72 | 73 | if (!IStrategy(implementation).isLooksRareV2Strategy()) { 74 | revert NotV2Strategy(); 75 | } 76 | 77 | strategyInfo[_countStrategies] = Strategy({ 78 | isActive: true, 79 | standardProtocolFeeBp: standardProtocolFeeBp, 80 | minTotalFeeBp: minTotalFeeBp, 81 | maxProtocolFeeBp: maxProtocolFeeBp, 82 | selector: selector, 83 | isMakerBid: isMakerBid, 84 | implementation: implementation 85 | }); 86 | 87 | emit NewStrategy( 88 | _countStrategies++, 89 | standardProtocolFeeBp, 90 | minTotalFeeBp, 91 | maxProtocolFeeBp, 92 | selector, 93 | isMakerBid, 94 | implementation 95 | ); 96 | } 97 | 98 | /** 99 | * @notice This function allows the owner to update parameters for an existing execution strategy. 100 | * @param strategyId Strategy id 101 | * @param isActive Whether the strategy must be active 102 | * @param newStandardProtocolFee New standard protocol fee (in basis point) 103 | * @param newMinTotalFee New minimum total fee (in basis point) 104 | * @dev Only callable by owner. 105 | */ 106 | function updateStrategy( 107 | uint256 strategyId, 108 | bool isActive, 109 | uint16 newStandardProtocolFee, 110 | uint16 newMinTotalFee 111 | ) external onlyOwner { 112 | if (strategyId >= _countStrategies) { 113 | revert StrategyNotUsed(); 114 | } 115 | 116 | if (newMinTotalFee > strategyInfo[strategyId].maxProtocolFeeBp || newStandardProtocolFee > newMinTotalFee) { 117 | revert StrategyProtocolFeeTooHigh(); 118 | } 119 | 120 | strategyInfo[strategyId].isActive = isActive; 121 | strategyInfo[strategyId].standardProtocolFeeBp = newStandardProtocolFee; 122 | strategyInfo[strategyId].minTotalFeeBp = newMinTotalFee; 123 | 124 | emit StrategyUpdated(strategyId, isActive, newStandardProtocolFee, newMinTotalFee); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /contracts/TransferSelectorNFT.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | // Direct dependencies 5 | import {PackableReentrancyGuard} from "@looksrare/contracts-libs/contracts/PackableReentrancyGuard.sol"; 6 | import {ExecutionManager} from "./ExecutionManager.sol"; 7 | import {TransferManager} from "./TransferManager.sol"; 8 | 9 | // Libraries 10 | import {OrderStructs} from "./libraries/OrderStructs.sol"; 11 | 12 | // Enums 13 | import {CollectionType} from "./enums/CollectionType.sol"; 14 | 15 | /** 16 | * @title TransferSelectorNFT 17 | * @notice This contract handles the logic for transferring non-fungible items. 18 | * @author LooksRare protocol team (👀,💎) 19 | */ 20 | contract TransferSelectorNFT is ExecutionManager, PackableReentrancyGuard { 21 | /** 22 | * @notice Transfer manager for ERC721 and ERC1155. 23 | */ 24 | TransferManager public immutable transferManager; 25 | 26 | /** 27 | * @notice Constructor 28 | * @param _owner Owner address 29 | * @param _protocolFeeRecipient Protocol fee recipient address 30 | * @param _transferManager Address of the transfer manager for ERC721/ERC1155 31 | */ 32 | constructor( 33 | address _owner, 34 | address _protocolFeeRecipient, 35 | address _transferManager 36 | ) ExecutionManager(_owner, _protocolFeeRecipient) { 37 | transferManager = TransferManager(_transferManager); 38 | } 39 | 40 | /** 41 | * @notice This function is internal and used to transfer non-fungible tokens. 42 | * @param collection Collection address 43 | * @param collectionType Collection type (e.g. 0 = ERC721, 1 = ERC1155) 44 | * @param sender Sender address 45 | * @param recipient Recipient address 46 | * @param itemIds Array of itemIds 47 | * @param amounts Array of amounts 48 | */ 49 | function _transferNFT( 50 | address collection, 51 | CollectionType collectionType, 52 | address sender, 53 | address recipient, 54 | uint256[] memory itemIds, 55 | uint256[] memory amounts 56 | ) internal { 57 | if (collectionType == CollectionType.ERC721) { 58 | transferManager.transferItemsERC721(collection, sender, recipient, itemIds, amounts); 59 | } else if (collectionType == CollectionType.ERC1155) { 60 | transferManager.transferItemsERC1155(collection, sender, recipient, itemIds, amounts); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /contracts/constants/AssemblyConstants.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | /* 5 | * @dev error OrderInvalid() 6 | * Memory layout: 7 | * - 0x00: Left-padded selector (data begins at 0x1c) 8 | * Revert buffer is memory[0x1c:0x20] 9 | */ 10 | uint256 constant OrderInvalid_error_selector = 0x2e0c0f71; 11 | uint256 constant OrderInvalid_error_length = 0x04; 12 | 13 | /* 14 | * @dev error CurrencyInvalid() 15 | * Memory layout: 16 | * - 0x00: Left-padded selector (data begins at 0x1c) 17 | * Revert buffer is memory[0x1c:0x20] 18 | */ 19 | uint256 constant CurrencyInvalid_error_selector = 0x4f795487; 20 | uint256 constant CurrencyInvalid_error_length = 0x04; 21 | 22 | /* 23 | * @dev error OutsideOfTimeRange() 24 | * Memory layout: 25 | * - 0x00: Left-padded selector (data begins at 0x1c) 26 | * Revert buffer is memory[0x1c:0x20] 27 | */ 28 | uint256 constant OutsideOfTimeRange_error_selector = 0x7476320f; 29 | uint256 constant OutsideOfTimeRange_error_length = 0x04; 30 | 31 | /* 32 | * @dev error NoSelectorForStrategy() 33 | * Memory layout: 34 | * - 0x00: Left-padded selector (data begins at 0x1c) 35 | * Revert buffer is memory[0x1c:0x20] 36 | */ 37 | uint256 constant NoSelectorForStrategy_error_selector = 0xab984846; 38 | uint256 constant NoSelectorForStrategy_error_length = 0x04; 39 | 40 | uint256 constant Error_selector_offset = 0x1c; 41 | 42 | uint256 constant OneWord = 0x20; 43 | -------------------------------------------------------------------------------- /contracts/constants/NumericConstants.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | /** 5 | * @dev 100% represented in basis point is 10_000. 6 | */ 7 | uint256 constant ONE_HUNDRED_PERCENT_IN_BP = 10_000; 8 | 9 | /** 10 | * @dev The maximum length of a proof for a batch order is 10. 11 | * The maximum merkle tree that can used for signing has a height of 12 | * 2**10 = 1_024. 13 | */ 14 | uint256 constant MAX_CALLDATA_PROOF_LENGTH = 10; 15 | -------------------------------------------------------------------------------- /contracts/enums/CollectionType.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | /** 5 | * @notice CollectionType is used in OrderStructs.Maker's collectionType to determine the collection type being traded. 6 | */ 7 | enum CollectionType { 8 | ERC721, 9 | ERC1155 10 | } 11 | -------------------------------------------------------------------------------- /contracts/enums/QuoteType.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | /** 5 | * @notice QuoteType is used in OrderStructs.Maker's quoteType to determine whether the maker order is a bid or an ask. 6 | */ 7 | enum QuoteType { 8 | Bid, 9 | Ask 10 | } 11 | -------------------------------------------------------------------------------- /contracts/errors/ChainlinkErrors.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | /** 5 | * @notice It is returned if the Chainlink price is invalid (e.g. negative). 6 | */ 7 | error ChainlinkPriceInvalid(); 8 | 9 | /** 10 | * @notice It is returned if the decimals from the NFT floor price feed is invalid. 11 | * Chainlink price feeds are expected to have 18 decimals. 12 | * @dev It can only be returned for owner operations. 13 | */ 14 | error DecimalsInvalid(); 15 | 16 | /** 17 | * @notice It is returned if the fixed discount for a maker bid is greater than floor price. 18 | */ 19 | error DiscountGreaterThanFloorPrice(); 20 | 21 | /** 22 | * @notice It is returned if the latency tolerance is set too high (i.e. greater than 3,600 sec). 23 | */ 24 | error LatencyToleranceTooHigh(); 25 | 26 | /** 27 | * @notice It is returned if the price feed for a collection is already set. 28 | * @dev It can only be returned for owner operations. 29 | */ 30 | error PriceFeedAlreadySet(); 31 | 32 | /** 33 | * @notice It is returned when the price feed is not available. 34 | */ 35 | error PriceFeedNotAvailable(); 36 | 37 | /** 38 | * @notice It is returned if the current block time relative to the latest price's update time 39 | * is greater than the latency tolerance. 40 | */ 41 | error PriceNotRecentEnough(); 42 | -------------------------------------------------------------------------------- /contracts/errors/ReservoirErrors.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | /** 5 | * @notice It is returned if the itemId is reported as flagged. 6 | */ 7 | error ItemIdFlagged(address collection, uint256 itemId); 8 | 9 | /** 10 | * @notice It is returned if the item was transferred too recently. 11 | */ 12 | error ItemTransferredTooRecently(address collection, uint256 itemId); 13 | 14 | /** 15 | * @notice It is returned if the itemId was never transferred. 16 | * @dev It indicates that the signature was generated with incorrect data. 17 | */ 18 | error LastTransferTimeInvalid(); 19 | 20 | /** 21 | * @notice It is returned if the recovered message id is not matching the expected message id. 22 | * @dev For instance, it is emitted if the itemId is not the same itemId from the message. 23 | */ 24 | error MessageIdInvalid(); 25 | 26 | /** 27 | * @notice It is returned if the signature from the Reservoir's offchain oracle has expired. 28 | */ 29 | error SignatureExpired(); 30 | 31 | /** 32 | * @notice It is returned if the transfer cooldown period specified in the the maker bid order is greater 33 | * than the limit that exists at the strategy level. 34 | */ 35 | error TransferCooldownPeriodTooHigh(); 36 | -------------------------------------------------------------------------------- /contracts/errors/SharedErrors.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | /** 5 | * @notice It is returned if the amount is invalid. 6 | * For ERC721, any number that is not 1. For ERC1155, if amount is 0. 7 | */ 8 | error AmountInvalid(); 9 | 10 | /** 11 | * @notice It is returned if the ask price is too high for the bid user. 12 | */ 13 | error AskTooHigh(); 14 | 15 | /** 16 | * @notice It is returned if the bid price is too low for the ask user. 17 | */ 18 | error BidTooLow(); 19 | 20 | /** 21 | * @notice It is returned if the function cannot be called by the sender. 22 | */ 23 | error CallerInvalid(); 24 | 25 | /** 26 | * @notice It is returned if the currency is invalid. 27 | */ 28 | error CurrencyInvalid(); 29 | 30 | /** 31 | * @notice The function selector is invalid for this strategy implementation. 32 | */ 33 | error FunctionSelectorInvalid(); 34 | 35 | /** 36 | * @notice It is returned if there is either a mismatch or an error in the length of the array(s). 37 | */ 38 | error LengthsInvalid(); 39 | 40 | /** 41 | * @notice It is returned if the merkle proof provided is invalid. 42 | */ 43 | error MerkleProofInvalid(); 44 | 45 | /** 46 | * @notice It is returned if the length of the merkle proof provided is greater than tolerated. 47 | * @param length Proof length 48 | */ 49 | error MerkleProofTooLarge(uint256 length); 50 | 51 | /** 52 | * @notice It is returned if the order is permanently invalid. 53 | * There may be an issue with the order formatting. 54 | */ 55 | error OrderInvalid(); 56 | 57 | /** 58 | * @notice It is returned if the maker quote type is invalid. 59 | */ 60 | error QuoteTypeInvalid(); 61 | -------------------------------------------------------------------------------- /contracts/executionStrategies/BaseStrategy.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | // Interfaces 5 | import {IStrategy} from "../interfaces/IStrategy.sol"; 6 | 7 | // Assembly constants 8 | import {OrderInvalid_error_selector} from "../constants/AssemblyConstants.sol"; 9 | 10 | // Libraries 11 | import {OrderStructs} from "../libraries/OrderStructs.sol"; 12 | 13 | // Enums 14 | import {CollectionType} from "../enums/CollectionType.sol"; 15 | 16 | /** 17 | * @title BaseStrategy 18 | * @author LooksRare protocol team (👀,💎) 19 | */ 20 | abstract contract BaseStrategy is IStrategy { 21 | /** 22 | * @inheritdoc IStrategy 23 | */ 24 | function isLooksRareV2Strategy() external pure override returns (bool) { 25 | return true; 26 | } 27 | 28 | /** 29 | * @dev This is equivalent to 30 | * if (amount == 0 || (amount != 1 && collectionType == 0)) { 31 | * return (0, OrderInvalid.selector); 32 | * } 33 | * @dev OrderInvalid_error_selector is a left-padded 4 bytes. If the error selector is returned 34 | * instead of reverting, the error selector needs to be right-padded by 35 | * 28 bytes. Therefore it needs to be left shifted by 28 x 8 = 224 bits. 36 | */ 37 | function _validateAmountNoRevert(uint256 amount, CollectionType collectionType) internal pure { 38 | assembly { 39 | if or(iszero(amount), and(xor(amount, 1), iszero(collectionType))) { 40 | mstore(0x00, 0x00) 41 | mstore(0x20, shl(224, OrderInvalid_error_selector)) 42 | return(0, 0x40) 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /contracts/executionStrategies/Chainlink/BaseStrategyChainlinkMultiplePriceFeeds.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | // Chainlink aggregator interface 5 | import {AggregatorV3Interface} from "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; 6 | 7 | // Dependencies 8 | import {BaseStrategyChainlinkPriceLatency} from "./BaseStrategyChainlinkPriceLatency.sol"; 9 | 10 | // Chainlink errors 11 | import {PriceFeedAlreadySet, DecimalsInvalid} from "../../errors/ChainlinkErrors.sol"; 12 | 13 | /** 14 | * @title BaseStrategyChainlinkMultiplePriceFeeds 15 | * @notice This contract allows a strategy to store Chainlink price feeds for price retrieval. 16 | * @author LooksRare protocol team (👀,💎) 17 | */ 18 | contract BaseStrategyChainlinkMultiplePriceFeeds is BaseStrategyChainlinkPriceLatency { 19 | /** 20 | * @notice This maps the collection address to a Chainlink price feed address. 21 | * All prices returned from the price feeds must have 18 decimals. 22 | */ 23 | mapping(address => address) public priceFeeds; 24 | 25 | /** 26 | * @notice It is emitted when a collection's price feed address is added. 27 | * @param collection NFT collection address 28 | * @param priceFeed Chainlink price feed address 29 | */ 30 | event PriceFeedUpdated(address indexed collection, address indexed priceFeed); 31 | 32 | /** 33 | * @notice Constructor 34 | * @param _owner Owner address 35 | */ 36 | constructor(address _owner) BaseStrategyChainlinkPriceLatency(_owner, 86_400) {} 37 | 38 | /** 39 | * @notice This function allows the owner to set an NFT collection's Chainlink price feed address. 40 | * @dev Only callable by owner. 41 | * Once the price feed is set for a collection, it is not possible to adjust it after. 42 | * @param _collection NFT collection address 43 | * @param _priceFeed Chainlink price feed address 44 | */ 45 | function setPriceFeed(address _collection, address _priceFeed) external onlyOwner { 46 | if (priceFeeds[_collection] != address(0)) { 47 | revert PriceFeedAlreadySet(); 48 | } 49 | 50 | if (AggregatorV3Interface(_priceFeed).decimals() != 18) { 51 | revert DecimalsInvalid(); 52 | } 53 | 54 | priceFeeds[_collection] = _priceFeed; 55 | 56 | emit PriceFeedUpdated(_collection, _priceFeed); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /contracts/executionStrategies/Chainlink/BaseStrategyChainlinkPriceLatency.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | // LooksRare unopinionated libraries 5 | import {OwnableTwoSteps} from "@looksrare/contracts-libs/contracts/OwnableTwoSteps.sol"; 6 | 7 | /** 8 | * @title BaseStrategyChainlinkPriceLatency 9 | * @notice This contract allows the owner to define the maximum acceptable Chainlink price latency. 10 | * @author LooksRare protocol team (👀,💎) 11 | */ 12 | contract BaseStrategyChainlinkPriceLatency is OwnableTwoSteps { 13 | /** 14 | * @notice Maximum latency accepted after which 15 | * the execution strategy rejects the retrieved price. 16 | * 17 | * For ETH, it cannot be higher than 3,600 as Chainlink will at least update the 18 | * price every 3,600 seconds, provided ETH's price does not deviate more than 0.5%. 19 | * 20 | * For NFTs, it cannot be higher than 86,400 as Chainlink will at least update the 21 | * price every 86,400 seconds, provided ETH's price does not deviate more than 2%. 22 | */ 23 | uint256 public immutable maxLatency; 24 | 25 | /** 26 | * @notice Constructor 27 | * @param _owner Owner address 28 | * @param _maxLatency Maximum price latency allowed 29 | */ 30 | constructor(address _owner, uint256 _maxLatency) OwnableTwoSteps(_owner) { 31 | maxLatency = _maxLatency; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /contracts/executionStrategies/StrategyDutchAuction.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | // Libraries 5 | import {OrderStructs} from "../libraries/OrderStructs.sol"; 6 | 7 | // Enums 8 | import {QuoteType} from "../enums/QuoteType.sol"; 9 | 10 | // Shared errors 11 | import {BidTooLow, OrderInvalid, FunctionSelectorInvalid, QuoteTypeInvalid} from "../errors/SharedErrors.sol"; 12 | 13 | // Base strategy contracts 14 | import {BaseStrategy, IStrategy} from "./BaseStrategy.sol"; 15 | 16 | /** 17 | * @title StrategyDutchAuction 18 | * @notice This contract offers a single execution strategy for users to create Dutch auctions. 19 | * @author LooksRare protocol team (👀,💎) 20 | */ 21 | contract StrategyDutchAuction is BaseStrategy { 22 | /** 23 | * @notice This function validates the order under the context of the chosen strategy 24 | * and returns the fulfillable items/amounts/price/nonce invalidation status. 25 | * The execution price set by the seller decreases linearly within the defined period. 26 | * @param takerBid Taker bid struct (taker ask-specific parameters for the execution) 27 | * @param makerAsk Maker ask struct (maker bid-specific parameters for the execution) 28 | * @dev The client has to provide the seller's desired initial start price as the additionalParameters. 29 | */ 30 | function executeStrategyWithTakerBid( 31 | OrderStructs.Taker calldata takerBid, 32 | OrderStructs.Maker calldata makerAsk 33 | ) 34 | external 35 | view 36 | returns (uint256 price, uint256[] memory itemIds, uint256[] memory amounts, bool isNonceInvalidated) 37 | { 38 | uint256 itemIdsLength = makerAsk.itemIds.length; 39 | 40 | if (itemIdsLength == 0 || itemIdsLength != makerAsk.amounts.length) { 41 | revert OrderInvalid(); 42 | } 43 | 44 | uint256 startPrice = abi.decode(makerAsk.additionalParameters, (uint256)); 45 | 46 | if (startPrice < makerAsk.price) { 47 | revert OrderInvalid(); 48 | } 49 | 50 | uint256 startTime = makerAsk.startTime; 51 | uint256 endTime = makerAsk.endTime; 52 | 53 | price = 54 | ((endTime - block.timestamp) * startPrice + (block.timestamp - startTime) * makerAsk.price) / 55 | (endTime - startTime); 56 | 57 | uint256 maxPrice = abi.decode(takerBid.additionalParameters, (uint256)); 58 | if (maxPrice < price) { 59 | revert BidTooLow(); 60 | } 61 | 62 | isNonceInvalidated = true; 63 | 64 | itemIds = makerAsk.itemIds; 65 | amounts = makerAsk.amounts; 66 | } 67 | 68 | /** 69 | * @inheritdoc IStrategy 70 | */ 71 | function isMakerOrderValid( 72 | OrderStructs.Maker calldata makerAsk, 73 | bytes4 functionSelector 74 | ) external pure override returns (bool isValid, bytes4 errorSelector) { 75 | if (functionSelector != StrategyDutchAuction.executeStrategyWithTakerBid.selector) { 76 | return (isValid, FunctionSelectorInvalid.selector); 77 | } 78 | 79 | if (makerAsk.quoteType != QuoteType.Ask) { 80 | return (isValid, QuoteTypeInvalid.selector); 81 | } 82 | 83 | uint256 itemIdsLength = makerAsk.itemIds.length; 84 | 85 | if (itemIdsLength == 0 || itemIdsLength != makerAsk.amounts.length) { 86 | return (isValid, OrderInvalid.selector); 87 | } 88 | 89 | for (uint256 i; i < itemIdsLength; ) { 90 | _validateAmountNoRevert(makerAsk.amounts[i], makerAsk.collectionType); 91 | unchecked { 92 | ++i; 93 | } 94 | } 95 | 96 | uint256 startPrice = abi.decode(makerAsk.additionalParameters, (uint256)); 97 | 98 | if (startPrice < makerAsk.price) { 99 | return (isValid, OrderInvalid.selector); 100 | } 101 | 102 | isValid = true; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /contracts/executionStrategies/StrategyItemIdsRange.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | // Libraries 5 | import {OrderStructs} from "../libraries/OrderStructs.sol"; 6 | 7 | // Enums 8 | import {QuoteType} from "../enums/QuoteType.sol"; 9 | 10 | // Shared errors 11 | import {OrderInvalid, FunctionSelectorInvalid, QuoteTypeInvalid} from "../errors/SharedErrors.sol"; 12 | 13 | // Base strategy contracts 14 | import {BaseStrategy, IStrategy} from "./BaseStrategy.sol"; 15 | 16 | /** 17 | * @title StrategyItemIdsRange 18 | * @notice This contract offers a single execution strategy for users to bid on 19 | * a specific amount of items in a range bounded by 2 item ids. 20 | * @author LooksRare protocol team (👀,💎) 21 | */ 22 | contract StrategyItemIdsRange is BaseStrategy { 23 | /** 24 | * @notice This function validates the order under the context of the chosen strategy 25 | * and returns the fulfillable items/amounts/price/nonce invalidation status. 26 | * With this strategy, the bidder picks a item id range (e.g. 1-100) 27 | * and a seller can fulfill the order with any tokens within the specified id range. 28 | * @param takerAsk Taker ask struct (taker ask-specific parameters for the execution) 29 | * @param makerBid Maker bid struct (maker bid-specific parameters for the execution) 30 | */ 31 | function executeStrategyWithTakerAsk( 32 | OrderStructs.Taker calldata takerAsk, 33 | OrderStructs.Maker calldata makerBid 34 | ) 35 | external 36 | pure 37 | returns (uint256 price, uint256[] memory itemIds, uint256[] memory amounts, bool isNonceInvalidated) 38 | { 39 | (itemIds, amounts) = abi.decode(takerAsk.additionalParameters, (uint256[], uint256[])); 40 | uint256 length = itemIds.length; 41 | if (length != amounts.length) { 42 | revert OrderInvalid(); 43 | } 44 | 45 | (uint256 minItemId, uint256 maxItemId, uint256 desiredAmount) = abi.decode( 46 | makerBid.additionalParameters, 47 | (uint256, uint256, uint256) 48 | ); 49 | 50 | if (minItemId >= maxItemId || desiredAmount == 0) { 51 | revert OrderInvalid(); 52 | } 53 | 54 | uint256 totalOfferedAmount; 55 | uint256 lastItemId; 56 | 57 | for (uint256 i; i < length; ) { 58 | uint256 offeredItemId = itemIds[i]; 59 | // Force the client to sort the item ids in ascending order, 60 | // in order to prevent taker ask from providing duplicated 61 | // item ids 62 | if (offeredItemId <= lastItemId) { 63 | if (i != 0) { 64 | revert OrderInvalid(); 65 | } 66 | } 67 | 68 | if (offeredItemId < minItemId || offeredItemId > maxItemId) { 69 | revert OrderInvalid(); 70 | } 71 | 72 | totalOfferedAmount += amounts[i]; 73 | 74 | lastItemId = offeredItemId; 75 | 76 | unchecked { 77 | ++i; 78 | } 79 | } 80 | 81 | if (totalOfferedAmount != desiredAmount) { 82 | revert OrderInvalid(); 83 | } 84 | 85 | price = makerBid.price; 86 | isNonceInvalidated = true; 87 | } 88 | 89 | /** 90 | * @inheritdoc IStrategy 91 | */ 92 | function isMakerOrderValid( 93 | OrderStructs.Maker calldata makerBid, 94 | bytes4 functionSelector 95 | ) external pure override returns (bool isValid, bytes4 errorSelector) { 96 | if (functionSelector != StrategyItemIdsRange.executeStrategyWithTakerAsk.selector) { 97 | return (isValid, FunctionSelectorInvalid.selector); 98 | } 99 | 100 | if (makerBid.quoteType != QuoteType.Bid) { 101 | return (isValid, QuoteTypeInvalid.selector); 102 | } 103 | 104 | (uint256 minItemId, uint256 maxItemId, uint256 desiredAmount) = abi.decode( 105 | makerBid.additionalParameters, 106 | (uint256, uint256, uint256) 107 | ); 108 | 109 | if (minItemId >= maxItemId || desiredAmount == 0) { 110 | return (isValid, OrderInvalid.selector); 111 | } 112 | 113 | isValid = true; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /contracts/helpers/ProtocolHelpers.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | // LooksRare unopinionated libraries 5 | import {SignatureCheckerCalldata} from "@looksrare/contracts-libs/contracts/SignatureCheckerCalldata.sol"; 6 | 7 | // Libraries 8 | import {OrderStructs} from "../libraries/OrderStructs.sol"; 9 | 10 | // Other dependencies 11 | import {LooksRareProtocol} from "../LooksRareProtocol.sol"; 12 | 13 | /** 14 | * @title ProtocolHelpers 15 | * @notice This contract contains helper view functions for order creation. 16 | * @author LooksRare protocol team (👀,💎) 17 | */ 18 | contract ProtocolHelpers { 19 | using OrderStructs for OrderStructs.Maker; 20 | 21 | // Encoding prefix for EIP-712 signatures 22 | string internal constant _ENCODING_PREFIX = "\x19\x01"; 23 | 24 | // LooksRareProtocol 25 | LooksRareProtocol public looksRareProtocol; 26 | 27 | /** 28 | * @notice Constructor 29 | * @param _looksRareProtocol LooksRare protocol address 30 | */ 31 | constructor(address _looksRareProtocol) { 32 | looksRareProtocol = LooksRareProtocol(_looksRareProtocol); 33 | } 34 | 35 | /** 36 | * @notice Compute digest for maker bid struct 37 | * @param maker Maker struct 38 | * @return digest Digest 39 | */ 40 | function computeMakerDigest(OrderStructs.Maker memory maker) public view returns (bytes32 digest) { 41 | bytes32 domainSeparator = looksRareProtocol.domainSeparator(); 42 | return keccak256(abi.encodePacked(_ENCODING_PREFIX, domainSeparator, maker.hash())); 43 | } 44 | 45 | /** 46 | * @notice Compute digest for merkle tree struct 47 | * @param merkleTree Merkle tree struct 48 | * @return digest Digest 49 | */ 50 | function computeDigestMerkleTree(OrderStructs.MerkleTree memory merkleTree) public view returns (bytes32 digest) { 51 | bytes32 domainSeparator = looksRareProtocol.domainSeparator(); 52 | bytes32 batchOrderHash = looksRareProtocol.hashBatchOrder(merkleTree.root, merkleTree.proof.length); 53 | return keccak256(abi.encodePacked(_ENCODING_PREFIX, domainSeparator, batchOrderHash)); 54 | } 55 | 56 | /** 57 | * @notice Verify maker order signature 58 | * @param maker Maker struct 59 | * @param makerSignature Maker signature 60 | * @param signer Signer address 61 | * @dev It returns true only if the SignatureCheckerCalldata does not revert before. 62 | */ 63 | function verifyMakerSignature( 64 | OrderStructs.Maker memory maker, 65 | bytes calldata makerSignature, 66 | address signer 67 | ) public view returns (bool) { 68 | bytes32 digest = computeMakerDigest(maker); 69 | SignatureCheckerCalldata.verify(digest, signer, makerSignature); 70 | return true; 71 | } 72 | 73 | /** 74 | * @notice Verify merkle tree signature 75 | * @param merkleTree Merkle tree struct 76 | * @param makerSignature Maker signature 77 | * @param signer Signer address 78 | * @dev It returns true only if the SignatureCheckerCalldata does not revert before. 79 | */ 80 | function verifyMerkleTree( 81 | OrderStructs.MerkleTree memory merkleTree, 82 | bytes calldata makerSignature, 83 | address signer 84 | ) public view returns (bool) { 85 | bytes32 digest = computeDigestMerkleTree(merkleTree); 86 | SignatureCheckerCalldata.verify(digest, signer, makerSignature); 87 | return true; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /contracts/interfaces/IAffiliateManager.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | /** 5 | * @title IAffiliateManager 6 | * @author LooksRare protocol team (👀,💎) 7 | */ 8 | interface IAffiliateManager { 9 | /** 10 | * @notice It is emitted when there is an update of affliate controller. 11 | * @param affiliateController Address of the new affiliate controller 12 | */ 13 | event NewAffiliateController(address affiliateController); 14 | 15 | /** 16 | * @notice It is emitted if the affiliate program is activated or deactivated. 17 | * @param isActive Whether the affiliate program is active after the update 18 | */ 19 | event NewAffiliateProgramStatus(bool isActive); 20 | 21 | /** 22 | * @notice It is emitted if there is a new affiliate and its associated rate (in basis point). 23 | * @param affiliate Address of the affiliate 24 | * @param rate Affiliate rate (in basis point) 25 | */ 26 | event NewAffiliateRate(address affiliate, uint256 rate); 27 | 28 | /** 29 | * @notice It is returned if the function is called by another address than the affiliate controller. 30 | */ 31 | error NotAffiliateController(); 32 | 33 | /** 34 | * @notice It is returned if the affiliate controller is trying to set an affiliate rate higher than 10,000. 35 | */ 36 | error PercentageTooHigh(); 37 | } 38 | -------------------------------------------------------------------------------- /contracts/interfaces/ICreatorFeeManager.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | // Interfaces 5 | import {IRoyaltyFeeRegistry} from "./IRoyaltyFeeRegistry.sol"; 6 | 7 | /** 8 | * @title ICreatorFeeManager 9 | * @author LooksRare protocol team (👀,💎) 10 | */ 11 | interface ICreatorFeeManager { 12 | /** 13 | * @notice It is returned if the bundle contains multiple itemIds with different creator fee structure. 14 | */ 15 | error BundleEIP2981NotAllowed(address collection); 16 | 17 | /** 18 | * @notice It returns the royalty fee registry address/interface. 19 | * @return royaltyFeeRegistry Interface of the royalty fee registry 20 | */ 21 | function royaltyFeeRegistry() external view returns (IRoyaltyFeeRegistry royaltyFeeRegistry); 22 | 23 | /** 24 | * @notice This function returns the creator address and calculates the creator fee amount. 25 | * @param collection Collection address 26 | * @param price Transaction price 27 | * @param itemIds Array of item ids 28 | * @return creator Creator address 29 | * @return creatorFeeAmount Creator fee amount 30 | */ 31 | function viewCreatorFeeInfo( 32 | address collection, 33 | uint256 price, 34 | uint256[] memory itemIds 35 | ) external view returns (address creator, uint256 creatorFeeAmount); 36 | } 37 | -------------------------------------------------------------------------------- /contracts/interfaces/ICurrencyManager.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | /** 5 | * @title ICurrencyManager 6 | * @author LooksRare protocol team (👀,💎) 7 | */ 8 | interface ICurrencyManager { 9 | /** 10 | * @notice It is emitted if the currency status in the allowlist is updated. 11 | * @param currency Currency address (address(0) = ETH) 12 | * @param isAllowed Whether the currency is allowed 13 | */ 14 | event CurrencyStatusUpdated(address currency, bool isAllowed); 15 | } 16 | -------------------------------------------------------------------------------- /contracts/interfaces/IExecutionManager.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | /** 5 | * @title IExecutionManager 6 | * @author LooksRare protocol team (👀,💎) 7 | */ 8 | interface IExecutionManager { 9 | /** 10 | * @notice It is issued when there is a new creator fee manager. 11 | * @param creatorFeeManager Address of the new creator fee manager 12 | */ 13 | event NewCreatorFeeManager(address creatorFeeManager); 14 | 15 | /** 16 | * @notice It is issued when there is a new maximum creator fee (in basis point). 17 | * @param maxCreatorFeeBp New maximum creator fee (in basis point) 18 | */ 19 | event NewMaxCreatorFeeBp(uint256 maxCreatorFeeBp); 20 | 21 | /** 22 | * @notice It is issued when there is a new protocol fee recipient address. 23 | * @param protocolFeeRecipient Address of the new protocol fee recipient 24 | */ 25 | event NewProtocolFeeRecipient(address protocolFeeRecipient); 26 | 27 | /** 28 | * @notice It is returned if the creator fee (in basis point) is too high. 29 | */ 30 | error CreatorFeeBpTooHigh(); 31 | 32 | /** 33 | * @notice It is returned if the new protocol fee recipient is set to address(0). 34 | */ 35 | error NewProtocolFeeRecipientCannotBeNullAddress(); 36 | 37 | /** 38 | * @notice It is returned if there is no selector for maker ask/bid for a given strategyId, 39 | * depending on the quote type. 40 | */ 41 | error NoSelectorForStrategy(); 42 | 43 | /** 44 | * @notice It is returned if the current block timestamp is not between start and end times in the maker order. 45 | */ 46 | error OutsideOfTimeRange(); 47 | 48 | /** 49 | * @notice It is returned if the strategy id has no implementation. 50 | * @dev It is returned if there is no implementation address and the strategyId is strictly greater than 0. 51 | */ 52 | error StrategyNotAvailable(uint256 strategyId); 53 | } 54 | -------------------------------------------------------------------------------- /contracts/interfaces/IImmutableCreate2Factory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | interface IImmutableCreate2Factory { 5 | function safeCreate2( 6 | bytes32 salt, 7 | bytes calldata initializationCode 8 | ) external payable returns (address deploymentAddress); 9 | } 10 | -------------------------------------------------------------------------------- /contracts/interfaces/INonceManager.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | /** 5 | * @title INonceManager 6 | * @author LooksRare protocol team (👀,💎) 7 | */ 8 | interface INonceManager { 9 | /** 10 | * @notice This struct contains the global bid and ask nonces of a user. 11 | * @param bidNonce Bid nonce 12 | * @param askNonce Ask nonce 13 | */ 14 | struct UserBidAskNonces { 15 | uint256 bidNonce; 16 | uint256 askNonce; 17 | } 18 | 19 | /** 20 | * @notice It is emitted when there is an update of the global bid/ask nonces for a user. 21 | * @param user Address of the user 22 | * @param bidNonce New bid nonce 23 | * @param askNonce New ask nonce 24 | */ 25 | event NewBidAskNonces(address user, uint256 bidNonce, uint256 askNonce); 26 | 27 | /** 28 | * @notice It is emitted when order nonces are cancelled for a user. 29 | * @param user Address of the user 30 | * @param orderNonces Array of order nonces cancelled 31 | */ 32 | event OrderNoncesCancelled(address user, uint256[] orderNonces); 33 | 34 | /** 35 | * @notice It is emitted when subset nonces are cancelled for a user. 36 | * @param user Address of the user 37 | * @param subsetNonces Array of subset nonces cancelled 38 | */ 39 | event SubsetNoncesCancelled(address user, uint256[] subsetNonces); 40 | } 41 | -------------------------------------------------------------------------------- /contracts/interfaces/IRoyaltyFeeRegistry.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | /** 5 | * @title IRoyaltyFeeRegistry 6 | * @author LooksRare protocol team (👀,💎) 7 | */ 8 | interface IRoyaltyFeeRegistry { 9 | /** 10 | * @notice This function returns the royalty information for a collection at a given transaction price. 11 | * @param collection Collection address 12 | * @param price Transaction price 13 | * @return receiver Receiver address 14 | * @return royaltyFee Royalty fee amount 15 | */ 16 | function royaltyInfo( 17 | address collection, 18 | uint256 price 19 | ) external view returns (address receiver, uint256 royaltyFee); 20 | } 21 | -------------------------------------------------------------------------------- /contracts/interfaces/IStrategy.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | // Libraries 5 | import {OrderStructs} from "../libraries/OrderStructs.sol"; 6 | 7 | /** 8 | * @title IStrategy 9 | * @author LooksRare protocol team (👀,💎) 10 | */ 11 | interface IStrategy { 12 | /** 13 | * @notice Validate *only the maker* order under the context of the chosen strategy. It does not revert if 14 | * the maker order is invalid. Instead it returns false and the error's 4 bytes selector. 15 | * @param makerOrder Maker struct (maker specific parameters for the execution) 16 | * @param functionSelector Function selector for the strategy 17 | * @return isValid Whether the maker struct is valid 18 | * @return errorSelector If isValid is false, it returns the error's 4 bytes selector 19 | */ 20 | function isMakerOrderValid( 21 | OrderStructs.Maker calldata makerOrder, 22 | bytes4 functionSelector 23 | ) external view returns (bool isValid, bytes4 errorSelector); 24 | 25 | /** 26 | * @notice This function acts as a safety check for the protocol's owner when adding new execution strategies. 27 | * @return isStrategy Whether it is a LooksRare V2 protocol strategy 28 | */ 29 | function isLooksRareV2Strategy() external pure returns (bool isStrategy); 30 | } 31 | -------------------------------------------------------------------------------- /contracts/interfaces/IStrategyManager.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | /** 5 | * @title IStrategyManager 6 | * @author LooksRare protocol team (👀,💎) 7 | */ 8 | interface IStrategyManager { 9 | /** 10 | * @notice This struct contains the parameter of an execution strategy. 11 | * @param strategyId Id of the new strategy 12 | * @param standardProtocolFeeBp Standard protocol fee (in basis point) 13 | * @param minTotalFeeBp Minimum total fee (in basis point) 14 | * @param maxProtocolFeeBp Maximum protocol fee (in basis point) 15 | * @param selector Function selector for the transaction to be executed 16 | * @param isMakerBid Whether the strategyId is for maker bid 17 | * @param implementation Address of the implementation of the strategy 18 | */ 19 | struct Strategy { 20 | bool isActive; 21 | uint16 standardProtocolFeeBp; 22 | uint16 minTotalFeeBp; 23 | uint16 maxProtocolFeeBp; 24 | bytes4 selector; 25 | bool isMakerBid; 26 | address implementation; 27 | } 28 | 29 | /** 30 | * @notice It is emitted when a new strategy is added. 31 | * @param strategyId Id of the new strategy 32 | * @param standardProtocolFeeBp Standard protocol fee (in basis point) 33 | * @param minTotalFeeBp Minimum total fee (in basis point) 34 | * @param maxProtocolFeeBp Maximum protocol fee (in basis point) 35 | * @param selector Function selector for the transaction to be executed 36 | * @param isMakerBid Whether the strategyId is for maker bid 37 | * @param implementation Address of the implementation of the strategy 38 | */ 39 | event NewStrategy( 40 | uint256 strategyId, 41 | uint16 standardProtocolFeeBp, 42 | uint16 minTotalFeeBp, 43 | uint16 maxProtocolFeeBp, 44 | bytes4 selector, 45 | bool isMakerBid, 46 | address implementation 47 | ); 48 | 49 | /** 50 | * @notice It is emitted when an existing strategy is updated. 51 | * @param strategyId Id of the strategy 52 | * @param isActive Whether the strategy is active (or not) after the update 53 | * @param standardProtocolFeeBp Standard protocol fee (in basis point) 54 | * @param minTotalFeeBp Minimum total fee (in basis point) 55 | */ 56 | event StrategyUpdated(uint256 strategyId, bool isActive, uint16 standardProtocolFeeBp, uint16 minTotalFeeBp); 57 | 58 | /** 59 | * @notice If the strategy has not set properly its implementation contract. 60 | * @dev It can only be returned for owner operations. 61 | */ 62 | error NotV2Strategy(); 63 | 64 | /** 65 | * @notice It is returned if the strategy has no selector. 66 | * @dev It can only be returned for owner operations. 67 | */ 68 | error StrategyHasNoSelector(); 69 | 70 | /** 71 | * @notice It is returned if the strategyId is invalid. 72 | */ 73 | error StrategyNotUsed(); 74 | 75 | /** 76 | * @notice It is returned if the strategy's protocol fee is too high. 77 | * @dev It can only be returned for owner operations. 78 | */ 79 | error StrategyProtocolFeeTooHigh(); 80 | } 81 | -------------------------------------------------------------------------------- /contracts/interfaces/ITransferManager.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | // Libraries 5 | import {OrderStructs} from "../libraries/OrderStructs.sol"; 6 | 7 | // Enums 8 | import {CollectionType} from "../enums/CollectionType.sol"; 9 | 10 | /** 11 | * @title ITransferManager 12 | * @author LooksRare protocol team (👀,💎) 13 | */ 14 | interface ITransferManager { 15 | /** 16 | * @notice This struct is only used for transferBatchItemsAcrossCollections. 17 | * @param collection Collection address 18 | * @param collectionType 0 for ERC721, 1 for ERC1155 19 | * @param itemIds Array of item ids to transfer 20 | * @param amounts Array of amounts to transfer 21 | */ 22 | struct BatchTransferItem { 23 | address collection; 24 | CollectionType collectionType; 25 | uint256[] itemIds; 26 | uint256[] amounts; 27 | } 28 | 29 | /** 30 | * @notice It is emitted if operators' approvals to transfer NFTs are granted by a user. 31 | * @param user Address of the user 32 | * @param operators Array of operator addresses 33 | */ 34 | event ApprovalsGranted(address user, address[] operators); 35 | 36 | /** 37 | * @notice It is emitted if operators' approvals to transfer NFTs are revoked by a user. 38 | * @param user Address of the user 39 | * @param operators Array of operator addresses 40 | */ 41 | event ApprovalsRemoved(address user, address[] operators); 42 | 43 | /** 44 | * @notice It is emitted if a new operator is added to the global allowlist. 45 | * @param operator Operator address 46 | */ 47 | event OperatorAllowed(address operator); 48 | 49 | /** 50 | * @notice It is emitted if an operator is removed from the global allowlist. 51 | * @param operator Operator address 52 | */ 53 | event OperatorRemoved(address operator); 54 | 55 | /** 56 | * @notice It is returned if the operator to approve has already been approved by the user. 57 | */ 58 | error OperatorAlreadyApprovedByUser(); 59 | 60 | /** 61 | * @notice It is returned if the operator to revoke has not been previously approved by the user. 62 | */ 63 | error OperatorNotApprovedByUser(); 64 | 65 | /** 66 | * @notice It is returned if the transfer caller is already allowed by the owner. 67 | * @dev This error can only be returned for owner operations. 68 | */ 69 | error OperatorAlreadyAllowed(); 70 | 71 | /** 72 | * @notice It is returned if the operator to approve is not in the global allowlist defined by the owner. 73 | * @dev This error can be returned if the user tries to grant approval to an operator address not in the 74 | * allowlist or if the owner tries to remove the operator from the global allowlist. 75 | */ 76 | error OperatorNotAllowed(); 77 | 78 | /** 79 | * @notice It is returned if the transfer caller is invalid. 80 | * For a transfer called to be valid, the operator must be in the global allowlist and 81 | * approved by the 'from' user. 82 | */ 83 | error TransferCallerInvalid(); 84 | } 85 | -------------------------------------------------------------------------------- /contracts/libraries/CurrencyValidator.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | // Assembly 5 | import {CurrencyInvalid_error_selector, CurrencyInvalid_error_length, Error_selector_offset} from "../constants/AssemblyConstants.sol"; 6 | 7 | /** 8 | * @title CurrencyValidator 9 | * @notice This library validates the order currency to be the 10 | * chain's native currency or the specified ERC20 token. 11 | * @author LooksRare protocol team (👀,💎) 12 | */ 13 | library CurrencyValidator { 14 | /** 15 | * @dev This is equivalent to 16 | * if (orderCurrency != address(0)) { 17 | * if (orderCurrency != allowedCurrency) { 18 | * revert CurrencyInvalid(); 19 | * } 20 | * } 21 | * 22 | * 1. If orderCurrency == WETH, allowedCurrency == WETH -> WETH * 0 == 0 23 | * 2. If orderCurrency == ETH, allowedCurrency == WETH -> 0 * 1 == 0 24 | * 3. If orderCurrency == USDC, allowedCurrency == WETH -> USDC * 1 != 0 25 | */ 26 | function allowNativeOrAllowedCurrency(address orderCurrency, address allowedCurrency) internal pure { 27 | assembly { 28 | if mul(orderCurrency, iszero(eq(orderCurrency, allowedCurrency))) { 29 | mstore(0x00, CurrencyInvalid_error_selector) 30 | revert(Error_selector_offset, CurrencyInvalid_error_length) 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /contracts/libraries/OpenZeppelin/MerkleProofCalldataWithNodes.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | // Libraries 5 | import {OrderStructs} from "../../libraries/OrderStructs.sol"; 6 | 7 | /** 8 | * @title MerkleProofCalldataWithNodes 9 | * @notice This library is adjusted from the work of OpenZeppelin. 10 | * It is based on the 4.7.0 (utils/cryptography/MerkleProof.sol). 11 | * @author OpenZeppelin (adjusted by LooksRare) 12 | */ 13 | library MerkleProofCalldataWithNodes { 14 | /** 15 | * @notice This returns true if a `leaf` can be proved to be a part of a Merkle tree defined by `root`. 16 | * For this, a `proof` must be provided, containing sibling hashes on the branch from the leaf to the 17 | * root of the tree. Each pair of leaves and each pair of pre-images are assumed to be sorted. 18 | */ 19 | function verifyCalldata( 20 | OrderStructs.MerkleTreeNode[] calldata proof, 21 | bytes32 root, 22 | bytes32 leaf 23 | ) internal pure returns (bool) { 24 | return processProofCalldata(proof, leaf) == root; 25 | } 26 | 27 | /** 28 | * @notice This returns the rebuilt hash obtained by traversing a Merkle tree up from `leaf` using `proof`. 29 | * A `proof` is valid if and only if the rebuilt hash matches the root of the tree. 30 | * When processing the proof, the pairs of leafs & pre-images are assumed to be sorted. 31 | */ 32 | function processProofCalldata( 33 | OrderStructs.MerkleTreeNode[] calldata proof, 34 | bytes32 leaf 35 | ) internal pure returns (bytes32) { 36 | bytes32 computedHash = leaf; 37 | uint256 length = proof.length; 38 | 39 | for (uint256 i = 0; i < length; ) { 40 | if (proof[i].position == OrderStructs.MerkleTreeNodePosition.Left) { 41 | computedHash = _efficientHash(proof[i].value, computedHash); 42 | } else { 43 | computedHash = _efficientHash(computedHash, proof[i].value); 44 | } 45 | unchecked { 46 | ++i; 47 | } 48 | } 49 | return computedHash; 50 | } 51 | 52 | function _efficientHash(bytes32 a, bytes32 b) private pure returns (bytes32 value) { 53 | /// @solidity memory-safe-assembly 54 | assembly { 55 | mstore(0x00, a) 56 | mstore(0x20, b) 57 | value := keccak256(0x00, 0x40) 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /contracts/libraries/OpenZeppelin/MerkleProofMemory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | /** 5 | * @title MerkleProofMemory 6 | * @notice This library is adjusted from the work of OpenZeppelin. 7 | * It is based on the 4.7.0 (utils/cryptography/MerkleProof.sol). 8 | * @author OpenZeppelin (adjusted by LooksRare) 9 | */ 10 | library MerkleProofMemory { 11 | /** 12 | * @notice This returns true if a `leaf` can be proved to be a part of a Merkle tree defined by `root`. 13 | * For this, a `proof` must be provided, containing sibling hashes on the branch from the leaf to the 14 | * root of the tree. Each pair of leaves and each pair of pre-images are assumed to be sorted. 15 | */ 16 | function verify(bytes32[] memory proof, bytes32 root, bytes32 leaf) internal pure returns (bool) { 17 | return processProof(proof, leaf) == root; 18 | } 19 | 20 | /** 21 | * @notice This returns the rebuilt hash obtained by traversing a Merkle tree up from `leaf` using `proof`. 22 | * A `proof` is valid if and only if the rebuilt hash matches the root of the tree. 23 | * When processing the proof, the pairs of leafs & pre-images are assumed to be sorted. 24 | */ 25 | function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) { 26 | bytes32 computedHash = leaf; 27 | uint256 length = proof.length; 28 | 29 | for (uint256 i = 0; i < length; ) { 30 | computedHash = _hashPair(computedHash, proof[i]); 31 | unchecked { 32 | ++i; 33 | } 34 | } 35 | return computedHash; 36 | } 37 | 38 | function _hashPair(bytes32 a, bytes32 b) private pure returns (bytes32) { 39 | return a < b ? _efficientHash(a, b) : _efficientHash(b, a); 40 | } 41 | 42 | function _efficientHash(bytes32 a, bytes32 b) private pure returns (bytes32 value) { 43 | /// @solidity memory-safe-assembly 44 | assembly { 45 | mstore(0x00, a) 46 | mstore(0x20, b) 47 | value := keccak256(0x00, 0x40) 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /contracts/libraries/OrderStructs.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | // Enums 5 | import {CollectionType} from "../enums/CollectionType.sol"; 6 | import {QuoteType} from "../enums/QuoteType.sol"; 7 | 8 | /** 9 | * @title OrderStructs 10 | * @notice This library contains all order struct types for the LooksRare protocol (v2). 11 | * @author LooksRare protocol team (👀,💎) 12 | */ 13 | library OrderStructs { 14 | /** 15 | * 1. Maker struct 16 | */ 17 | 18 | /** 19 | * @notice Maker is the struct for a maker order. 20 | * @param quoteType Quote type (i.e. 0 = BID, 1 = ASK) 21 | * @param globalNonce Global user order nonce for maker orders 22 | * @param subsetNonce Subset nonce (shared across bid/ask maker orders) 23 | * @param orderNonce Order nonce (it can be shared across bid/ask maker orders) 24 | * @param strategyId Strategy id 25 | * @param collectionType Collection type (i.e. 0 = ERC721, 1 = ERC1155) 26 | * @param collection Collection address 27 | * @param currency Currency address (@dev address(0) = ETH) 28 | * @param signer Signer address 29 | * @param startTime Start timestamp 30 | * @param endTime End timestamp 31 | * @param price Minimum price for maker ask, maximum price for maker bid 32 | * @param itemIds Array of itemIds 33 | * @param amounts Array of amounts 34 | * @param additionalParameters Extra data specific for the order 35 | */ 36 | struct Maker { 37 | QuoteType quoteType; 38 | uint256 globalNonce; 39 | uint256 subsetNonce; 40 | uint256 orderNonce; 41 | uint256 strategyId; 42 | CollectionType collectionType; 43 | address collection; 44 | address currency; 45 | address signer; 46 | uint256 startTime; 47 | uint256 endTime; 48 | uint256 price; 49 | uint256[] itemIds; 50 | uint256[] amounts; 51 | bytes additionalParameters; 52 | } 53 | 54 | /** 55 | * 2. Taker struct 56 | */ 57 | 58 | /** 59 | * @notice Taker is the struct for a taker ask/bid order. It contains the parameters required for a direct purchase. 60 | * @dev Taker struct is matched against MakerAsk/MakerBid structs at the protocol level. 61 | * @param recipient Recipient address (to receive NFTs or non-fungible tokens) 62 | * @param additionalParameters Extra data specific for the order 63 | */ 64 | struct Taker { 65 | address recipient; 66 | bytes additionalParameters; 67 | } 68 | 69 | /** 70 | * 3. Merkle tree struct 71 | */ 72 | 73 | enum MerkleTreeNodePosition { Left, Right } 74 | 75 | /** 76 | * @notice MerkleTreeNode is a MerkleTree's node. 77 | * @param value It can be an order hash or a proof 78 | * @param position The node's position in its branch. 79 | * It can be left or right or none 80 | * (before the tree is sorted). 81 | */ 82 | struct MerkleTreeNode { 83 | bytes32 value; 84 | MerkleTreeNodePosition position; 85 | } 86 | 87 | /** 88 | * @notice MerkleTree is the struct for a merkle tree of order hashes. 89 | * @dev A Merkle tree can be computed with order hashes. 90 | * It can contain order hashes from both maker bid and maker ask structs. 91 | * @param root Merkle root 92 | * @param proof Array containing the merkle proof 93 | */ 94 | struct MerkleTree { 95 | bytes32 root; 96 | MerkleTreeNode[] proof; 97 | } 98 | 99 | /** 100 | * 4. Constants 101 | */ 102 | 103 | /** 104 | * @notice This is the type hash constant used to compute the maker order hash. 105 | */ 106 | bytes32 internal constant _MAKER_TYPEHASH = 107 | keccak256( 108 | "Maker(" 109 | "uint8 quoteType," 110 | "uint256 globalNonce," 111 | "uint256 subsetNonce," 112 | "uint256 orderNonce," 113 | "uint256 strategyId," 114 | "uint8 collectionType," 115 | "address collection," 116 | "address currency," 117 | "address signer," 118 | "uint256 startTime," 119 | "uint256 endTime," 120 | "uint256 price," 121 | "uint256[] itemIds," 122 | "uint256[] amounts," 123 | "bytes additionalParameters" 124 | ")" 125 | ); 126 | 127 | /** 128 | * 5. Hash functions 129 | */ 130 | 131 | /** 132 | * @notice This function is used to compute the order hash for a maker struct. 133 | * @param maker Maker order struct 134 | * @return makerHash Hash of the maker struct 135 | */ 136 | function hash(Maker memory maker) internal pure returns (bytes32) { 137 | // Encoding is done into two parts to avoid stack too deep issues 138 | return 139 | keccak256( 140 | bytes.concat( 141 | abi.encode( 142 | _MAKER_TYPEHASH, 143 | maker.quoteType, 144 | maker.globalNonce, 145 | maker.subsetNonce, 146 | maker.orderNonce, 147 | maker.strategyId, 148 | maker.collectionType, 149 | maker.collection, 150 | maker.currency 151 | ), 152 | abi.encode( 153 | maker.signer, 154 | maker.startTime, 155 | maker.endTime, 156 | maker.price, 157 | keccak256(abi.encodePacked(maker.itemIds)), 158 | keccak256(abi.encodePacked(maker.amounts)), 159 | keccak256(maker.additionalParameters) 160 | ) 161 | ) 162 | ); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | auto_detect_solc = true 3 | block_base_fee_per_gas = 0 4 | block_coinbase = '0x0000000000000000000000000000000000000000' 5 | block_difficulty = 0 6 | block_number = 0 7 | block_timestamp = 0 8 | cache = true 9 | cache_path = 'cache' 10 | evm_version = 'london' 11 | extra_output = [] 12 | extra_output_files = [] 13 | ffi = false 14 | force = false 15 | fuzz_max_global_rejects = 65536 16 | fuzz_max_local_rejects = 1024 17 | fuzz_runs = 1000 18 | gas_limit = 9223372036854775807 19 | gas_price = 0 20 | gas_reports = ['LooksRareProtocol'] 21 | ignored_error_codes = [1878] 22 | initial_balance = '0xffffffffffffffffffffffff' 23 | libraries = [] 24 | libs = ["node_modules", "lib"] 25 | names = false 26 | offline = false 27 | optimizer = true 28 | optimizer_runs = 888888 29 | out = 'artifacts' 30 | sender = '0x00a329c0648769a73afac7f9381e08fb43dbea72' 31 | sizes = false 32 | src = 'contracts' 33 | test = 'test/foundry' 34 | tx_origin = '0x00a329c0648769a73afac7f9381e08fb43dbea72' 35 | verbosity = 0 36 | via_ir = false 37 | 38 | [rpc_endpoints] 39 | mainnet = "${MAINNET_RPC_URL}" 40 | goerli = "${GOERLI_RPC_URL}" 41 | -------------------------------------------------------------------------------- /hardhat.config.ts: -------------------------------------------------------------------------------- 1 | import type { HardhatUserConfig } from "hardhat/types"; 2 | 3 | import "@nomiclabs/hardhat-etherscan"; 4 | import "@typechain/hardhat"; 5 | import "hardhat-abi-exporter"; 6 | import "dotenv/config"; 7 | import "solidity-docgen"; 8 | 9 | const config: HardhatUserConfig = { 10 | defaultNetwork: "hardhat", 11 | networks: { 12 | hardhat: { 13 | allowUnlimitedContractSize: false, 14 | mining: { 15 | auto: true, 16 | interval: 50000, 17 | }, 18 | gasPrice: "auto", 19 | }, 20 | }, 21 | etherscan: { 22 | apiKey: process.env.ETHERSCAN_KEY, 23 | }, 24 | solidity: { 25 | compilers: [ 26 | { 27 | version: "0.8.17", 28 | settings: { optimizer: { enabled: true, runs: 888888 } }, 29 | }, 30 | { 31 | version: "0.4.18", 32 | settings: { optimizer: { enabled: true, runs: 999 } }, 33 | }, 34 | ], 35 | }, 36 | docgen: { pages: "files", exclude: ["contracts/helpers/*"] }, 37 | paths: { 38 | sources: "./contracts/", 39 | tests: "./test", 40 | cache: "./cache", 41 | artifacts: "./artifacts", 42 | }, 43 | abiExporter: { 44 | path: "./abis", 45 | runOnCompile: true, 46 | clear: true, 47 | flat: true, 48 | only: ["LooksRareProtocol.sol", "TransferManager.sol"], 49 | except: ["test*"], 50 | }, 51 | }; 52 | 53 | export default config; 54 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@looksrare/contracts-exchange-v2", 3 | "version": "0.1.3", 4 | "description": "LooksRare exchange protocol smart contracts (v2)", 5 | "author": "LooksRare", 6 | "license": "MIT", 7 | "keywords": [ 8 | "ethereum", 9 | "looksrare", 10 | "solidity" 11 | ], 12 | "homepage": "https://looksrare.org/", 13 | "bugs": "https://github.com/LooksRare/contracts-exchange-v2/issues", 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/LooksRare/contracts-exchange-v2.git" 17 | }, 18 | "files": [ 19 | "/abis/*.json", 20 | "/contracts/*.sol", 21 | "/contracts/constants/*.sol", 22 | "/contracts/enums/*.sol", 23 | "/contracts/helpers/*.sol", 24 | "/contracts/interfaces/*.sol", 25 | "/contracts/libraries/*.sol", 26 | "/contracts/libraries/OpenZeppelin/*.sol", 27 | "/contracts/errors/SharedErrors.sol", 28 | "/contracts/executionStrategies/BaseStrategy.sol", 29 | "/contracts/executionStrategies/StrategyCollectionOffer.sol", 30 | "/test/mock/MockRoyaltyFeeRegistry.sol", 31 | "inheritance-contracts.svg" 32 | ], 33 | "publishConfig": { 34 | "access": "public", 35 | "registry": "https://registry.npmjs.org" 36 | }, 37 | "scripts": { 38 | "compile": "hardhat compile", 39 | "compile:forge": "FOUNDRY_PROFILE=local forge build", 40 | "compile:hardhat": "hardhat compile --force", 41 | "generate:docs": "rm -f -R docs && hardhat docgen", 42 | "format:check": "prettier --check '**/*.{js,jsx,ts,tsx,sol,json,yaml,md}'", 43 | "format:write": "prettier --write '**/*.{js,jsx,ts,tsx,sol,json,yaml,md}'", 44 | "lint": "eslint '**/*.{js,jsx,ts,tsx}'", 45 | "prepare": "husky install", 46 | "prepublishOnly": "yarn compile", 47 | "release": "hardhat export-abi && release-it", 48 | "test:forge:gas:coverage": "FOUNDRY_PROFILE=local forge test --gas-report", 49 | "test:forge": "FOUNDRY_PROFILE=local forge test -vv", 50 | "test:forge:coverage": "forge coverage --report lcov && LCOV_EXCLUDE=(\"test/*\" \"contracts/libraries/*\" \"contracts/helpers/ProtocolHelpers.sol\") echo $LCOV_EXCLUDE | xargs lcov --output-file lcov-filtered.info --remove lcov.info genhtml lcov-filtered.info --output-directory out && open out/index.html", 51 | "test:forge:watch": "FOUNDRY_PROFILE=local forge test -vvv --watch", 52 | "test:hardhat": "hardhat test", 53 | "test:hardhat:gas": "REPORT_GAS=true hardhat test", 54 | "test:hardhat:coverage": "hardhat coverage && hardhat compile --force" 55 | }, 56 | "devDependencies": { 57 | "@commitlint/cli": "^16.2.3", 58 | "@commitlint/config-conventional": "^16.2.1", 59 | "@nomiclabs/hardhat-ethers": "^2.0.6", 60 | "@nomiclabs/hardhat-etherscan": "^3.0.3", 61 | "@typechain/ethers-v5": "^7.0.1", 62 | "@typechain/hardhat": "^2.3.0", 63 | "@types/chai": "^4.2.21", 64 | "@types/mocha": "^9.0.0", 65 | "@types/node": "^12.0.0", 66 | "@typescript-eslint/eslint-plugin": "^4.29.1", 67 | "@typescript-eslint/parser": "^4.29.1", 68 | "chai": "^4.2.0", 69 | "dotenv": "^10.0.0", 70 | "eslint": "^7.29.0", 71 | "eslint-config-prettier": "^8.3.0", 72 | "eslint-config-standard": "^16.0.3", 73 | "eslint-plugin-import": "^2.27.5", 74 | "eslint-plugin-node": "^11.1.0", 75 | "eslint-plugin-prettier": "^3.4.0", 76 | "eslint-plugin-promise": "^5.1.0", 77 | "ethers": "^5.6.4", 78 | "hardhat": "^2.9.4", 79 | "hardhat-abi-exporter": "^2.9.0", 80 | "husky": "^7.0.4", 81 | "merkletreejs": "^0.2.31", 82 | "prettier": "^2.3.2", 83 | "prettier-plugin-solidity": "^1.0.0", 84 | "release-it": "^15.6.0", 85 | "solhint": "^3.3.7", 86 | "solidity-docgen": "^0.6.0-beta.34", 87 | "solmate": "^6.6.1", 88 | "ts-node": "^10.1.0", 89 | "typechain": "^5.1.2", 90 | "typescript": "^4.5.2" 91 | }, 92 | "dependencies": { 93 | "@chainlink/contracts": "0.5.1", 94 | "@looksrare/contracts-libs": "3.0.3" 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /remappings.txt: -------------------------------------------------------------------------------- 1 | @looksrare/=node_modules/@looksrare/ 2 | hardhat/=node_modules/hardhat/ 3 | solmate/=node_modules/solmate/ 4 | -------------------------------------------------------------------------------- /scripts/AddAffiliate.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | // Scripting tool 5 | import {Script} from "../lib/forge-std/src/Script.sol"; 6 | 7 | // Core contracts 8 | import {LooksRareProtocol} from "../contracts/LooksRareProtocol.sol"; 9 | 10 | contract AddAffiliate is Script { 11 | error ChainIdInvalid(uint256 chainId); 12 | 13 | function run() external { 14 | uint256 chainId = block.chainid; 15 | 16 | if (chainId != 5) { 17 | revert ChainIdInvalid(chainId); 18 | } 19 | 20 | uint256 deployerPrivateKey = vm.envUint("TESTNET_KEY"); 21 | LooksRareProtocol looksRareProtocol = LooksRareProtocol(0x35C2215F2FFe8917B06454eEEaba189877F200cf); 22 | 23 | vm.startBroadcast(deployerPrivateKey); 24 | 25 | looksRareProtocol.updateAffiliateRate(0xdb5Ac292C5a3749e1feDc330294acBa7272294ce, 2_500); 26 | 27 | vm.stopBroadcast(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /scripts/deployment/Deployment.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | // Scripting tool 5 | import {Script} from "../../lib/forge-std/src/Script.sol"; 6 | 7 | // Core contracts 8 | import {LooksRareProtocol} from "../../contracts/LooksRareProtocol.sol"; 9 | import {TransferManager} from "../../contracts/TransferManager.sol"; 10 | import {StrategyCollectionOffer} from "../../contracts/executionStrategies/StrategyCollectionOffer.sol"; 11 | 12 | // Create2 factory interface 13 | import {IImmutableCreate2Factory} from "../../contracts/interfaces/IImmutableCreate2Factory.sol"; 14 | 15 | // Other contracts 16 | import {OrderValidatorV2A} from "../../contracts/helpers/OrderValidatorV2A.sol"; 17 | 18 | contract Deployment is Script { 19 | IImmutableCreate2Factory private constant IMMUTABLE_CREATE2_FACTORY = 20 | IImmutableCreate2Factory(0x0000000000FFe8B47B3e2130213B802212439497); 21 | 22 | error ChainIdInvalid(uint256 chainId); 23 | 24 | address public weth; 25 | 26 | // address public royaltyFeeRegistry; 27 | 28 | uint16 internal constant _standardProtocolFeeBp = uint16(50); 29 | uint16 internal constant _minTotalFeeBp = uint16(50); 30 | uint16 internal constant _maxProtocolFeeBp = uint16(200); 31 | 32 | function run() external { 33 | uint256 chainId = block.chainid; 34 | uint256 deployerPrivateKey; 35 | 36 | if (chainId == 1) { 37 | weth = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; 38 | // royaltyFeeRegistry = 0x55010472a93921a117aAD9b055c141060c8d8022; 39 | deployerPrivateKey = vm.envUint("MAINNET_KEY"); 40 | } else if (chainId == 5) { 41 | weth = 0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6; 42 | // royaltyFeeRegistry = 0x12405dB79325D06a973aD913D6e9BdA1343cD526; 43 | deployerPrivateKey = vm.envUint("TESTNET_KEY"); 44 | } else if (chainId == 11155111) { 45 | weth = 0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14; 46 | deployerPrivateKey = vm.envUint("TESTNET_KEY"); 47 | } else { 48 | revert ChainIdInvalid(chainId); 49 | } 50 | 51 | vm.startBroadcast(deployerPrivateKey); 52 | 53 | // 1. Deploy TransferManager 54 | address transferManagerAddress = IMMUTABLE_CREATE2_FACTORY.safeCreate2({ 55 | salt: vm.envBytes32("TRANSFER_MANAGER_SALT"), 56 | initializationCode: abi.encodePacked( 57 | type(TransferManager).creationCode, 58 | abi.encode(vm.envAddress("OWNER_ADDRESS")) 59 | ) 60 | }); 61 | 62 | // 2. Transfer 1 wei to LooksRareProtocol before it is deployed. 63 | // It cannot receive ETH after it is deployed. 64 | payable(0x0000000000E655fAe4d56241588680F86E3b2377).transfer(1 wei); 65 | 66 | // 3. Deploy LooksRareProtocol 67 | address looksRareProtocolAddress = IMMUTABLE_CREATE2_FACTORY.safeCreate2({ 68 | salt: vm.envBytes32("LOOKSRARE_PROTOCOL_SALT"), 69 | initializationCode: abi.encodePacked( 70 | type(LooksRareProtocol).creationCode, 71 | abi.encode( 72 | vm.envAddress("OWNER_ADDRESS"), 73 | vm.envAddress("PROTOCOL_FEE_RECIPIENT_ADDRESS"), 74 | transferManagerAddress, 75 | weth 76 | ) 77 | ) 78 | }); 79 | 80 | // 4. Other operations 81 | TransferManager(transferManagerAddress).allowOperator(looksRareProtocolAddress); 82 | LooksRareProtocol(looksRareProtocolAddress).updateCurrencyStatus(address(0), true); 83 | LooksRareProtocol(looksRareProtocolAddress).updateCurrencyStatus(weth, true); 84 | 85 | // 5. Deploy OrderValidatorV2A, this needs to happen after updateCreatorFeeManager 86 | // as the order validator calls creator fee manager to retrieve the royalty fee registry 87 | new OrderValidatorV2A(looksRareProtocolAddress); 88 | 89 | // 6. Deploy StrategyCollectionOffer 90 | address strategyCollectionOfferAddress = IMMUTABLE_CREATE2_FACTORY.safeCreate2({ 91 | salt: vm.envBytes32("STRATEGY_COLLECTION_OFFER_SALT"), 92 | initializationCode: type(StrategyCollectionOffer).creationCode 93 | }); 94 | 95 | LooksRareProtocol(looksRareProtocolAddress).addStrategy( 96 | _standardProtocolFeeBp, 97 | _minTotalFeeBp, 98 | _maxProtocolFeeBp, 99 | StrategyCollectionOffer.executeCollectionStrategyWithTakerAsk.selector, 100 | true, 101 | strategyCollectionOfferAddress 102 | ); 103 | 104 | LooksRareProtocol(looksRareProtocolAddress).addStrategy( 105 | _standardProtocolFeeBp, 106 | _minTotalFeeBp, 107 | _maxProtocolFeeBp, 108 | StrategyCollectionOffer.executeCollectionStrategyWithTakerAskWithProof.selector, 109 | true, 110 | strategyCollectionOfferAddress 111 | ); 112 | 113 | vm.stopBroadcast(); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /scripts/deployment/ProtocolFeeRecipientDeployment.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | // Scripting tool 5 | import {Script} from "../../lib/forge-std/src/Script.sol"; 6 | 7 | // Core contracts 8 | import {ProtocolFeeRecipient} from "../../contracts/ProtocolFeeRecipient.sol"; 9 | 10 | contract ProtocolFeeRecipientDeployment is Script { 11 | error ChainIdInvalid(uint256 chainId); 12 | 13 | // WETH 14 | address public weth; 15 | address private feeSharingSetter; 16 | 17 | function run() external { 18 | uint256 chainId = block.chainid; 19 | uint256 deployerPrivateKey; 20 | 21 | if (chainId == 1) { 22 | weth = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; 23 | feeSharingSetter = 0x5924A28caAF1cc016617874a2f0C3710d881f3c1; 24 | deployerPrivateKey = vm.envUint("MAINNET_KEY"); 25 | } else if (chainId == 5) { 26 | weth = 0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6; 27 | feeSharingSetter = 0x3d1E1521b659b0C942836DeF24dd254aBdEb873b; 28 | deployerPrivateKey = vm.envUint("TESTNET_KEY"); 29 | } else if (chainId == 11155111) { 30 | weth = 0x7b79995e5f793A07Bc00c21412e50Ecae098E7f9; 31 | feeSharingSetter = 0x8a7c709648160a5A1191D07dfAB316224E4C6b07; 32 | deployerPrivateKey = vm.envUint("TESTNET_KEY"); 33 | } else { 34 | revert ChainIdInvalid(chainId); 35 | } 36 | 37 | vm.startBroadcast(deployerPrivateKey); 38 | 39 | new ProtocolFeeRecipient(feeSharingSetter, weth); 40 | 41 | vm.stopBroadcast(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /test/foundry/BatchMakerCollectionOrders.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | // Strategies 5 | import {StrategyCollectionOffer} from "../../contracts/executionStrategies/StrategyCollectionOffer.sol"; 6 | 7 | // Libraries 8 | import {OrderStructs} from "../../contracts/libraries/OrderStructs.sol"; 9 | 10 | // Constants 11 | import {ONE_HUNDRED_PERCENT_IN_BP} from "../../contracts/constants/NumericConstants.sol"; 12 | 13 | // Base test 14 | import {ProtocolBase} from "./ProtocolBase.t.sol"; 15 | 16 | // Helpers 17 | import {EIP712MerkleTree} from "./utils/EIP712MerkleTree.sol"; 18 | 19 | // Enums 20 | import {CollectionType} from "../../contracts/enums/CollectionType.sol"; 21 | import {QuoteType} from "../../contracts/enums/QuoteType.sol"; 22 | 23 | contract BatchMakerCollectionOrdersTest is ProtocolBase { 24 | StrategyCollectionOffer private strategy; 25 | uint256 private constant price = 1 ether; // Fixed price of sale 26 | EIP712MerkleTree private eip712MerkleTree; 27 | 28 | function setUp() public { 29 | _setUp(); 30 | 31 | strategy = new StrategyCollectionOffer(); 32 | vm.prank(_owner); 33 | looksRareProtocol.addStrategy( 34 | _standardProtocolFeeBp, 35 | _minTotalFeeBp, 36 | _maxProtocolFeeBp, 37 | StrategyCollectionOffer.executeCollectionStrategyWithTakerAsk.selector, 38 | true, 39 | address(strategy) 40 | ); 41 | 42 | _setUpUsers(); 43 | eip712MerkleTree = new EIP712MerkleTree(looksRareProtocol); 44 | } 45 | 46 | function testTakerAskMultipleOrdersSignedERC721(uint256 numberOrders) public { 47 | vm.assume(numberOrders > 0 && numberOrders <= 10); 48 | 49 | mockERC721.batchMint(takerUser, numberOrders); 50 | 51 | OrderStructs.Maker[] memory makerBids = _createBatchMakerBids(numberOrders); 52 | 53 | (bytes memory signature, ) = eip712MerkleTree.sign(makerUserPK, makerBids, 0); 54 | 55 | for (uint256 i; i < numberOrders; i++) { 56 | // To prove that we only need 1 signature for multiple collection offers, 57 | // we are not using the signature from the sign call in the loop. 58 | (, OrderStructs.MerkleTree memory merkleTree) = eip712MerkleTree.sign(makerUserPK, makerBids, i); 59 | 60 | OrderStructs.Maker memory makerBidToExecute = makerBids[i]; 61 | 62 | // Verify validity 63 | _assertValidMakerOrderWithMerkleTree(makerBidToExecute, signature, merkleTree); 64 | 65 | OrderStructs.Taker memory takerOrder = OrderStructs.Taker(takerUser, abi.encode(i)); 66 | 67 | // Execute taker ask transaction 68 | vm.prank(takerUser); 69 | looksRareProtocol.executeTakerAsk(takerOrder, makerBidToExecute, signature, merkleTree, _EMPTY_AFFILIATE); 70 | 71 | // Maker user has received the asset 72 | assertEq(mockERC721.ownerOf(i), makerUser); 73 | 74 | // Verify the nonce is marked as executed 75 | assertEq( 76 | looksRareProtocol.userOrderNonce(makerUser, makerBidToExecute.orderNonce), 77 | MAGIC_VALUE_ORDER_NONCE_EXECUTED 78 | ); 79 | } 80 | 81 | uint256 totalValue = price * numberOrders; 82 | assertEq( 83 | weth.balanceOf(makerUser), 84 | _initialWETHBalanceUser - totalValue, 85 | "Maker bid user should pay the whole price" 86 | ); 87 | assertEq( 88 | weth.balanceOf(takerUser), 89 | _initialWETHBalanceUser + 90 | (totalValue * _sellerProceedBpWithStandardProtocolFeeBp) / 91 | ONE_HUNDRED_PERCENT_IN_BP, 92 | "Taker ask user should receive 99.5% of the whole price (0.5% protocol)" 93 | ); 94 | } 95 | 96 | function _createBatchMakerBids(uint256 numberOrders) private view returns (OrderStructs.Maker[] memory makerBids) { 97 | makerBids = new OrderStructs.Maker[](numberOrders); 98 | for (uint256 i; i < numberOrders; i++) { 99 | makerBids[i] = _createSingleItemMakerOrder({ 100 | quoteType: QuoteType.Bid, 101 | globalNonce: 0, 102 | subsetNonce: 0, 103 | strategyId: 1, 104 | collectionType: CollectionType.ERC721, 105 | orderNonce: i, // incremental 106 | collection: address(mockERC721), 107 | currency: address(weth), 108 | signer: makerUser, 109 | price: price, 110 | itemId: 0 // Not used 111 | }); 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /test/foundry/BatchOrderTypehashRegistry.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | import {Test} from "../../lib/forge-std/src/Test.sol"; 5 | 6 | import {BatchOrderTypehashRegistry} from "../../contracts/BatchOrderTypehashRegistry.sol"; 7 | 8 | // Shared errors 9 | import {MerkleProofTooLarge} from "../../contracts/errors/SharedErrors.sol"; 10 | 11 | contract BatchOrderTypehashRegistryInheriter is BatchOrderTypehashRegistry { 12 | function getBatchOrderTypehash(uint256 height) external pure returns (bytes32 typehash) { 13 | return _getBatchOrderTypehash(height); 14 | } 15 | } 16 | 17 | contract BatchOrderTypehashRegistryTest is Test { 18 | function testHash() public { 19 | BatchOrderTypehashRegistryInheriter registry = new BatchOrderTypehashRegistryInheriter(); 20 | bytes32 root = hex"6942000000000000000000000000000000000000000000000000000000000000"; 21 | assertEq( 22 | registry.hashBatchOrder(root, 1), 23 | hex"8f0c85a215cff55fe39cf62ee7a1e0b5205a8ade02ff12ffee9ece02d626ffc3" 24 | ); 25 | assertEq( 26 | registry.hashBatchOrder(root, 2), 27 | hex"f04a7d8a4688cf084b00b51ed583de7e5a19e59b073635e00a45a474899e89ec" 28 | ); 29 | assertEq( 30 | registry.hashBatchOrder(root, 3), 31 | hex"56ef3bb8c564d19cfe494776934aa5e7ed84c41ae609d5f10e726f76281dd30b" 32 | ); 33 | assertEq( 34 | registry.hashBatchOrder(root, 4), 35 | hex"2b0cb021eacab73e36d9ac9a04c1cf58589ff5bb4dc0d9b88ec29f67358ca812" 36 | ); 37 | assertEq( 38 | registry.hashBatchOrder(root, 5), 39 | hex"253b3cc8d591a8b01fc8967cefe3ac3d0e078b884d96aa589f1ffd4536921bbb" 40 | ); 41 | assertEq( 42 | registry.hashBatchOrder(root, 6), 43 | hex"7e4c4a2c5806fc4765bca325e8b78ccf9633bd1c7643144a56210293daefcbca" 44 | ); 45 | assertEq( 46 | registry.hashBatchOrder(root, 7), 47 | hex"e8e39cebe7137f0fadf6b88ba611044ac79c0168444eab66ca53bddd0c5fb717" 48 | ); 49 | assertEq( 50 | registry.hashBatchOrder(root, 8), 51 | hex"6e02f123509255ed381c7552de5e2ac1c1ea401a23e026e2452f01b70564affb" 52 | ); 53 | assertEq( 54 | registry.hashBatchOrder(root, 9), 55 | hex"7eeb4a7fe4655841fdd66f8ecfcf6cd261d50eafabbaebb10f63f5fe84ddddc9" 56 | ); 57 | assertEq( 58 | registry.hashBatchOrder(root, 10), 59 | hex"a96dee8b7b88deda5d50b55f641ca08c1ee00825eeb1db7a324f392fa0b8bb83" 60 | ); 61 | } 62 | 63 | function testGetTypehash() public { 64 | BatchOrderTypehashRegistryInheriter registry = new BatchOrderTypehashRegistryInheriter(); 65 | bytes memory makerOrderString = bytes( 66 | "Maker(" 67 | "uint8 quoteType," 68 | "uint256 globalNonce," 69 | "uint256 subsetNonce," 70 | "uint256 orderNonce," 71 | "uint256 strategyId," 72 | "uint8 collectionType," 73 | "address collection," 74 | "address currency," 75 | "address signer," 76 | "uint256 startTime," 77 | "uint256 endTime," 78 | "uint256 price," 79 | "uint256[] itemIds," 80 | "uint256[] amounts," 81 | "bytes additionalParameters" 82 | ")" 83 | ); 84 | 85 | assertEq( 86 | registry.getBatchOrderTypehash(1), 87 | keccak256(bytes.concat(bytes("BatchOrder(Maker[2] tree)"), makerOrderString)) 88 | ); 89 | 90 | assertEq( 91 | registry.getBatchOrderTypehash(2), 92 | keccak256(bytes.concat(bytes("BatchOrder(Maker[2][2] tree)"), makerOrderString)) 93 | ); 94 | 95 | assertEq( 96 | registry.getBatchOrderTypehash(3), 97 | keccak256(bytes.concat(bytes("BatchOrder(Maker[2][2][2] tree)"), makerOrderString)) 98 | ); 99 | 100 | assertEq( 101 | registry.getBatchOrderTypehash(4), 102 | keccak256(bytes.concat(bytes("BatchOrder(Maker[2][2][2][2] tree)"), makerOrderString)) 103 | ); 104 | 105 | assertEq( 106 | registry.getBatchOrderTypehash(5), 107 | keccak256(bytes.concat(bytes("BatchOrder(Maker[2][2][2][2][2] tree)"), makerOrderString)) 108 | ); 109 | 110 | assertEq( 111 | registry.getBatchOrderTypehash(6), 112 | keccak256(bytes.concat(bytes("BatchOrder(Maker[2][2][2][2][2][2] tree)"), makerOrderString)) 113 | ); 114 | 115 | assertEq( 116 | registry.getBatchOrderTypehash(7), 117 | keccak256(bytes.concat(bytes("BatchOrder(Maker[2][2][2][2][2][2][2] tree)"), makerOrderString)) 118 | ); 119 | 120 | assertEq( 121 | registry.getBatchOrderTypehash(8), 122 | keccak256(bytes.concat(bytes("BatchOrder(Maker[2][2][2][2][2][2][2][2] tree)"), makerOrderString)) 123 | ); 124 | 125 | assertEq( 126 | registry.getBatchOrderTypehash(9), 127 | keccak256(bytes.concat(bytes("BatchOrder(Maker[2][2][2][2][2][2][2][2][2] tree)"), makerOrderString)) 128 | ); 129 | 130 | assertEq( 131 | registry.getBatchOrderTypehash(10), 132 | keccak256(bytes.concat(bytes("BatchOrder(Maker[2][2][2][2][2][2][2][2][2][2] tree)"), makerOrderString)) 133 | ); 134 | } 135 | 136 | function testGetTypehashMerkleProofTooLarge(uint256 height) public { 137 | vm.assume(height > 10); 138 | 139 | BatchOrderTypehashRegistryInheriter registry = new BatchOrderTypehashRegistryInheriter(); 140 | vm.expectRevert(abi.encodeWithSelector(MerkleProofTooLarge.selector, height)); 141 | registry.getBatchOrderTypehash(height); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /test/foundry/CurrencyManager.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | // LooksRare unopinionated libraries 5 | import {IOwnableTwoSteps} from "@looksrare/contracts-libs/contracts/interfaces/IOwnableTwoSteps.sol"; 6 | 7 | // Core contracts 8 | import {CurrencyManager, ICurrencyManager} from "../../contracts/CurrencyManager.sol"; 9 | 10 | // Other mocks and utils 11 | import {TestHelpers} from "./utils/TestHelpers.sol"; 12 | import {TestParameters} from "./utils/TestParameters.sol"; 13 | import {MockERC20} from "../mock/MockERC20.sol"; 14 | 15 | contract CurrencyManagerTest is TestHelpers, TestParameters, ICurrencyManager { 16 | CurrencyManager private currencyManager; 17 | MockERC20 private mockERC20; 18 | 19 | function setUp() public asPrankedUser(_owner) { 20 | currencyManager = new CurrencyManager(_owner); 21 | mockERC20 = new MockERC20(); 22 | } 23 | 24 | function testUpdateCurrencyStatus() public asPrankedUser(_owner) { 25 | // Set to true 26 | vm.expectEmit({checkTopic1: true, checkTopic2: false, checkTopic3: false, checkData: true}); 27 | emit CurrencyStatusUpdated(address(mockERC20), true); 28 | currencyManager.updateCurrencyStatus(address(mockERC20), true); 29 | assertTrue(currencyManager.isCurrencyAllowed(address(mockERC20))); 30 | 31 | // Set to false 32 | vm.expectEmit({checkTopic1: true, checkTopic2: false, checkTopic3: false, checkData: true}); 33 | emit CurrencyStatusUpdated(address(mockERC20), false); 34 | currencyManager.updateCurrencyStatus(address(mockERC20), false); 35 | assertFalse(currencyManager.isCurrencyAllowed(address(mockERC20))); 36 | } 37 | 38 | function testUpdateCurrencyStatusNotOwner() public { 39 | vm.expectRevert(IOwnableTwoSteps.NotOwner.selector); 40 | currencyManager.updateCurrencyStatus(address(mockERC20), true); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /test/foundry/DomainSeparatorUpdates.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | // LooksRare unopinionated libraries 5 | import {IOwnableTwoSteps} from "@looksrare/contracts-libs/contracts/interfaces/IOwnableTwoSteps.sol"; 6 | import {SignatureEOAInvalid} from "@looksrare/contracts-libs/contracts/errors/SignatureCheckerErrors.sol"; 7 | 8 | // Libraries and interfaces 9 | import {OrderStructs} from "../../contracts/libraries/OrderStructs.sol"; 10 | import {ILooksRareProtocol} from "../../contracts/interfaces/ILooksRareProtocol.sol"; 11 | 12 | // Base test 13 | import {ProtocolBase} from "./ProtocolBase.t.sol"; 14 | 15 | contract DomainSeparatorUpdatesTest is ProtocolBase { 16 | function setUp() public { 17 | _setUp(); 18 | } 19 | 20 | function testUpdateDomainSeparator(uint64 newChainId) public asPrankedUser(_owner) { 21 | vm.assume(newChainId != block.chainid); 22 | 23 | vm.chainId(newChainId); 24 | vm.expectEmit({checkTopic1: true, checkTopic2: false, checkTopic3: false, checkData: true}); 25 | emit NewDomainSeparator(); 26 | looksRareProtocol.updateDomainSeparator(); 27 | assertEq(looksRareProtocol.chainId(), newChainId); 28 | assertEq( 29 | looksRareProtocol.domainSeparator(), 30 | keccak256( 31 | abi.encode( 32 | keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), 33 | keccak256("LooksRareProtocol"), 34 | keccak256(bytes("2")), 35 | newChainId, 36 | address(looksRareProtocol) 37 | ) 38 | ) 39 | ); 40 | } 41 | 42 | function testCannotTradeIfDomainSeparatorHasBeenUpdated(uint64 newChainId) public { 43 | vm.assume(newChainId != block.chainid); 44 | 45 | _setUpUsers(); 46 | 47 | // ChainId update 48 | vm.chainId(newChainId); 49 | 50 | // Owner updates the domain separator 51 | vm.prank(_owner); 52 | looksRareProtocol.updateDomainSeparator(); 53 | 54 | (OrderStructs.Maker memory makerAsk, OrderStructs.Taker memory takerBid) = _createMockMakerAskAndTakerBid( 55 | address(mockERC721) 56 | ); 57 | 58 | bytes memory signature = _signMakerOrder(makerAsk, makerUserPK); 59 | 60 | // Mint asset 61 | mockERC721.mint(makerUser, makerAsk.itemIds[0]); 62 | 63 | vm.prank(takerUser); 64 | vm.expectRevert(SignatureEOAInvalid.selector); 65 | looksRareProtocol.executeTakerBid{value: makerAsk.price}( 66 | takerBid, 67 | makerAsk, 68 | signature, 69 | _EMPTY_MERKLE_TREE, 70 | _EMPTY_AFFILIATE 71 | ); 72 | } 73 | 74 | function testCannotTradeIfChainIdHasChanged(uint64 newChainId) public { 75 | vm.assume(newChainId != block.chainid); 76 | 77 | _setUpUsers(); 78 | 79 | // ChainId update 80 | vm.chainId(newChainId); 81 | 82 | (OrderStructs.Maker memory makerAsk, OrderStructs.Taker memory takerBid) = _createMockMakerAskAndTakerBid( 83 | address(mockERC721) 84 | ); 85 | 86 | bytes memory signature = _signMakerOrder(makerAsk, makerUserPK); 87 | 88 | // Mint asset 89 | mockERC721.mint(makerUser, makerAsk.itemIds[0]); 90 | 91 | vm.prank(takerUser); 92 | vm.expectRevert(ILooksRareProtocol.ChainIdInvalid.selector); 93 | looksRareProtocol.executeTakerBid{value: makerAsk.price}( 94 | takerBid, 95 | makerAsk, 96 | signature, 97 | _EMPTY_MERKLE_TREE, 98 | _EMPTY_AFFILIATE 99 | ); 100 | } 101 | 102 | function testUpdateDomainSeparatorSameDomainSeparator() public asPrankedUser(_owner) { 103 | vm.expectRevert(SameDomainSeparator.selector); 104 | looksRareProtocol.updateDomainSeparator(); 105 | } 106 | 107 | function testUpdateDomainSeparatorNotOwner() public { 108 | vm.expectRevert(IOwnableTwoSteps.NotOwner.selector); 109 | looksRareProtocol.updateDomainSeparator(); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /test/foundry/InitialStates.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | // Interfaces 5 | import {IStrategyManager} from "../../contracts/interfaces/IStrategyManager.sol"; 6 | 7 | // Base test 8 | import {ProtocolBase} from "./ProtocolBase.t.sol"; 9 | 10 | contract InitialStatesTest is ProtocolBase, IStrategyManager { 11 | function setUp() public { 12 | _setUp(); 13 | } 14 | 15 | /** 16 | * Verify initial post-deployment states are as expected 17 | */ 18 | function testInitialStates() public { 19 | assertEq(looksRareProtocol.owner(), _owner); 20 | assertEq(looksRareProtocol.protocolFeeRecipient(), address(protocolFeeRecipient)); 21 | assertEq(address(looksRareProtocol.transferManager()), address(transferManager)); 22 | assertEq(looksRareProtocol.WETH(), address(weth)); 23 | assertEq(looksRareProtocol.chainId(), block.chainid); 24 | 25 | bytes32 domainSeparator = looksRareProtocol.domainSeparator(); 26 | bytes32 expectedDomainSeparator = keccak256( 27 | abi.encode( 28 | keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), 29 | keccak256("LooksRareProtocol"), 30 | keccak256(bytes("2")), 31 | block.chainid, 32 | address(looksRareProtocol) 33 | ) 34 | ); 35 | assertEq(domainSeparator, expectedDomainSeparator); 36 | 37 | ( 38 | bool strategyIsActive, 39 | uint16 strategyStandardProtocolFee, 40 | uint16 strategyMinTotalFee, 41 | uint16 strategyMaxProtocolFee, 42 | bytes4 strategySelector, 43 | bool strategyIsMakerBid, 44 | address strategyImplementation 45 | ) = looksRareProtocol.strategyInfo(0); 46 | 47 | assertTrue(strategyIsActive); 48 | assertEq(strategyStandardProtocolFee, _standardProtocolFeeBp); 49 | assertEq(strategyMinTotalFee, _minTotalFeeBp); 50 | assertEq(strategyMaxProtocolFee, _maxProtocolFeeBp); 51 | assertEq(strategySelector, _EMPTY_BYTES4); 52 | assertFalse(strategyIsMakerBid); 53 | assertEq(strategyImplementation, address(0)); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /test/foundry/ProtocolFeeRecipient.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | // LooksRare unopinionated libraries 5 | import {IERC20} from "@looksrare/contracts-libs/contracts/interfaces/generic/IERC20.sol"; 6 | import {IWETH} from "@looksrare/contracts-libs/contracts/interfaces/generic/IWETH.sol"; 7 | 8 | // Core contracts 9 | import {ProtocolFeeRecipient} from "../../contracts/ProtocolFeeRecipient.sol"; 10 | 11 | // Other mocks and utils 12 | import {MockERC20} from "../mock/MockERC20.sol"; 13 | import {TestParameters} from "./utils/TestParameters.sol"; 14 | 15 | contract ProtocolFeeRecipientTest is TestParameters { 16 | ProtocolFeeRecipient private protocolFeeRecipient; 17 | uint256 private feeSharingSetterInitialWETHBalance; 18 | 19 | address private constant FEE_SHARING_SETTER = 0x5924A28caAF1cc016617874a2f0C3710d881f3c1; 20 | uint256 private constant DUST = 0.69420 ether; 21 | 22 | function setUp() public { 23 | vm.createSelectFork(vm.rpcUrl("mainnet")); 24 | protocolFeeRecipient = new ProtocolFeeRecipient(FEE_SHARING_SETTER, WETH_MAINNET); 25 | feeSharingSetterInitialWETHBalance = IERC20(WETH_MAINNET).balanceOf(FEE_SHARING_SETTER); 26 | 27 | vm.deal(address(protocolFeeRecipient), 0); 28 | deal(WETH_MAINNET, address(protocolFeeRecipient), 0); 29 | } 30 | 31 | function test_TransferETH_NoWETHBalance_WithETHBalance() public { 32 | _sendETHToProtocolFeeRecipient(); 33 | 34 | protocolFeeRecipient.transferETH(); 35 | 36 | assertEq(address(protocolFeeRecipient).balance, 0); 37 | assertEq(IERC20(WETH_MAINNET).balanceOf(address(protocolFeeRecipient)), 0); 38 | assertEq(IERC20(WETH_MAINNET).balanceOf(FEE_SHARING_SETTER), feeSharingSetterInitialWETHBalance + 1 ether); 39 | } 40 | 41 | function test_TransferETH_WithWETHBalance_WithETHBalance() public { 42 | _sendETHToProtocolFeeRecipient(); 43 | _sendWETHToProtocolFeeRecipient(); 44 | 45 | protocolFeeRecipient.transferETH(); 46 | 47 | assertEq(address(protocolFeeRecipient).balance, 0); 48 | assertEq(IERC20(WETH_MAINNET).balanceOf(address(protocolFeeRecipient)), 0); 49 | assertEq( 50 | IERC20(WETH_MAINNET).balanceOf(FEE_SHARING_SETTER), 51 | feeSharingSetterInitialWETHBalance + 1 ether + DUST 52 | ); 53 | } 54 | 55 | function test_TransferETH_WithWETHBalance_NoETHBalance() public { 56 | _sendWETHToProtocolFeeRecipient(); 57 | 58 | protocolFeeRecipient.transferETH(); 59 | 60 | assertEq(address(protocolFeeRecipient).balance, 0); 61 | assertEq(IERC20(WETH_MAINNET).balanceOf(address(protocolFeeRecipient)), 0); 62 | assertEq(IERC20(WETH_MAINNET).balanceOf(FEE_SHARING_SETTER), feeSharingSetterInitialWETHBalance + DUST); 63 | } 64 | 65 | function test_TransferETH_RevertIf_NothingToTransfer() public { 66 | vm.expectRevert(ProtocolFeeRecipient.NothingToTransfer.selector); 67 | protocolFeeRecipient.transferETH(); 68 | } 69 | 70 | function test_TransferWETH() public { 71 | _sendWETHToProtocolFeeRecipient(); 72 | 73 | protocolFeeRecipient.transferERC20(WETH_MAINNET); 74 | 75 | assertEq(IERC20(WETH_MAINNET).balanceOf(address(protocolFeeRecipient)), 0); 76 | assertEq(IERC20(WETH_MAINNET).balanceOf(FEE_SHARING_SETTER), feeSharingSetterInitialWETHBalance + DUST); 77 | } 78 | 79 | function test_TransferWETH_RevertIf_NothingToTransfer() public { 80 | vm.expectRevert(ProtocolFeeRecipient.NothingToTransfer.selector); 81 | protocolFeeRecipient.transferERC20(WETH_MAINNET); 82 | } 83 | 84 | function test_TransferERC20() public { 85 | MockERC20 mockERC20 = new MockERC20(); 86 | mockERC20.mint(address(protocolFeeRecipient), DUST); 87 | 88 | protocolFeeRecipient.transferERC20(address(mockERC20)); 89 | 90 | assertEq(mockERC20.balanceOf(address(protocolFeeRecipient)), 0); 91 | assertEq(mockERC20.balanceOf(FEE_SHARING_SETTER), DUST); 92 | } 93 | 94 | function test_TransferERC20_RevertIf_NothingToTransfer() public { 95 | MockERC20 mockERC20 = new MockERC20(); 96 | vm.expectRevert(ProtocolFeeRecipient.NothingToTransfer.selector); 97 | protocolFeeRecipient.transferERC20(address(mockERC20)); 98 | } 99 | 100 | function _sendETHToProtocolFeeRecipient() private { 101 | (bool success, ) = address(protocolFeeRecipient).call{value: 1 ether}(""); 102 | assertTrue(success); 103 | assertEq(address(protocolFeeRecipient).balance, 1 ether); 104 | } 105 | 106 | function _sendWETHToProtocolFeeRecipient() private { 107 | IWETH(WETH_MAINNET).deposit{value: DUST}(); 108 | IERC20(WETH_MAINNET).transfer(address(protocolFeeRecipient), DUST); 109 | assertEq(IERC20(WETH_MAINNET).balanceOf(address(protocolFeeRecipient)), DUST); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /test/foundry/SignaturesEIP2098.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | // Libraries, interfaces, errors 5 | import "@looksrare/contracts-libs/contracts/errors/SignatureCheckerErrors.sol"; 6 | import {OrderStructs} from "../../contracts/libraries/OrderStructs.sol"; 7 | 8 | // Base test 9 | import {ProtocolBase} from "./ProtocolBase.t.sol"; 10 | 11 | // Constants 12 | import {ONE_HUNDRED_PERCENT_IN_BP} from "../../contracts/constants/NumericConstants.sol"; 13 | 14 | // Enums 15 | import {CollectionType} from "../../contracts/enums/CollectionType.sol"; 16 | import {QuoteType} from "../../contracts/enums/QuoteType.sol"; 17 | 18 | contract SignaturesEIP2098Test is ProtocolBase { 19 | function setUp() public { 20 | _setUp(); 21 | } 22 | 23 | function testCanSignValidMakerAskEIP2098(uint256 price, uint256 itemId) public { 24 | vm.assume(price <= 2 ether); 25 | 26 | _setUpUsers(); 27 | _setupRegistryRoyalties(address(mockERC721), _standardRoyaltyFee); 28 | 29 | (OrderStructs.Maker memory makerAsk, ) = _createMockMakerAskAndTakerBid(address(mockERC721)); 30 | makerAsk.price = price; 31 | makerAsk.itemIds[0] = itemId; 32 | 33 | bytes memory signature = _signMakerOrder(makerAsk, makerUserPK); 34 | 35 | // Mint asset 36 | mockERC721.mint(makerUser, makerAsk.itemIds[0]); 37 | 38 | // Adjust the signature 39 | signature = _eip2098Signature(signature); 40 | 41 | // Verify validity of maker ask order 42 | _assertValidMakerOrder(makerAsk, signature); 43 | } 44 | 45 | function testCanSignValidMakerBidEIP2098(uint256 price, uint256 itemId) public { 46 | vm.assume(price <= 2 ether); 47 | 48 | _setUpUsers(); 49 | _setupRegistryRoyalties(address(mockERC721), _standardRoyaltyFee); 50 | 51 | (OrderStructs.Maker memory makerBid, ) = _createMockMakerBidAndTakerAsk(address(mockERC721), address(weth)); 52 | makerBid.price = price; 53 | makerBid.itemIds[0] = itemId; 54 | 55 | bytes memory signature = _signMakerOrder(makerBid, makerUserPK); 56 | 57 | // Adjust the signature 58 | signature = _eip2098Signature(signature); 59 | 60 | // Mint asset 61 | mockERC721.mint(takerUser, makerBid.itemIds[0]); 62 | 63 | // Verify validity of maker bid order 64 | _assertValidMakerOrder(makerBid, signature); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /test/foundry/assembly/VerifyOrderTimestampValidityEquivalence.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | import {Test} from "../../../lib/forge-std/src/Test.sol"; 5 | 6 | // Assembly 7 | import {OutsideOfTimeRange_error_selector, OutsideOfTimeRange_error_length, Error_selector_offset} from "../../../contracts/constants/AssemblyConstants.sol"; 8 | 9 | contract NonAssemblyCode { 10 | error OutsideOfTimeRange(); 11 | 12 | function run(uint256 startTime, uint256 endTime) external view returns (bool) { 13 | if (startTime > block.timestamp || endTime < block.timestamp) revert OutsideOfTimeRange(); 14 | return true; 15 | } 16 | } 17 | 18 | contract AssemblyCode { 19 | function run(uint256 startTime, uint256 endTime) external view returns (bool) { 20 | assembly { 21 | if or(gt(startTime, timestamp()), lt(endTime, timestamp())) { 22 | mstore(0x00, OutsideOfTimeRange_error_selector) 23 | revert(Error_selector_offset, OutsideOfTimeRange_error_length) 24 | } 25 | } 26 | return true; 27 | } 28 | } 29 | 30 | contract VerifyOrderTimestampValidityEquivalenceTest is Test { 31 | AssemblyCode private assemblyCode; 32 | NonAssemblyCode private nonAssemblyCode; 33 | 34 | function setUp() public { 35 | assemblyCode = new AssemblyCode(); 36 | nonAssemblyCode = new NonAssemblyCode(); 37 | } 38 | 39 | /** 40 | * @dev The gap between start and end time is always at least 41 | * 3 seconds so that we can test the 2 boundaries as well 42 | * as the 2 timestamps inside the boundaries 43 | */ 44 | function testEquivalenceWithinBoundaries(uint256 startTime, uint256 endTime) public { 45 | vm.assume(endTime > 3 && startTime < endTime - 3); 46 | 47 | vm.warp(startTime); 48 | assertTrue(assemblyCode.run(startTime, endTime)); 49 | assertTrue(nonAssemblyCode.run(startTime, endTime)); 50 | 51 | vm.warp(startTime + 1); 52 | assertTrue(assemblyCode.run(startTime, endTime)); 53 | assertTrue(nonAssemblyCode.run(startTime, endTime)); 54 | 55 | vm.warp(endTime - 1); 56 | assertTrue(assemblyCode.run(startTime, endTime)); 57 | assertTrue(nonAssemblyCode.run(startTime, endTime)); 58 | 59 | vm.warp(endTime); 60 | assertTrue(assemblyCode.run(startTime, endTime)); 61 | assertTrue(nonAssemblyCode.run(startTime, endTime)); 62 | } 63 | 64 | function testEquivalenceTooEarly(uint256 startTime, uint256 endTime) public { 65 | vm.assume(startTime > 0 && startTime < endTime); 66 | 67 | vm.warp(startTime - 1); 68 | 69 | vm.expectRevert(NonAssemblyCode.OutsideOfTimeRange.selector); 70 | assemblyCode.run(startTime, endTime); 71 | 72 | vm.expectRevert(NonAssemblyCode.OutsideOfTimeRange.selector); 73 | nonAssemblyCode.run(startTime, endTime); 74 | } 75 | 76 | function testEquivalenceTooLate(uint256 startTime, uint256 endTime) public { 77 | vm.assume(endTime > 0 && endTime < type(uint256).max && startTime < endTime); 78 | 79 | vm.warp(endTime + 1); 80 | 81 | vm.expectRevert(NonAssemblyCode.OutsideOfTimeRange.selector); 82 | assemblyCode.run(startTime, endTime); 83 | 84 | vm.expectRevert(NonAssemblyCode.OutsideOfTimeRange.selector); 85 | nonAssemblyCode.run(startTime, endTime); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /test/foundry/executionStrategies/Chainlink/FloorFromChainlinkDiscountBasisPointsOrders.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | // LooksRare libraries 5 | import {IOwnableTwoSteps} from "@looksrare/contracts-libs/contracts/interfaces/IOwnableTwoSteps.sol"; 6 | 7 | // Libraries and interfaces 8 | import {OrderStructs} from "../../../../contracts/libraries/OrderStructs.sol"; 9 | import {IExecutionManager} from "../../../../contracts/interfaces/IExecutionManager.sol"; 10 | 11 | // Errors and constants 12 | import {OrderInvalid} from "../../../../contracts/errors/SharedErrors.sol"; 13 | import {ONE_HUNDRED_PERCENT_IN_BP} from "../../../../contracts/constants/NumericConstants.sol"; 14 | import {MAKER_ORDER_PERMANENTLY_INVALID_NON_STANDARD_SALE} from "../../../../contracts/constants/ValidationCodeConstants.sol"; 15 | 16 | // Strategies 17 | import {StrategyChainlinkFloor} from "../../../../contracts/executionStrategies/Chainlink/StrategyChainlinkFloor.sol"; 18 | 19 | // Mocks and other tests 20 | import {MockChainlinkAggregator} from "../../../mock/MockChainlinkAggregator.sol"; 21 | import {FloorFromChainlinkDiscountOrdersTest} from "./FloorFromChainlinkDiscountOrders.t.sol"; 22 | 23 | contract FloorFromChainlinkDiscountBasisPointsOrdersTest is FloorFromChainlinkDiscountOrdersTest { 24 | function setUp() public override { 25 | _setIsFixedAmount(0); 26 | _setDiscount(100); 27 | _setSelector( 28 | StrategyChainlinkFloor.executeBasisPointsDiscountCollectionOfferStrategyWithTakerAsk.selector, 29 | true 30 | ); 31 | super.setUp(); 32 | } 33 | 34 | function testInactiveStrategy() public { 35 | (OrderStructs.Maker memory makerBid, OrderStructs.Taker memory takerAsk) = _createMakerBidAndTakerAsk({ 36 | discount: discount 37 | }); 38 | 39 | bytes memory signature = _signMakerOrder(makerBid, makerUserPK); 40 | 41 | _setPriceFeed(); 42 | 43 | _assertOrderIsValid(makerBid); 44 | _assertValidMakerOrder(makerBid, signature); 45 | 46 | vm.prank(_owner); 47 | looksRareProtocol.updateStrategy(1, false, _standardProtocolFeeBp, _minTotalFeeBp); 48 | 49 | vm.expectRevert(abi.encodeWithSelector(IExecutionManager.StrategyNotAvailable.selector, 1)); 50 | _executeTakerAsk(takerAsk, makerBid, signature); 51 | } 52 | 53 | function testFloorFromChainlinkDiscountBasisPointsDesiredDiscountedPriceGreaterThanOrEqualToMaxPrice() public { 54 | // Floor price = 9.7 ETH, discount = 1%, desired price = 9.603 ETH 55 | // Max price = 9.5 ETH 56 | (OrderStructs.Maker memory makerBid, OrderStructs.Taker memory takerAsk) = _createMakerBidAndTakerAsk({ 57 | discount: discount 58 | }); 59 | 60 | makerBid.price = 9.5 ether; 61 | takerAsk.additionalParameters = abi.encode(42, 9.5 ether); 62 | 63 | bytes memory signature = _signMakerOrder(makerBid, makerUserPK); 64 | 65 | _setPriceFeed(); 66 | 67 | _assertOrderIsValid(makerBid); 68 | _assertValidMakerOrder(makerBid, signature); 69 | 70 | _executeTakerAsk(takerAsk, makerBid, signature); 71 | 72 | // Maker user has received the asset 73 | assertEq(mockERC721.ownerOf(42), makerUser); 74 | 75 | // Maker bid user pays the whole price 76 | assertEq(weth.balanceOf(makerUser), _initialWETHBalanceUser - 9.5 ether); 77 | // Taker ask user receives 99.5% of the whole price (0.5% protocol) 78 | assertEq(weth.balanceOf(takerUser), _initialWETHBalanceUser + 9.4525 ether); 79 | } 80 | 81 | function testFloorFromChainlinkDiscountBasisPointsDesiredDiscountedPriceLessThanMaxPrice() public { 82 | // Floor price = 9.7 ETH, discount = 3%, desired price = 9.409 ETH 83 | // Max price = 9.5 ETH 84 | (OrderStructs.Maker memory makerBid, OrderStructs.Taker memory takerAsk) = _createMakerBidAndTakerAsk({ 85 | discount: 300 86 | }); 87 | 88 | makerBid.price = 9.41 ether; 89 | 90 | bytes memory signature = _signMakerOrder(makerBid, makerUserPK); 91 | 92 | _setPriceFeed(); 93 | 94 | _assertOrderIsValid(makerBid); 95 | _assertValidMakerOrder(makerBid, signature); 96 | 97 | _executeTakerAsk(takerAsk, makerBid, signature); 98 | 99 | // Maker user has received the asset 100 | assertEq(mockERC721.ownerOf(42), makerUser); 101 | 102 | // Maker bid user pays the whole price 103 | assertEq(weth.balanceOf(makerUser), _initialWETHBalanceUser - 9.409 ether); 104 | // Taker ask user receives 99.5% of the whole price (0.5% protocol) 105 | assertEq(weth.balanceOf(takerUser), _initialWETHBalanceUser + 9.361955 ether); 106 | } 107 | 108 | function testFloorFromChainlinkDiscountBasisPointsDesiredDiscountBasisPointsGreaterThan10000() public { 109 | // Floor price = 9.7 ETH, discount = 100%, desired price = 0 110 | // Max price = 0 111 | (OrderStructs.Maker memory makerBid, OrderStructs.Taker memory takerAsk) = _createMakerBidAndTakerAsk({ 112 | discount: ONE_HUNDRED_PERCENT_IN_BP + 1 113 | }); 114 | 115 | bytes memory signature = _signMakerOrder(makerBid, makerUserPK); 116 | 117 | _setPriceFeed(); 118 | 119 | (bool isValid, bytes4 errorSelector) = strategyFloorFromChainlink.isMakerOrderValid(makerBid, selector); 120 | assertFalse(isValid); 121 | assertEq(errorSelector, OrderInvalid.selector); 122 | 123 | _assertMakerOrderReturnValidationCode(makerBid, signature, MAKER_ORDER_PERMANENTLY_INVALID_NON_STANDARD_SALE); 124 | 125 | vm.expectRevert(OrderInvalid.selector); 126 | _executeTakerAsk(takerAsk, makerBid, signature); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /test/foundry/executionStrategies/Chainlink/FloorFromChainlinkPremiumBasisPointsOrders.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | // Libraries and interfaces 5 | import {OrderStructs} from "../../../../contracts/libraries/OrderStructs.sol"; 6 | import {IExecutionManager} from "../../../../contracts/interfaces/IExecutionManager.sol"; 7 | 8 | // Strategies 9 | import {StrategyChainlinkFloor} from "../../../../contracts/executionStrategies/Chainlink/StrategyChainlinkFloor.sol"; 10 | 11 | // Other tests 12 | import {FloorFromChainlinkPremiumOrdersTest} from "./FloorFromChainlinkPremiumOrders.t.sol"; 13 | 14 | // Errors and constants 15 | import {STRATEGY_NOT_ACTIVE} from "../../../../contracts/constants/ValidationCodeConstants.sol"; 16 | 17 | /** 18 | * @notice The primary scenarios are tested in FloorFromChainlinkPremiumFixedAmountOrdersTest 19 | */ 20 | contract FloorFromChainlinkPremiumBasisPointsOrdersTest is FloorFromChainlinkPremiumOrdersTest { 21 | function setUp() public override { 22 | _setIsFixedAmount(0); 23 | _setPremium(100); 24 | _setSelector(StrategyChainlinkFloor.executeBasisPointsPremiumStrategyWithTakerBid.selector, false); 25 | super.setUp(); 26 | } 27 | 28 | function testInactiveStrategy() public { 29 | (OrderStructs.Maker memory makerAsk, OrderStructs.Taker memory takerBid) = _createMakerAskAndTakerBid({ 30 | premium: premium 31 | }); 32 | 33 | bytes memory signature = _signMakerOrder(makerAsk, makerUserPK); 34 | 35 | _setPriceFeed(); 36 | 37 | vm.prank(_owner); 38 | looksRareProtocol.updateStrategy(1, false, _standardProtocolFeeBp, _minTotalFeeBp); 39 | 40 | _assertOrderIsValid(makerAsk); 41 | _assertMakerOrderReturnValidationCode(makerAsk, signature, STRATEGY_NOT_ACTIVE); 42 | 43 | vm.expectRevert(abi.encodeWithSelector(IExecutionManager.StrategyNotAvailable.selector, 1)); 44 | _executeTakerBid(takerBid, makerAsk, signature); 45 | } 46 | 47 | function testFloorFromChainlinkPremiumBasisPointsDesiredSalePriceGreaterThanMinPrice() public { 48 | // Floor price = 9.7 ETH, premium = 1%, desired price = 9.797 ETH 49 | // Min price = 9.7 ETH 50 | (OrderStructs.Maker memory makerAsk, OrderStructs.Taker memory takerBid) = _createMakerAskAndTakerBid({ 51 | premium: premium 52 | }); 53 | 54 | _testFloorFromChainlinkPremiumBasisPointsDesiredSalePriceGreaterThanOrEqualToMinPrice(makerAsk, takerBid); 55 | } 56 | 57 | function testFloorFromChainlinkPremiumBasisPointsDesiredSalePriceEqualToMinPrice() public { 58 | // Floor price = 9.7 ETH, premium = 1%, desired price = 9.797 ETH 59 | // Min price = 9.7 ETH 60 | (OrderStructs.Maker memory makerAsk, OrderStructs.Taker memory takerBid) = _createMakerAskAndTakerBid({ 61 | premium: premium 62 | }); 63 | makerAsk.price = 9.797 ether; 64 | 65 | _testFloorFromChainlinkPremiumBasisPointsDesiredSalePriceGreaterThanOrEqualToMinPrice(makerAsk, takerBid); 66 | } 67 | 68 | function _testFloorFromChainlinkPremiumBasisPointsDesiredSalePriceGreaterThanOrEqualToMinPrice( 69 | OrderStructs.Maker memory newMakerAsk, 70 | OrderStructs.Taker memory newTakerBid 71 | ) private { 72 | bytes memory signature = _signMakerOrder(newMakerAsk, makerUserPK); 73 | 74 | _setPriceFeed(); 75 | 76 | // Verify it is valid 77 | _assertOrderIsValid(newMakerAsk); 78 | _assertValidMakerOrder(newMakerAsk, signature); 79 | 80 | _executeTakerBid(newTakerBid, newMakerAsk, signature); 81 | 82 | // Taker user has received the asset 83 | assertEq(mockERC721.ownerOf(1), takerUser); 84 | // Taker bid user pays the whole price 85 | assertEq(weth.balanceOf(takerUser), _initialWETHBalanceUser - 9.797 ether); 86 | // Maker ask user receives 99.5% of the whole price (0.5% protocol) 87 | assertEq(weth.balanceOf(makerUser), _initialWETHBalanceUser + 9.748015 ether); 88 | } 89 | 90 | function testFloorFromChainlinkPremiumBasisPointsDesiredSalePriceLessThanMinPrice() public { 91 | (, , , , , , address implementation) = looksRareProtocol.strategyInfo(1); 92 | strategyFloorFromChainlink = StrategyChainlinkFloor(implementation); 93 | 94 | // Floor price = 9.7 ETH, premium = 1%, desired price = 9.797 ETH 95 | // Min price = 9.8 ETH 96 | (OrderStructs.Maker memory makerAsk, OrderStructs.Taker memory takerBid) = _createMakerAskAndTakerBid({ 97 | premium: premium 98 | }); 99 | 100 | makerAsk.price = 9.8 ether; 101 | takerBid.additionalParameters = abi.encode(makerAsk.price); 102 | 103 | bytes memory signature = _signMakerOrder(makerAsk, makerUserPK); 104 | 105 | _setPriceFeed(); 106 | 107 | // Verify it is valid 108 | _assertOrderIsValid(makerAsk); 109 | _assertValidMakerOrder(makerAsk, signature); 110 | 111 | _executeTakerBid(takerBid, makerAsk, signature); 112 | 113 | // Taker user has received the asset 114 | assertEq(mockERC721.ownerOf(1), takerUser); 115 | 116 | // Taker bid user pays the whole price 117 | assertEq(weth.balanceOf(takerUser), _initialWETHBalanceUser - 9.8 ether); 118 | // Maker ask user receives 99.5% of the whole price (0.5% protocol) 119 | assertEq(weth.balanceOf(makerUser), _initialWETHBalanceUser + 9.751 ether); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /test/foundry/executionStrategies/Chainlink/FloorFromChainlinkPremiumFixedAmountOrders.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | // Libraries and interfaces 5 | import {OrderStructs} from "../../../../contracts/libraries/OrderStructs.sol"; 6 | import {IExecutionManager} from "../../../../contracts/interfaces/IExecutionManager.sol"; 7 | 8 | // Strategies 9 | import {StrategyChainlinkFloor} from "../../../../contracts/executionStrategies/Chainlink/StrategyChainlinkFloor.sol"; 10 | 11 | // Other tests 12 | import {FloorFromChainlinkPremiumOrdersTest} from "./FloorFromChainlinkPremiumOrders.t.sol"; 13 | 14 | // Errors and constants 15 | import {STRATEGY_NOT_ACTIVE} from "../../../../contracts/constants/ValidationCodeConstants.sol"; 16 | 17 | contract FloorFromChainlinkPremiumFixedAmountOrdersTest is FloorFromChainlinkPremiumOrdersTest { 18 | function setUp() public override { 19 | _setPremium(0.1 ether); 20 | _setIsFixedAmount(1); 21 | _setSelector(StrategyChainlinkFloor.executeFixedPremiumStrategyWithTakerBid.selector, false); 22 | super.setUp(); 23 | } 24 | 25 | function testInactiveStrategy() public { 26 | (OrderStructs.Maker memory makerAsk, OrderStructs.Taker memory takerBid) = _createMakerAskAndTakerBid({ 27 | premium: premium 28 | }); 29 | bytes memory signature = _signMakerOrder(makerAsk, makerUserPK); 30 | 31 | _setPriceFeed(); 32 | 33 | vm.prank(_owner); 34 | looksRareProtocol.updateStrategy(1, false, _standardProtocolFeeBp, _minTotalFeeBp); 35 | 36 | _assertOrderIsValid(makerAsk); 37 | _assertMakerOrderReturnValidationCode(makerAsk, signature, STRATEGY_NOT_ACTIVE); 38 | 39 | vm.expectRevert(abi.encodeWithSelector(IExecutionManager.StrategyNotAvailable.selector, 1)); 40 | _executeTakerBid(takerBid, makerAsk, signature); 41 | } 42 | 43 | function testFloorFromChainlinkPremiumFixedAmountDesiredSalePriceGreaterThanMinPrice() public { 44 | (, , , , , , address implementation) = looksRareProtocol.strategyInfo(1); 45 | strategyFloorFromChainlink = StrategyChainlinkFloor(implementation); 46 | 47 | // Floor price = 9.7 ETH, premium = 0.1 ETH, desired price = 9.8 ETH 48 | // Min price = 9.7 ETH 49 | (OrderStructs.Maker memory makerAsk, OrderStructs.Taker memory takerBid) = _createMakerAskAndTakerBid({ 50 | premium: premium 51 | }); 52 | _testFloorFromChainlinkPremiumFixedAmountDesiredSalePriceGreaterThanOrEqualToMinPrice(makerAsk, takerBid); 53 | } 54 | 55 | function testFloorFromChainlinkPremiumFixedAmountDesiredSalePriceEqualToMinPrice() public { 56 | (, , , , , , address implementation) = looksRareProtocol.strategyInfo(1); 57 | strategyFloorFromChainlink = StrategyChainlinkFloor(implementation); 58 | 59 | // Floor price = 9.7 ETH, premium = 0.1 ETH, desired price = 9.8 ETH 60 | // Min price = 9.8 ETH 61 | (OrderStructs.Maker memory makerAsk, OrderStructs.Taker memory takerBid) = _createMakerAskAndTakerBid({ 62 | premium: premium 63 | }); 64 | makerAsk.price = 9.8 ether; 65 | _testFloorFromChainlinkPremiumFixedAmountDesiredSalePriceGreaterThanOrEqualToMinPrice(makerAsk, takerBid); 66 | } 67 | 68 | function _testFloorFromChainlinkPremiumFixedAmountDesiredSalePriceGreaterThanOrEqualToMinPrice( 69 | OrderStructs.Maker memory makerAsk, 70 | OrderStructs.Taker memory takerBid 71 | ) public { 72 | bytes memory signature = _signMakerOrder(makerAsk, makerUserPK); 73 | 74 | _setPriceFeed(); 75 | 76 | _assertOrderIsValid(makerAsk); 77 | _assertValidMakerOrder(makerAsk, signature); 78 | 79 | _executeTakerBid(takerBid, makerAsk, signature); 80 | 81 | // Taker user has received the asset 82 | assertEq(mockERC721.ownerOf(1), takerUser); 83 | // Taker bid user pays the whole price 84 | assertEq(weth.balanceOf(takerUser), _initialWETHBalanceUser - 9.8 ether); 85 | // Maker ask user receives 99.5% of the whole price (0.5% protocol) 86 | assertEq(weth.balanceOf(makerUser), _initialWETHBalanceUser + 9.751 ether); 87 | } 88 | 89 | function testFloorFromChainlinkPremiumFixedAmountDesiredSalePriceLessThanMinPrice() public { 90 | (, , , , , , address implementation) = looksRareProtocol.strategyInfo(1); 91 | strategyFloorFromChainlink = StrategyChainlinkFloor(implementation); 92 | 93 | // Floor price = 9.7 ETH, premium = 0.1 ETH, desired price = 9.8 ETH 94 | // Min price = 9.9 ETH 95 | (OrderStructs.Maker memory makerAsk, OrderStructs.Taker memory takerBid) = _createMakerAskAndTakerBid({ 96 | premium: premium 97 | }); 98 | 99 | makerAsk.price = 9.9 ether; 100 | takerBid.additionalParameters = abi.encode(makerAsk.price); 101 | 102 | bytes memory signature = _signMakerOrder(makerAsk, makerUserPK); 103 | 104 | _setPriceFeed(); 105 | 106 | _assertOrderIsValid(makerAsk); 107 | _assertValidMakerOrder(makerAsk, signature); 108 | 109 | _executeTakerBid(takerBid, makerAsk, signature); 110 | 111 | // Taker user has received the asset 112 | assertEq(mockERC721.ownerOf(1), takerUser); 113 | 114 | // Taker bid user pays the whole price 115 | assertEq(weth.balanceOf(takerUser), _initialWETHBalanceUser - 9.9 ether); 116 | // Maker ask user receives 99.5% of the whole price (0.5% protocol) 117 | assertEq(weth.balanceOf(makerUser), _initialWETHBalanceUser + 9.8505 ether); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /test/foundry/utils/BytesLib.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | /* 5 | * @title Solidity Bytes Arrays Utils 6 | * @author Gonçalo Sá 7 | * @notice We only copied the `slice` function from the original https://github.com/GNSPS/solidity-bytes-utils 8 | * as that's the only function needed. 9 | * @dev Bytes tightly packed arrays utility library for ethereum contracts written in Solidity. 10 | * The library lets you slice bytes arrays in memory. 11 | */ 12 | library BytesLib { 13 | function slice(bytes memory _bytes, uint256 _start, uint256 _length) internal pure returns (bytes memory) { 14 | require(_length + 31 >= _length, "slice_overflow"); 15 | require(_bytes.length >= _start + _length, "slice_outOfBounds"); 16 | 17 | bytes memory tempBytes; 18 | 19 | assembly { 20 | switch iszero(_length) 21 | case 0 { 22 | // Get a location of some free memory and store it in tempBytes as 23 | // Solidity does for memory variables. 24 | tempBytes := mload(0x40) 25 | 26 | // The first word of the slice result is potentially a partial 27 | // word read from the original array. To read it, we calculate 28 | // the length of that partial word and start copying that many 29 | // bytes into the array. The first word we copy will start with 30 | // data we don't care about, but the last `lengthmod` bytes will 31 | // land at the beginning of the contents of the new array. When 32 | // we're done copying, we overwrite the full first word with 33 | // the actual length of the slice. 34 | let lengthmod := and(_length, 31) 35 | 36 | // The multiplication in the next line is necessary 37 | // because when slicing multiples of 32 bytes (lengthmod == 0) 38 | // the following copy loop was copying the origin's length 39 | // and then ending prematurely not copying everything it should. 40 | let mc := add(add(tempBytes, lengthmod), mul(0x20, iszero(lengthmod))) 41 | let end := add(mc, _length) 42 | 43 | for { 44 | // The multiplication in the next line has the same exact purpose 45 | // as the one above. 46 | let cc := add(add(add(_bytes, lengthmod), mul(0x20, iszero(lengthmod))), _start) 47 | } lt(mc, end) { 48 | mc := add(mc, 0x20) 49 | cc := add(cc, 0x20) 50 | } { 51 | mstore(mc, mload(cc)) 52 | } 53 | 54 | mstore(tempBytes, _length) 55 | 56 | //update free-memory pointer 57 | //allocating the array padded to 32 bytes like the compiler does now 58 | mstore(0x40, and(add(mc, 31), not(31))) 59 | } 60 | //if we want a zero-length slice let's just return a zero-length array 61 | default { 62 | tempBytes := mload(0x40) 63 | //zero out the 32 bytes slice we are about to return 64 | //we need to do it because Solidity does not garbage collect 65 | mstore(tempBytes, 0) 66 | 67 | mstore(0x40, add(tempBytes, 0x20)) 68 | } 69 | } 70 | 71 | return tempBytes; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /test/foundry/utils/EIP712MerkleTree.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | // Forge test 5 | import {Test} from "forge-std/Test.sol"; 6 | 7 | // Libraries 8 | import {OrderStructs} from "../../../contracts/libraries/OrderStructs.sol"; 9 | 10 | // Core contracts 11 | import {LooksRareProtocol} from "../../../contracts/LooksRareProtocol.sol"; 12 | 13 | // Utils 14 | import {MerkleWithPosition} from "./MerkleWithPosition.sol"; 15 | import {MathLib} from "./MathLib.sol"; 16 | 17 | // Constants 18 | import {MAX_CALLDATA_PROOF_LENGTH} from "../../../contracts/constants/NumericConstants.sol"; 19 | 20 | contract EIP712MerkleTree is Test { 21 | using OrderStructs for OrderStructs.Maker; 22 | 23 | LooksRareProtocol private looksRareProtocol; 24 | 25 | constructor(LooksRareProtocol _looksRareProtocol) { 26 | looksRareProtocol = _looksRareProtocol; 27 | } 28 | 29 | function sign( 30 | uint256 privateKey, 31 | OrderStructs.Maker[] memory makerOrders, 32 | uint256 makerOrderIndex 33 | ) external returns (bytes memory signature, OrderStructs.MerkleTree memory merkleTree) { 34 | uint256 bidCount = makerOrders.length; 35 | uint256 treeHeight = MathLib.log2(bidCount); 36 | if (2 ** treeHeight != bidCount || treeHeight == 0) { 37 | treeHeight += 1; 38 | } 39 | bytes32 batchOrderTypehash = _getBatchOrderTypehash(treeHeight); 40 | uint256 leafCount = 2 ** treeHeight; 41 | OrderStructs.MerkleTreeNode[] memory leaves = new OrderStructs.MerkleTreeNode[](leafCount); 42 | 43 | for (uint256 i; i < bidCount; i++) { 44 | leaves[i] = OrderStructs.MerkleTreeNode({ 45 | value: makerOrders[i].hash(), 46 | position: i % 2 == 0 47 | ? OrderStructs.MerkleTreeNodePosition.Left 48 | : OrderStructs.MerkleTreeNodePosition.Right 49 | }); 50 | } 51 | 52 | bytes32 emptyMakerOrderHash = _emptyMakerOrderHash(); 53 | for (uint256 i = bidCount; i < leafCount; i++) { 54 | leaves[i] = OrderStructs.MerkleTreeNode({ 55 | value: emptyMakerOrderHash, 56 | position: i % 2 == 0 57 | ? OrderStructs.MerkleTreeNodePosition.Left 58 | : OrderStructs.MerkleTreeNodePosition.Right 59 | }); 60 | } 61 | 62 | MerkleWithPosition merkle = new MerkleWithPosition(); 63 | OrderStructs.MerkleTreeNode[] memory proof = merkle.getProof(leaves, makerOrderIndex); 64 | bytes32 root = merkle.getRoot(leaves); 65 | 66 | signature = _sign(privateKey, batchOrderTypehash, root); 67 | merkleTree = OrderStructs.MerkleTree({root: root, proof: proof}); 68 | } 69 | 70 | function _emptyMakerOrderHash() private pure returns (bytes32 makerOrderHash) { 71 | OrderStructs.Maker memory makerOrder; 72 | makerOrderHash = makerOrder.hash(); 73 | } 74 | 75 | function _sign( 76 | uint256 privateKey, 77 | bytes32 batchOrderTypehash, 78 | bytes32 root 79 | ) private view returns (bytes memory signature) { 80 | bytes32 digest = keccak256(abi.encode(batchOrderTypehash, root)); 81 | 82 | bytes32 domainSeparator = looksRareProtocol.domainSeparator(); 83 | 84 | (uint8 v, bytes32 r, bytes32 s) = vm.sign( 85 | privateKey, 86 | keccak256(abi.encodePacked("\x19\x01", domainSeparator, digest)) 87 | ); 88 | 89 | signature = abi.encodePacked(r, s, v); 90 | } 91 | 92 | function _getBatchOrderTypehash(uint256 treeHeight) private pure returns (bytes32 batchOrderTypehash) { 93 | if (treeHeight == 1) { 94 | batchOrderTypehash = hex"9661287f7a4aa4867db46a2453ee15bebac4e8fc25667a58718da658f15de643"; 95 | } else if (treeHeight == 2) { 96 | batchOrderTypehash = hex"a54ab330ea9e1dfccee2b86f3666989e7fbd479704416c757c8de8e820142a08"; 97 | } else if (treeHeight == 3) { 98 | batchOrderTypehash = hex"93390f5d45ede9dea305f16aec86b2472af4f823851637f1b7019ad0775cea49"; 99 | } else if (treeHeight == 4) { 100 | batchOrderTypehash = hex"9dda2c8358da895e43d574bb15954ce5727b22e923a2d8f28261f297bce42f0b"; 101 | } else if (treeHeight == 5) { 102 | batchOrderTypehash = hex"92dc717124e161262f9d10c7079e7d54dc51271893fba54aa4a0f270fecdcc98"; 103 | } else if (treeHeight == 6) { 104 | batchOrderTypehash = hex"ce02aee5a7a35d40d974463c4c6e5534954fb07a7e7bc966fee268a15337bfd8"; 105 | } else if (treeHeight == 7) { 106 | batchOrderTypehash = hex"f7a65efd167a18f7091b2bb929d687dd94503cf0a43620487055ed7d6b727559"; 107 | } else if (treeHeight == 8) { 108 | batchOrderTypehash = hex"def24acacad1318b664520f7c10e8bc6d1e7f6f6f7c8b031e70624ceb42266a6"; 109 | } else if (treeHeight == 9) { 110 | batchOrderTypehash = hex"4cb4080dc4e7bae88b4dc4307ad5117fa4f26195998a1b5f40368809d7f4c7f2"; 111 | } else if (treeHeight == 10) { 112 | batchOrderTypehash = hex"f8b1f864164d8d6e0b45f1399bd711223117a4ab0b057a9c2d7779e86a7c88db"; 113 | } else if (treeHeight == 11) { 114 | batchOrderTypehash = hex"4787f505db237e03a7193c312d5159add8a5705278e1c7dcf92ab87126cbe490"; 115 | } else if (treeHeight == 12) { 116 | batchOrderTypehash = hex"7a6517e5a16c56b29947b57b748aa91736987376e1a366d948e7a802a9df3431"; 117 | } else if (treeHeight == 13) { 118 | batchOrderTypehash = hex"35806d347e9929042ce209d143da48f100f0ff0cbdb1fde68cf13af8059d79df"; 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /test/foundry/utils/ERC1271Wallet.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | import {ERC1271WalletMock} from "openzeppelin-contracts/contracts/mocks/ERC1271WalletMock.sol"; 5 | 6 | contract ERC1271Wallet is ERC1271WalletMock { 7 | constructor(address originalOwner) ERC1271WalletMock(originalOwner) {} 8 | 9 | function onERC1155Received(address, address, uint256, uint256, bytes calldata) external pure returns (bytes4) { 10 | return this.onERC1155Received.selector; 11 | } 12 | 13 | function onERC1155BatchReceived( 14 | address, 15 | address, 16 | uint256[] calldata, 17 | uint256[] calldata, 18 | bytes calldata 19 | ) external pure returns (bytes4) { 20 | return this.onERC1155BatchReceived.selector; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /test/foundry/utils/GasGriefer.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | contract GasGriefer { 5 | receive() external payable { 6 | uint256 count; 7 | while (true) { 8 | count += 1; 9 | } 10 | } 11 | 12 | function isValidSignature(bytes32, bytes memory) external pure returns (bytes4 magicValue) { 13 | magicValue = this.isValidSignature.selector; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test/foundry/utils/MaliciousERC1271Wallet.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | import {ILooksRareProtocol} from "../../../contracts/interfaces/ILooksRareProtocol.sol"; 5 | import {OrderStructs} from "../../../contracts/libraries/OrderStructs.sol"; 6 | 7 | abstract contract MaliciousERC1271Wallet { 8 | enum FunctionToReenter { 9 | None, 10 | ExecuteTakerAsk, 11 | ExecuteTakerBid, 12 | ExecuteMultipleTakerBids 13 | } 14 | 15 | ILooksRareProtocol internal immutable looksRareProtocol; 16 | FunctionToReenter internal functionToReenter; 17 | 18 | constructor(address _looksRareProtocol) { 19 | looksRareProtocol = ILooksRareProtocol(_looksRareProtocol); 20 | } 21 | 22 | function setFunctionToReenter(FunctionToReenter _functionToReenter) external { 23 | functionToReenter = _functionToReenter; 24 | } 25 | 26 | function isValidSignature(bytes32, bytes calldata) external virtual returns (bytes4 magicValue) { 27 | magicValue = this.isValidSignature.selector; 28 | } 29 | 30 | function onERC1155Received(address, address, uint256, uint256, bytes calldata) external virtual returns (bytes4) { 31 | return this.onERC1155Received.selector; 32 | } 33 | 34 | function onERC1155BatchReceived( 35 | address, 36 | address, 37 | uint256[] calldata, 38 | uint256[] calldata, 39 | bytes calldata 40 | ) external virtual returns (bytes4) { 41 | return this.onERC1155BatchReceived.selector; 42 | } 43 | 44 | function _executeTakerAsk(bytes memory signature) internal { 45 | OrderStructs.Taker memory takerAsk; 46 | OrderStructs.Maker memory makerBid; 47 | OrderStructs.MerkleTree memory merkleTree; 48 | 49 | looksRareProtocol.executeTakerAsk(takerAsk, makerBid, signature, merkleTree, address(this)); 50 | } 51 | 52 | function _executeTakerBid(bytes memory signature) internal { 53 | OrderStructs.Taker memory takerBid; 54 | OrderStructs.Maker memory makerAsk; 55 | OrderStructs.MerkleTree memory merkleTree; 56 | 57 | looksRareProtocol.executeTakerBid(takerBid, makerAsk, signature, merkleTree, address(this)); 58 | } 59 | 60 | function _executeMultipleTakerBids() internal { 61 | OrderStructs.Taker[] memory takerBids = new OrderStructs.Taker[](2); 62 | OrderStructs.Maker[] memory makerAsks = new OrderStructs.Maker[](2); 63 | bytes[] memory signatures = new bytes[](2); 64 | OrderStructs.MerkleTree[] memory merkleTrees = new OrderStructs.MerkleTree[](2); 65 | 66 | looksRareProtocol.executeMultipleTakerBids(takerBids, makerAsks, signatures, merkleTrees, address(this), false); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /test/foundry/utils/MaliciousIsValidSignatureERC1271Wallet.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | import {MaliciousERC1271Wallet} from "./MaliciousERC1271Wallet.sol"; 5 | 6 | contract MaliciousIsValidSignatureERC1271Wallet is MaliciousERC1271Wallet { 7 | constructor(address _looksRareProtocol) MaliciousERC1271Wallet(_looksRareProtocol) {} 8 | 9 | function isValidSignature(bytes32, bytes calldata signature) external override returns (bytes4 magicValue) { 10 | if (functionToReenter == FunctionToReenter.ExecuteTakerAsk) { 11 | _executeTakerAsk(signature); 12 | } else if (functionToReenter == FunctionToReenter.ExecuteTakerBid) { 13 | _executeTakerBid(signature); 14 | } else if (functionToReenter == FunctionToReenter.ExecuteMultipleTakerBids) { 15 | _executeMultipleTakerBids(); 16 | } 17 | 18 | magicValue = this.isValidSignature.selector; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /test/foundry/utils/MaliciousOnERC1155ReceivedERC1271Wallet.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | import {MaliciousERC1271Wallet} from "./MaliciousERC1271Wallet.sol"; 5 | 6 | contract MaliciousOnERC1155ReceivedERC1271Wallet is MaliciousERC1271Wallet { 7 | constructor(address _looksRareProtocol) MaliciousERC1271Wallet(_looksRareProtocol) {} 8 | 9 | function onERC1155Received(address, address, uint256, uint256, bytes memory) external override returns (bytes4) { 10 | if (functionToReenter == FunctionToReenter.ExecuteTakerAsk) { 11 | _executeTakerAsk(new bytes(0)); 12 | } else if (functionToReenter == FunctionToReenter.ExecuteTakerBid) { 13 | _executeTakerBid(new bytes(0)); 14 | } else if (functionToReenter == FunctionToReenter.ExecuteMultipleTakerBids) { 15 | _executeMultipleTakerBids(); 16 | } 17 | 18 | return this.onERC1155Received.selector; 19 | } 20 | 21 | function onERC1155BatchReceived( 22 | address, 23 | address, 24 | uint256[] calldata, 25 | uint256[] calldata, 26 | bytes calldata 27 | ) external override returns (bytes4) { 28 | if (functionToReenter == FunctionToReenter.ExecuteTakerAsk) { 29 | _executeTakerAsk(new bytes(0)); 30 | } else if (functionToReenter == FunctionToReenter.ExecuteTakerBid) { 31 | _executeTakerBid(new bytes(0)); 32 | } else if (functionToReenter == FunctionToReenter.ExecuteMultipleTakerBids) { 33 | _executeMultipleTakerBids(); 34 | } 35 | 36 | return this.onERC1155BatchReceived.selector; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /test/foundry/utils/MaliciousOnERC1155ReceivedTheThirdTimeERC1271Wallet.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | import {MaliciousERC1271Wallet} from "./MaliciousERC1271Wallet.sol"; 5 | 6 | contract MaliciousOnERC1155ReceivedTheThirdTimeERC1271Wallet is MaliciousERC1271Wallet { 7 | uint256 private isValidSignatureEnterCount; 8 | 9 | constructor(address _looksRareProtocol) MaliciousERC1271Wallet(_looksRareProtocol) {} 10 | 11 | function onERC1155Received(address, address, uint256, uint256, bytes memory) external override returns (bytes4) { 12 | if (++isValidSignatureEnterCount == 3) { 13 | if (functionToReenter == FunctionToReenter.ExecuteTakerAsk) { 14 | _executeTakerAsk(new bytes(0)); 15 | } else if (functionToReenter == FunctionToReenter.ExecuteTakerBid) { 16 | _executeTakerBid(new bytes(0)); 17 | } else if (functionToReenter == FunctionToReenter.ExecuteMultipleTakerBids) { 18 | _executeMultipleTakerBids(); 19 | } 20 | } 21 | 22 | return this.onERC1155Received.selector; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /test/foundry/utils/MathLib.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | /** 5 | * @author OpenZeppelin (last updated v4.8.0) (utils/math/Math.sol) 6 | * @dev Standard math utilities missing in the Solidity language. 7 | */ 8 | library MathLib { 9 | /** 10 | * @dev Return the log in base 2, rounded down, of a positive value. 11 | * Returns 0 if given 0. 12 | */ 13 | function log2(uint256 value) internal pure returns (uint256) { 14 | uint256 result = 0; 15 | unchecked { 16 | if (value >> 128 > 0) { 17 | value >>= 128; 18 | result += 128; 19 | } 20 | if (value >> 64 > 0) { 21 | value >>= 64; 22 | result += 64; 23 | } 24 | if (value >> 32 > 0) { 25 | value >>= 32; 26 | result += 32; 27 | } 28 | if (value >> 16 > 0) { 29 | value >>= 16; 30 | result += 16; 31 | } 32 | if (value >> 8 > 0) { 33 | value >>= 8; 34 | result += 8; 35 | } 36 | if (value >> 4 > 0) { 37 | value >>= 4; 38 | result += 4; 39 | } 40 | if (value >> 2 > 0) { 41 | value >>= 2; 42 | result += 2; 43 | } 44 | if (value >> 1 > 0) { 45 | result += 1; 46 | } 47 | } 48 | return result; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /test/foundry/utils/MerkleWithPosition.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | import {OrderStructs} from "../../../contracts/libraries/OrderStructs.sol"; 5 | 6 | /** 7 | * @dev Modified from MurkyBase to add each node's position after hashing. 8 | * hashLeafPair does not sort the nodes to match EIP-712. 9 | */ 10 | contract MerkleWithPosition { 11 | /******************** 12 | * PROOF GENERATION * 13 | ********************/ 14 | 15 | function getRoot(OrderStructs.MerkleTreeNode[] memory data) public pure returns (bytes32) { 16 | require(data.length > 1, "won't generate root for single leaf"); 17 | while (data.length > 1) { 18 | data = hashLevel(data); 19 | } 20 | return data[0].value; 21 | } 22 | 23 | function getProof( 24 | OrderStructs.MerkleTreeNode[] memory data, 25 | uint256 node 26 | ) public pure returns (OrderStructs.MerkleTreeNode[] memory result) { 27 | require(data.length > 1, "won't generate proof for single leaf"); 28 | // The size of the proof is equal to the ceiling of log2(numLeaves) 29 | result = new OrderStructs.MerkleTreeNode[](log2ceilBitMagic(data.length)); 30 | uint256 pos = 0; 31 | 32 | // Two overflow risks: node, pos 33 | // node: max array size is 2**256-1. Largest index in the array will be 1 less than that. Also, 34 | // for dynamic arrays, size is limited to 2**64-1 35 | // pos: pos is bounded by log2(data.length), which should be less than type(uint256).max 36 | while (data.length > 1) { 37 | unchecked { 38 | if (node & 0x1 == 1) { 39 | result[pos] = data[node - 1]; 40 | } else if (node + 1 == data.length) { 41 | result[pos] = OrderStructs.MerkleTreeNode({ 42 | value: bytes32(0), 43 | position: OrderStructs.MerkleTreeNodePosition.Left 44 | }); 45 | } else { 46 | result[pos] = data[node + 1]; 47 | } 48 | ++pos; 49 | node /= 2; 50 | } 51 | data = hashLevel(data); 52 | } 53 | return result; 54 | } 55 | 56 | ///@dev function is private to prevent unsafe data from being passed 57 | function hashLevel( 58 | OrderStructs.MerkleTreeNode[] memory data 59 | ) private pure returns (OrderStructs.MerkleTreeNode[] memory result) { 60 | // Function is private, and all internal callers check that data.length >=2. 61 | // Underflow is not possible as lowest possible value for data/result index is 1 62 | // overflow should be safe as length is / 2 always. 63 | unchecked { 64 | uint256 length = data.length; 65 | if (length & 0x1 == 1) { 66 | result = new OrderStructs.MerkleTreeNode[](length / 2 + 1); 67 | bytes32 hashed = hashLeafPairs(data[length - 1].value, bytes32(0)); 68 | result[result.length - 1] = OrderStructs.MerkleTreeNode({ 69 | value: hashed, 70 | position: OrderStructs.MerkleTreeNodePosition.Left 71 | }); 72 | } else { 73 | result = new OrderStructs.MerkleTreeNode[](length / 2); 74 | } 75 | // pos is upper bounded by data.length / 2, so safe even if array is at max size 76 | uint256 pos = 0; 77 | bool nextIsLeft = true; 78 | for (uint256 i = 0; i < length - 1; i += 2) { 79 | bytes32 hashed = hashLeafPairs(data[i].value, data[i + 1].value); 80 | result[pos] = OrderStructs.MerkleTreeNode({ 81 | value: hashed, 82 | position: nextIsLeft 83 | ? OrderStructs.MerkleTreeNodePosition.Left 84 | : OrderStructs.MerkleTreeNodePosition.Right 85 | }); 86 | nextIsLeft = !nextIsLeft; 87 | ++pos; 88 | } 89 | } 90 | return result; 91 | } 92 | 93 | /****************** 94 | * MATH "LIBRARY" * 95 | ******************/ 96 | 97 | /// Original bitmagic adapted from https://github.com/paulrberg/prb-math/blob/main/contracts/PRBMath.sol 98 | /// @dev Note that x assumed > 1 99 | function log2ceilBitMagic(uint256 x) public pure returns (uint256) { 100 | if (x <= 1) { 101 | return 0; 102 | } 103 | uint256 msb = 0; 104 | uint256 _x = x; 105 | if (x >= 2 ** 128) { 106 | x >>= 128; 107 | msb += 128; 108 | } 109 | if (x >= 2 ** 64) { 110 | x >>= 64; 111 | msb += 64; 112 | } 113 | if (x >= 2 ** 32) { 114 | x >>= 32; 115 | msb += 32; 116 | } 117 | if (x >= 2 ** 16) { 118 | x >>= 16; 119 | msb += 16; 120 | } 121 | if (x >= 2 ** 8) { 122 | x >>= 8; 123 | msb += 8; 124 | } 125 | if (x >= 2 ** 4) { 126 | x >>= 4; 127 | msb += 4; 128 | } 129 | if (x >= 2 ** 2) { 130 | x >>= 2; 131 | msb += 2; 132 | } 133 | if (x >= 2 ** 1) { 134 | msb += 1; 135 | } 136 | 137 | uint256 lsb = (~_x + 1) & _x; 138 | if ((lsb == _x) && (msb > 0)) { 139 | return msb; 140 | } else { 141 | return msb + 1; 142 | } 143 | } 144 | 145 | function hashLeafPairs(bytes32 left, bytes32 right) public pure returns (bytes32 _hash) { 146 | assembly { 147 | mstore(0x0, left) 148 | mstore(0x20, right) 149 | _hash := keccak256(0x0, 0x40) 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /test/foundry/utils/MockOrderGenerator.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | // Generic interfaces 5 | import {IERC165} from "@looksrare/contracts-libs/contracts/interfaces/generic/IERC165.sol"; 6 | 7 | // Libraries 8 | import {OrderStructs} from "../../../contracts/libraries/OrderStructs.sol"; 9 | 10 | // Other helpers 11 | import {ProtocolHelpers} from "../utils/ProtocolHelpers.sol"; 12 | 13 | // Enums 14 | import {CollectionType} from "../../../contracts/enums/CollectionType.sol"; 15 | import {QuoteType} from "../../../contracts/enums/QuoteType.sol"; 16 | 17 | contract MockOrderGenerator is ProtocolHelpers { 18 | function _createMockMakerAskAndTakerBid( 19 | address collection 20 | ) internal view returns (OrderStructs.Maker memory newMakerAsk, OrderStructs.Taker memory newTakerBid) { 21 | CollectionType collectionType = _getCollectionType(collection); 22 | 23 | newMakerAsk = _createSingleItemMakerOrder({ 24 | quoteType: QuoteType.Ask, 25 | globalNonce: 0, 26 | subsetNonce: 0, 27 | strategyId: STANDARD_SALE_FOR_FIXED_PRICE_STRATEGY, 28 | collectionType: collectionType, 29 | orderNonce: 0, 30 | collection: collection, 31 | currency: ETH, 32 | signer: makerUser, 33 | price: 1 ether, 34 | itemId: 420 35 | }); 36 | 37 | newTakerBid = OrderStructs.Taker(takerUser, abi.encode()); 38 | } 39 | 40 | function _createMockMakerBidAndTakerAsk( 41 | address collection, 42 | address currency 43 | ) internal view returns (OrderStructs.Maker memory newMakerBid, OrderStructs.Taker memory newTakerAsk) { 44 | CollectionType collectionType = _getCollectionType(collection); 45 | 46 | newMakerBid = _createSingleItemMakerOrder({ 47 | quoteType: QuoteType.Bid, 48 | globalNonce: 0, 49 | subsetNonce: 0, 50 | strategyId: STANDARD_SALE_FOR_FIXED_PRICE_STRATEGY, 51 | collectionType: collectionType, 52 | orderNonce: 0, 53 | collection: collection, 54 | currency: currency, 55 | signer: makerUser, 56 | price: 1 ether, 57 | itemId: 420 58 | }); 59 | 60 | newTakerAsk = OrderStructs.Taker(takerUser, abi.encode()); 61 | } 62 | 63 | function _createMockMakerAskAndTakerBidWithBundle( 64 | address collection, 65 | uint256 numberTokens 66 | ) internal view returns (OrderStructs.Maker memory newMakerAsk, OrderStructs.Taker memory newTakerBid) { 67 | CollectionType collectionType = _getCollectionType(collection); 68 | 69 | (uint256[] memory itemIds, uint256[] memory amounts) = _setBundleItemIdsAndAmounts( 70 | collectionType, 71 | numberTokens 72 | ); 73 | 74 | newMakerAsk = _createMultiItemMakerOrder({ 75 | quoteType: QuoteType.Ask, 76 | globalNonce: 0, 77 | subsetNonce: 0, 78 | strategyId: STANDARD_SALE_FOR_FIXED_PRICE_STRATEGY, 79 | collectionType: collectionType, 80 | orderNonce: 0, 81 | collection: collection, 82 | currency: ETH, 83 | signer: makerUser, 84 | price: 1 ether, 85 | itemIds: itemIds, 86 | amounts: amounts 87 | }); 88 | 89 | newTakerBid = OrderStructs.Taker(takerUser, abi.encode()); 90 | } 91 | 92 | function _createMockMakerBidAndTakerAskWithBundle( 93 | address collection, 94 | address currency, 95 | uint256 numberTokens 96 | ) internal view returns (OrderStructs.Maker memory newMakerBid, OrderStructs.Taker memory newTakerAsk) { 97 | CollectionType collectionType = _getCollectionType(collection); 98 | 99 | (uint256[] memory itemIds, uint256[] memory amounts) = _setBundleItemIdsAndAmounts( 100 | collectionType, 101 | numberTokens 102 | ); 103 | 104 | newMakerBid = _createMultiItemMakerOrder({ 105 | quoteType: QuoteType.Bid, 106 | globalNonce: 0, 107 | subsetNonce: 0, 108 | strategyId: STANDARD_SALE_FOR_FIXED_PRICE_STRATEGY, 109 | collectionType: collectionType, 110 | orderNonce: 0, 111 | collection: collection, 112 | currency: currency, 113 | signer: makerUser, 114 | price: 1 ether, 115 | itemIds: itemIds, 116 | amounts: amounts 117 | }); 118 | 119 | newTakerAsk = OrderStructs.Taker(takerUser, abi.encode()); 120 | } 121 | 122 | function _getCollectionType(address collection) private view returns (CollectionType collectionType) { 123 | collectionType = CollectionType.ERC721; 124 | 125 | // If ERC1155, adjust the collection type 126 | if (IERC165(collection).supportsInterface(0xd9b67a26)) { 127 | collectionType = CollectionType.ERC1155; 128 | } 129 | } 130 | 131 | function _setBundleItemIdsAndAmounts( 132 | CollectionType collectionType, 133 | uint256 numberTokens 134 | ) private pure returns (uint256[] memory itemIds, uint256[] memory amounts) { 135 | itemIds = new uint256[](numberTokens); 136 | amounts = new uint256[](numberTokens); 137 | 138 | for (uint256 i; i < itemIds.length; i++) { 139 | itemIds[i] = i; 140 | if (collectionType != CollectionType.ERC1155) { 141 | amounts[i] = 1; 142 | } else { 143 | amounts[i] = 1 + i; 144 | } 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /test/foundry/utils/ProtocolHelpers.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | // Dependencies 5 | import {BatchOrderTypehashRegistry} from "../../../contracts/BatchOrderTypehashRegistry.sol"; 6 | 7 | // Libraries 8 | import {OrderStructs} from "../../../contracts/libraries/OrderStructs.sol"; 9 | 10 | // Other tests 11 | import {TestHelpers} from "./TestHelpers.sol"; 12 | import {TestParameters} from "./TestParameters.sol"; 13 | 14 | // Enums 15 | import {CollectionType} from "../../../contracts/enums/CollectionType.sol"; 16 | import {QuoteType} from "../../../contracts/enums/QuoteType.sol"; 17 | 18 | contract ProtocolHelpers is TestHelpers, TestParameters { 19 | using OrderStructs for OrderStructs.Maker; 20 | 21 | bytes32 internal _domainSeparator; 22 | 23 | receive() external payable {} 24 | 25 | function _createSingleItemMakerOrder( 26 | QuoteType quoteType, 27 | uint256 globalNonce, 28 | uint256 subsetNonce, 29 | uint256 strategyId, 30 | CollectionType collectionType, 31 | uint256 orderNonce, 32 | address collection, 33 | address currency, 34 | address signer, 35 | uint256 price, 36 | uint256 itemId 37 | ) internal view returns (OrderStructs.Maker memory makerOrder) { 38 | uint256[] memory itemIds = new uint256[](1); 39 | itemIds[0] = itemId; 40 | uint256[] memory amounts = new uint256[](1); 41 | amounts[0] = 1; 42 | 43 | makerOrder = OrderStructs.Maker({ 44 | quoteType: quoteType, 45 | globalNonce: globalNonce, 46 | subsetNonce: subsetNonce, 47 | orderNonce: orderNonce, 48 | strategyId: strategyId, 49 | collectionType: collectionType, 50 | collection: collection, 51 | currency: currency, 52 | signer: signer, 53 | startTime: block.timestamp, 54 | endTime: block.timestamp + 1, 55 | price: price, 56 | itemIds: itemIds, 57 | amounts: amounts, 58 | additionalParameters: abi.encode() 59 | }); 60 | } 61 | 62 | function _createMultiItemMakerOrder( 63 | QuoteType quoteType, 64 | uint256 globalNonce, 65 | uint256 subsetNonce, 66 | uint256 strategyId, 67 | CollectionType collectionType, 68 | uint256 orderNonce, 69 | address collection, 70 | address currency, 71 | address signer, 72 | uint256 price, 73 | uint256[] memory itemIds, 74 | uint256[] memory amounts 75 | ) internal view returns (OrderStructs.Maker memory newMakerBid) { 76 | newMakerBid = OrderStructs.Maker({ 77 | quoteType: quoteType, 78 | globalNonce: globalNonce, 79 | subsetNonce: subsetNonce, 80 | orderNonce: orderNonce, 81 | strategyId: strategyId, 82 | collectionType: collectionType, 83 | collection: collection, 84 | currency: currency, 85 | signer: signer, 86 | startTime: block.timestamp, 87 | endTime: block.timestamp + 1, 88 | price: price, 89 | itemIds: itemIds, 90 | amounts: amounts, 91 | additionalParameters: abi.encode() 92 | }); 93 | } 94 | 95 | function _signMakerOrder(OrderStructs.Maker memory maker, uint256 signerKey) internal view returns (bytes memory) { 96 | bytes32 orderHash = _computeOrderHash(maker); 97 | 98 | (uint8 v, bytes32 r, bytes32 s) = vm.sign( 99 | signerKey, 100 | keccak256(abi.encodePacked("\x19\x01", _domainSeparator, orderHash)) 101 | ); 102 | 103 | return abi.encodePacked(r, s, v); 104 | } 105 | 106 | function _computeOrderHash(OrderStructs.Maker memory maker) internal pure returns (bytes32) { 107 | return maker.hash(); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /test/foundry/utils/StrategyTestMultiFillCollectionOrder.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | // Libraries 5 | import {OrderStructs} from "../../../contracts/libraries/OrderStructs.sol"; 6 | 7 | // Custom errors 8 | import {OrderInvalid} from "../../../contracts/errors/SharedErrors.sol"; 9 | 10 | // Base strategy contracts 11 | import {BaseStrategy, IStrategy} from "../../../contracts/executionStrategies/BaseStrategy.sol"; 12 | 13 | // Enums 14 | import {CollectionType} from "../../../contracts/enums/CollectionType.sol"; 15 | 16 | contract StrategyTestMultiFillCollectionOrder is BaseStrategy { 17 | using OrderStructs for OrderStructs.Maker; 18 | 19 | // Address of the protocol 20 | address public immutable LOOKSRARE_PROTOCOL; 21 | 22 | // Tracks historical fills 23 | mapping(bytes32 => uint256) internal countItemsFilledForOrderHash; 24 | 25 | /** 26 | * @notice Constructor 27 | * @param _looksRareProtocol Address of the LooksRare protocol 28 | */ 29 | constructor(address _looksRareProtocol) { 30 | LOOKSRARE_PROTOCOL = _looksRareProtocol; 31 | } 32 | 33 | /** 34 | * @notice Execute collection strategy with taker ask order 35 | * @param takerAsk Taker ask struct (taker ask-specific parameters for the execution) 36 | * @param makerBid Maker bid struct (maker bid-specific parameters for the execution) 37 | */ 38 | function executeStrategyWithTakerAsk( 39 | OrderStructs.Taker calldata takerAsk, 40 | OrderStructs.Maker calldata makerBid 41 | ) external returns (uint256 price, uint256[] memory itemIds, uint256[] memory amounts, bool isNonceInvalidated) { 42 | if (msg.sender != LOOKSRARE_PROTOCOL) revert OrderInvalid(); 43 | // Only available for ERC721 44 | if (makerBid.collectionType != CollectionType.ERC721) revert OrderInvalid(); 45 | 46 | bytes32 orderHash = makerBid.hash(); 47 | uint256 countItemsFilled = countItemsFilledForOrderHash[orderHash]; 48 | uint256 countItemsFillable = makerBid.amounts[0]; 49 | 50 | price = makerBid.price; 51 | (itemIds, amounts) = abi.decode(takerAsk.additionalParameters, (uint256[], uint256[])); 52 | uint256 countItemsToFill = amounts.length; 53 | 54 | if ( 55 | countItemsToFill == 0 || 56 | makerBid.amounts.length != 1 || 57 | itemIds.length != countItemsToFill || 58 | countItemsFillable < countItemsToFill + countItemsFilled 59 | ) revert OrderInvalid(); 60 | 61 | price *= countItemsToFill; 62 | 63 | if (countItemsToFill + countItemsFilled == countItemsFillable) { 64 | delete countItemsFilledForOrderHash[orderHash]; 65 | isNonceInvalidated = true; 66 | } else { 67 | countItemsFilledForOrderHash[orderHash] += countItemsToFill; 68 | } 69 | } 70 | 71 | function isMakerOrderValid( 72 | OrderStructs.Maker calldata, 73 | bytes4 74 | ) external view override returns (bool isValid, bytes4 errorSelector) { 75 | // 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /test/foundry/utils/TestHelpers.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | import {Test} from "../../../lib/forge-std/src/Test.sol"; 5 | import {BytesLib} from "./BytesLib.sol"; 6 | 7 | abstract contract TestHelpers is Test { 8 | using BytesLib for bytes; 9 | 10 | modifier asPrankedUser(address user) { 11 | vm.startPrank(user); 12 | _; 13 | vm.stopPrank(); 14 | } 15 | 16 | /** 17 | * @dev Transforms a standard signature into an EIP2098 compliant signature 18 | * @param signature The secp256k1 65-bytes signature 19 | * @return eip2098Signature The 64-bytes EIP2098 compliant signature 20 | */ 21 | function _eip2098Signature(bytes memory signature) internal pure returns (bytes memory eip2098Signature) { 22 | eip2098Signature = signature.slice(0, 64); 23 | uint8 parityBit = uint8(eip2098Signature[32]) | ((uint8(signature[64]) % 27) << 7); 24 | eip2098Signature[32] = bytes1(parityBit); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test/foundry/utils/TestParameters.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | import {Test} from "../../../lib/forge-std/src/Test.sol"; 5 | import {OrderStructs} from "../../../contracts/libraries/OrderStructs.sol"; 6 | 7 | abstract contract TestParameters is Test { 8 | // Empty constants 9 | OrderStructs.MerkleTree internal _EMPTY_MERKLE_TREE; 10 | bytes4 internal constant _EMPTY_BYTES4 = bytes4(0); 11 | address internal constant _EMPTY_AFFILIATE = address(0); 12 | bytes32 public constant MAGIC_VALUE_ORDER_NONCE_EXECUTED = keccak256("ORDER_NONCE_EXECUTED"); 13 | 14 | // Addresses 15 | address internal constant _owner = address(42); 16 | address internal constant _sender = address(88); 17 | address internal constant _recipient = address(90); 18 | address internal constant _transferrer = address(100); 19 | address internal constant _royaltyRecipient = address(22); 20 | address internal constant _affiliate = address(2); 21 | 22 | // Currencies 23 | address internal constant ETH = address(0); 24 | address internal constant WETH_MAINNET = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; 25 | 26 | // Generic fee parameters 27 | uint16 internal constant _standardProtocolFeeBp = uint16(50); 28 | uint16 internal constant _minTotalFeeBp = uint16(50); 29 | uint16 internal constant _maxProtocolFeeBp = uint16(200); 30 | uint16 internal constant _standardRoyaltyFee = uint16(0); 31 | 32 | uint256 internal constant _sellerProceedBpWithStandardProtocolFeeBp = 9_950; 33 | 34 | // Public/Private keys for maker/taker user 35 | uint256 internal constant makerUserPK = 1; 36 | uint256 internal constant takerUserPK = 2; 37 | // it is equal to vm.addr(makerUserPK) 38 | address internal constant makerUser = 0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf; 39 | // it is equal to vm.addr(takerUserPK) 40 | address internal constant takerUser = 0x2B5AD5c4795c026514f8317c7a215E218DcCD6cF; 41 | 42 | // Initial balances 43 | // @dev The balances are on purpose different across users to make sure the tests are properly checking the assertion 44 | uint256 internal constant _initialETHBalanceUser = 100 ether; 45 | uint256 internal constant _initialWETHBalanceUser = 10 ether; 46 | uint256 internal constant _initialETHBalanceRoyaltyRecipient = 10 ether; 47 | uint256 internal constant _initialWETHBalanceRoyaltyRecipient = 25 ether; 48 | uint256 internal constant _initialETHBalanceOwner = 50 ether; 49 | uint256 internal constant _initialWETHBalanceOwner = 15 ether; 50 | uint256 internal constant _initialETHBalanceAffiliate = 30 ether; 51 | uint256 internal constant _initialWETHBalanceAffiliate = 12 ether; 52 | 53 | // Chainlink ETH/USD price feed (Ethereum mainnet) 54 | address internal constant CHAINLINK_ETH_USD_PRICE_FEED = 0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419; 55 | 56 | // Strategy id 57 | uint256 internal constant STANDARD_SALE_FOR_FIXED_PRICE_STRATEGY = 0; 58 | } 59 | -------------------------------------------------------------------------------- /test/mock/MockChainlinkAggregator.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.17; 3 | 4 | contract MockChainlinkAggregator { 5 | int256 private _answer; 6 | uint8 private _decimals = 18; 7 | 8 | function setDecimals(uint8 newDecimals) external { 9 | _decimals = newDecimals; 10 | } 11 | 12 | function decimals() external view returns (uint8) { 13 | return _decimals; 14 | } 15 | 16 | function setAnswer(int256 answer) external { 17 | _answer = answer; 18 | } 19 | 20 | function latestRoundData() 21 | external 22 | view 23 | returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) 24 | { 25 | roundId = 1; 26 | answer = _answer; 27 | startedAt = block.timestamp; 28 | updatedAt = block.timestamp; 29 | answeredInRound = 1; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /test/mock/MockERC1155.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.7; 3 | 4 | import {ERC1155} from "solmate/src/tokens/ERC1155.sol"; 5 | 6 | // LooksRare unopinionated libraries 7 | import {IERC2981} from "@looksrare/contracts-libs/contracts/interfaces/generic/IERC2981.sol"; 8 | 9 | contract MockERC1155 is ERC1155 { 10 | function batchMint(address to, uint256[] memory tokenIds, uint256[] memory amounts) public { 11 | _batchMint(to, tokenIds, amounts, ""); 12 | } 13 | 14 | function mint(address to, uint256 tokenId, uint256 amount) public { 15 | _mint(to, tokenId, amount, ""); 16 | } 17 | 18 | function uri(uint256) public pure override returns (string memory) { 19 | return "uri"; 20 | } 21 | 22 | function royaltyInfo(uint256, uint256) external pure returns (address, uint256) { 23 | return (address(0), 0); 24 | } 25 | 26 | function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { 27 | return interfaceId == type(IERC2981).interfaceId || super.supportsInterface(interfaceId); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test/mock/MockERC1155SupportsNoInterface.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.7; 3 | 4 | import {MockERC1155} from "./MockERC1155.sol"; 5 | 6 | contract MockERC1155SupportsNoInterface is MockERC1155 { 7 | function supportsInterface(bytes4) public view virtual override returns (bool) { 8 | return false; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/mock/MockERC1155WithoutAnyBalanceOf.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.7; 3 | 4 | import {ERC1155} from "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; 5 | 6 | /** 7 | * @dev This contract has to inherit from OZ instead of Solmate because 8 | * Solmate's implementation defines balanceOf as a public mapping 9 | * and it cannot be overridden. 10 | */ 11 | contract MockERC1155WithoutAnyBalanceOf is ERC1155 { 12 | constructor() ERC1155("https://example.com") {} 13 | 14 | function balanceOf(address, uint256) public view virtual override returns (uint256) { 15 | revert("Not implemented"); 16 | } 17 | 18 | function balanceOfBatch(address[] memory, uint256[] memory) public pure override returns (uint256[] memory) { 19 | revert("Not implemented"); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test/mock/MockERC1155WithoutBalanceOfBatch.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.7; 3 | 4 | import {MockERC1155} from "./MockERC1155.sol"; 5 | 6 | contract MockERC1155WithoutBalanceOfBatch is MockERC1155 { 7 | function balanceOfBatch(address[] calldata, uint256[] calldata) public pure override returns (uint256[] memory) { 8 | revert("Not implemented"); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/mock/MockERC1155WithoutIsApprovedForAll.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.7; 3 | 4 | import {ERC1155} from "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; 5 | 6 | /** 7 | * @dev This contract has to inherit from OZ instead of Solmate because 8 | * Solmate's implementation defines isApprovedForAll as a public mapping 9 | * and it cannot be overridden. 10 | */ 11 | contract MockERC1155WithoutIsApprovedForAll is ERC1155 { 12 | constructor() ERC1155("https://example.com") {} 13 | 14 | function mint(address to, uint256 tokenId, uint256 amount) public { 15 | _mint(to, tokenId, amount, ""); 16 | } 17 | 18 | function isApprovedForAll(address, address) public view virtual override returns (bool) { 19 | revert("Not implemented"); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test/mock/MockERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.7; 3 | 4 | import {ERC20} from "solmate/src/tokens/ERC20.sol"; 5 | 6 | contract MockERC20 is ERC20("MockERC20", "MockERC20", 18) { 7 | function mint(address to, uint256 amount) public { 8 | _mint(to, amount); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/mock/MockERC721.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.7; 3 | 4 | import {ERC721} from "solmate/src/tokens/ERC721.sol"; 5 | import {IERC165} from "@looksrare/contracts-libs/contracts/interfaces/generic/IERC165.sol"; 6 | 7 | contract MockERC721 is ERC721("MockERC721", "MockERC721") { 8 | function mint(address to, uint256 tokenId) public { 9 | _mint(to, tokenId); 10 | } 11 | 12 | function batchMint(address to, uint256 amount) external { 13 | for (uint256 i; i < amount; i++) { 14 | _mint(to, i); 15 | } 16 | } 17 | 18 | function batchMint(address to, uint256[] memory tokenIds) public { 19 | for (uint256 i; i < tokenIds.length; i++) { 20 | _mint(to, tokenIds[i]); 21 | } 22 | } 23 | 24 | function tokenURI(uint256) public pure override returns (string memory) { 25 | return "tokenURI"; 26 | } 27 | 28 | function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { 29 | return super.supportsInterface(interfaceId); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /test/mock/MockERC721SupportsNoInterface.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.7; 3 | 4 | import {MockERC721} from "./MockERC721.sol"; 5 | 6 | contract MockERC721SupportsNoInterface is MockERC721 { 7 | function supportsInterface(bytes4) public view virtual override returns (bool) { 8 | return false; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/mock/MockERC721WithRoyalties.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | import {IERC165, IERC2981} from "@looksrare/contracts-libs/contracts/interfaces/generic/IERC2981.sol"; 5 | import {MockERC721} from "./MockERC721.sol"; 6 | 7 | // Constants 8 | import {ONE_HUNDRED_PERCENT_IN_BP} from "../../contracts/constants/NumericConstants.sol"; 9 | 10 | /** 11 | * @dev This contract allows adding a royalty basis points higher than 10,000, which 12 | * reverts during the royaltyInfo call. The purpose is to create a scenario where 13 | * royaltyInfo returns the correct response for some token IDs and reverts for some 14 | * other token IDs. This can potentially happen in a bundle transaction. 15 | */ 16 | contract MockERC721WithRoyalties is MockERC721, IERC2981 { 17 | address public immutable DEFAULT_ROYALTY_RECIPIENT; 18 | uint256 public immutable DEFAULT_ROYALTY_FEE; 19 | 20 | mapping(uint256 => uint256) internal _royaltyFeeForTokenId; 21 | mapping(uint256 => address) internal _royaltyRecipientForTokenId; 22 | 23 | constructor(address _royaltyFeeRecipient, uint256 _royaltyFee) { 24 | DEFAULT_ROYALTY_RECIPIENT = _royaltyFeeRecipient; 25 | DEFAULT_ROYALTY_FEE = _royaltyFee; 26 | } 27 | 28 | function addCustomRoyaltyInformationForTokenId( 29 | uint256 tokenId, 30 | address royaltyRecipient, 31 | uint256 royaltyFee 32 | ) external { 33 | _royaltyRecipientForTokenId[tokenId] = royaltyRecipient; 34 | _royaltyFeeForTokenId[tokenId] = royaltyFee; 35 | } 36 | 37 | function royaltyInfo( 38 | uint256 tokenId, 39 | uint256 salePrice 40 | ) external view override returns (address royaltyRecipient, uint256 royaltyAmount) { 41 | royaltyRecipient = _royaltyRecipientForTokenId[tokenId] == address(0) 42 | ? DEFAULT_ROYALTY_RECIPIENT 43 | : _royaltyRecipientForTokenId[tokenId]; 44 | uint256 _royaltyFee = _royaltyFeeForTokenId[tokenId]; 45 | uint256 royaltyFee = _royaltyFee == 0 ? DEFAULT_ROYALTY_FEE : _royaltyFee; 46 | require(royaltyFee <= ONE_HUNDRED_PERCENT_IN_BP, "Royalty too high"); 47 | royaltyAmount = (royaltyFee * salePrice) / ONE_HUNDRED_PERCENT_IN_BP; 48 | } 49 | 50 | function supportsInterface(bytes4 interfaceId) public view override(MockERC721, IERC165) returns (bool) { 51 | return interfaceId == 0x2a55205a || super.supportsInterface(interfaceId); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /test/mock/MockRoyaltyFeeRegistry.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | // LooksRare unopinionated libraries 5 | import {OwnableTwoSteps} from "@looksrare/contracts-libs/contracts/OwnableTwoSteps.sol"; 6 | 7 | // Royalty Fee Registry interface 8 | import {IRoyaltyFeeRegistry} from "../../contracts/interfaces/IRoyaltyFeeRegistry.sol"; 9 | 10 | // Constants 11 | import {ONE_HUNDRED_PERCENT_IN_BP} from "../../contracts/constants/NumericConstants.sol"; 12 | 13 | /** 14 | * @title MockRoyaltyFeeRegistry 15 | * @notice It is a royalty fee registry for the LooksRare exchange. 16 | * @dev The original one used the standard Ownable library from OpenZeppelin. 17 | */ 18 | contract MockRoyaltyFeeRegistry is IRoyaltyFeeRegistry, OwnableTwoSteps { 19 | struct FeeInfo { 20 | address setter; 21 | address receiver; 22 | uint256 fee; 23 | } 24 | 25 | // Limit (if enforced for fee royalty in basis point (10,000 = 100%) 26 | uint256 public royaltyFeeLimit; 27 | 28 | mapping(address => FeeInfo) private _royaltyFeeInfoCollection; 29 | 30 | event NewRoyaltyFeeLimit(uint256 royaltyFeeLimit); 31 | event RoyaltyFeeUpdate(address indexed collection, address indexed setter, address indexed receiver, uint256 fee); 32 | 33 | /** 34 | * @notice Constructor 35 | * @param _owner Owner address 36 | * @param _royaltyFeeLimit new royalty fee limit (500 = 5%, 1,000 = 10%) 37 | */ 38 | constructor(address _owner, uint256 _royaltyFeeLimit) OwnableTwoSteps(_owner) { 39 | require(_royaltyFeeLimit <= 9_500, "Owner: Royalty fee limit too high"); 40 | royaltyFeeLimit = _royaltyFeeLimit; 41 | } 42 | 43 | /** 44 | * @notice Update royalty info for collection 45 | * @param _royaltyFeeLimit new royalty fee limit (500 = 5%, 1,000 = 10%) 46 | */ 47 | function updateRoyaltyFeeLimit(uint256 _royaltyFeeLimit) external onlyOwner { 48 | require(_royaltyFeeLimit <= 9_500, "Owner: Royalty fee limit too high"); 49 | royaltyFeeLimit = _royaltyFeeLimit; 50 | 51 | emit NewRoyaltyFeeLimit(_royaltyFeeLimit); 52 | } 53 | 54 | /** 55 | * @notice Update royalty info for collection 56 | * @param collection address of the NFT contract 57 | * @param setter address that sets the receiver 58 | * @param receiver receiver for the royalty fee 59 | * @param fee fee (500 = 5%, 1,000 = 10%) 60 | */ 61 | function updateRoyaltyInfoForCollection( 62 | address collection, 63 | address setter, 64 | address receiver, 65 | uint256 fee 66 | ) external onlyOwner { 67 | require(fee <= royaltyFeeLimit, "Registry: Royalty fee too high"); 68 | _royaltyFeeInfoCollection[collection] = FeeInfo({setter: setter, receiver: receiver, fee: fee}); 69 | 70 | emit RoyaltyFeeUpdate(collection, setter, receiver, fee); 71 | } 72 | 73 | /** 74 | * @notice Calculate royalty info for a collection address and a sale gross amount 75 | * @param collection collection address 76 | * @param amount amount 77 | * @return receiver address and amount received by royalty recipient 78 | */ 79 | function royaltyInfo(address collection, uint256 amount) external view returns (address, uint256) { 80 | return ( 81 | _royaltyFeeInfoCollection[collection].receiver, 82 | (amount * _royaltyFeeInfoCollection[collection].fee) / ONE_HUNDRED_PERCENT_IN_BP 83 | ); 84 | } 85 | 86 | /** 87 | * @notice View royalty info for a collection address 88 | * @param collection collection address 89 | */ 90 | function royaltyFeeInfoCollection(address collection) external view returns (address, address, uint256) { 91 | return ( 92 | _royaltyFeeInfoCollection[collection].setter, 93 | _royaltyFeeInfoCollection[collection].receiver, 94 | _royaltyFeeInfoCollection[collection].fee 95 | ); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /test/mock/MockSmartWallet.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.17; 3 | 4 | contract MockSmartWallet { 5 | address public owner; 6 | 7 | bytes4 internal constant MAGICVALUE = bytes4(keccak256("isValidSignature(bytes32,bytes)")); 8 | 9 | modifier onlyOwner() { 10 | if (msg.sender != owner) { 11 | revert("Unauthorized"); 12 | } 13 | _; 14 | } 15 | 16 | constructor() { 17 | owner = msg.sender; 18 | } 19 | 20 | receive() external payable {} 21 | 22 | function transferOwnership(address _newOwner) external onlyOwner { 23 | owner = _newOwner; 24 | } 25 | 26 | function executeBatch(address[] calldata dest, bytes[] calldata calldata_) external onlyOwner { 27 | if (dest.length != calldata_.length) { 28 | revert("Invalid array length"); 29 | } 30 | for (uint256 i = 0; i < dest.length; ++i) { 31 | execute(dest[i], 0, calldata_[i]); 32 | } 33 | } 34 | 35 | function isValidSignature(bytes32 _hash, bytes memory _signature) external view returns (bytes4 magicValue) { 36 | bytes32 r; 37 | bytes32 s; 38 | uint8 v; 39 | 40 | assembly { 41 | r := mload(add(_signature, 0x20)) 42 | s := mload(add(_signature, 0x40)) 43 | v := byte(0, mload(add(_signature, 0x60))) 44 | } 45 | address recovered = ecrecover(_hash, v, r, s); 46 | if (recovered == address(0)) { 47 | revert("Invalid signature"); 48 | } 49 | if (owner != recovered) { 50 | revert("Invalid signer"); 51 | } 52 | 53 | return MAGICVALUE; 54 | } 55 | 56 | function execute(address dest, uint256 value, bytes calldata calldata_) public onlyOwner { 57 | (bool success, bytes memory result) = dest.call{value: value}(calldata_); 58 | 59 | if (!success) { 60 | assembly { 61 | revert(add(result, 32), mload(result)) 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /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", "./test", "./typechain"], 11 | "files": ["./hardhat.config.ts"] 12 | } 13 | --------------------------------------------------------------------------------