├── .eslintignore ├── .eslintrc.yaml ├── .gitignore ├── .gitmodules ├── .prettierignore ├── .prettierrc ├── .solcover.js ├── .solhint.json ├── README.md ├── contracts ├── FETH.sol ├── FoundationTreasury.sol ├── NFTCollection.sol ├── NFTCollectionFactory.sol ├── NFTDropCollection.sol ├── NFTDropMarket.sol ├── PercentSplitETH.sol ├── interfaces │ ├── IAdminRole.sol │ ├── ICollectionFactory.sol │ ├── IERC20Approve.sol │ ├── IERC20IncreaseAllowance.sol │ ├── IFethMarket.sol │ ├── IGetFees.sol │ ├── IGetRoyalties.sol │ ├── INFTCollectionInitializer.sol │ ├── INFTDropCollectionInitializer.sol │ ├── INFTDropCollectionMint.sol │ ├── IOperatorRole.sol │ ├── IOwnable.sol │ ├── IProxyCall.sol │ ├── IRoles.sol │ ├── IRoyaltyInfo.sol │ └── ITokenCreator.sol ├── libraries │ ├── AddressLibrary.sol │ ├── ArrayLibrary.sol │ ├── BytesLibrary.sol │ └── LockedBalance.sol ├── mixins │ ├── collections │ │ ├── CollectionRoyalties.sol │ │ └── SequentialMintCollection.sol │ ├── nftDropMarket │ │ ├── NFTDropMarketCore.sol │ │ └── NFTDropMarketFixedPriceSale.sol │ ├── roles │ │ ├── AdminRole.sol │ │ └── MinterRole.sol │ ├── shared │ │ ├── Constants.sol │ │ ├── ContractFactory.sol │ │ ├── FETHNode.sol │ │ ├── FoundationTreasuryNode.sol │ │ ├── Gap10000.sol │ │ ├── MarketFees.sol │ │ ├── MarketSharedCore.sol │ │ ├── OZERC165Checker.sol │ │ └── SendValueWithFallbackWithdraw.sol │ └── treasury │ │ ├── AdminRole.sol │ │ ├── CollateralManagement.sol │ │ ├── OZAccessControlUpgradeable.sol │ │ └── OperatorRole.sol └── mocks │ ├── BasicERC721.sol │ ├── BasicERC721WithAccessControlMock.sol │ ├── BasicERC721WithoutOwnerOfRevert.sol │ ├── EmptyMockContract.sol │ ├── FETHMarketMock.sol │ ├── MockNFT.sol │ ├── MockTreasury.sol │ ├── NFTDropCollectionUnknownCreatorMock.sol │ ├── NFTDropCollectionWithoutAccessControl.sol │ ├── NonReceivableMock.sol │ ├── ReceivableMock.sol │ ├── RoyaltyRegistry │ └── MockRoyaltyRegistry.sol │ ├── WETH9.sol │ └── collections │ └── SequentialMintCollectionMock.sol ├── discord-export ├── Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html ├── Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files │ ├── 0266e5039778e2135b8ce9b7c0243400-A3510.png │ ├── 123c800a205b56354c1bd121a4b1b969-621EA.png │ ├── 1f355-981A7.svg │ ├── 1f374-505CF.svg │ ├── 1f389-5C738.svg │ ├── 1f3c1-445DC.svg │ ├── 1f3c6-621A1.svg │ ├── 1f43a-EB486.svg │ ├── 1f440-6C64D.svg │ ├── 1f44b-8A059.svg │ ├── 1f44c-59547.svg │ ├── 1f44d-27259.svg │ ├── 1f44f-3D381.svg │ ├── 1f49c-71A75.svg │ ├── 1f4af-4CFF5.svg │ ├── 1f4b0-53FFF.svg │ ├── 1f4b8-E3468.svg │ ├── 1f4c6-44E30.svg │ ├── 1f4db-4C577.svg │ ├── 1f4dc-AC641.svg │ ├── 1f506-55112.svg │ ├── 1f50d-195C0.svg │ ├── 1f525-8FE4F.svg │ ├── 1f554-A741F.svg │ ├── 1f602-168C5.svg │ ├── 1f604-BF863.svg │ ├── 1f605-42B43.svg │ ├── 1f606-BE94E.svg │ ├── 1f609-9EC67.svg │ ├── 1f60b-B5303.svg │ ├── 1f629-B734A.svg │ ├── 1f62d-02603.svg │ ├── 1f642-83E8A.svg │ ├── 1f64f-22B8D.svg │ ├── 1f680-A35CE.svg │ ├── 1f6a8-A8AB3.svg │ ├── 1f7e5-2145C.svg │ ├── 1f911-F346C.svg │ ├── 1f914-15707.svg │ ├── 1f919-1f3fb-2BA09.svg │ ├── 1f923-5854E.svg │ ├── 1f926-E188B.svg │ ├── 1f929-12865.svg │ ├── 1f972-F415D.svg │ ├── 1f973-88B39.svg │ ├── 1f97a-1F57B.svg │ ├── 1f9d1-5BC80.svg │ ├── 2696-15F4A.svg │ ├── 2705-0589F.svg │ ├── 2764-A3D25.svg │ ├── 2764-fe0f-200d-1f525-0B2B8.svg │ ├── 2a9faff195fe333526cfe6ae6fce1420-927E9.png │ ├── 32-20e3-EF1FD.svg │ ├── 39a730e4124c3b42df900de7d9d32973-2B76A.png │ ├── 3b01c38b7c5b905fd8e8a1d72f7d7492-6DE9E.png │ ├── 4c405c8d602d7b3489d628bf20bfb843-60774.png │ ├── 4d708938ebe45c5d7a5f46ecbd3b144e-C87A6.png │ ├── 5b069e074423293b86222b4350052da5-77740.png │ ├── 5f978694dec96afc1640b5426003aef1-3E74E.png │ ├── 62208180fa28d8609e6f66d9ad7fca37-00B6C.png │ ├── 67594ee4b4d1fc03bca468327a0d145b-0C614.png │ ├── 7295c56e5b235d31dcba650a186322b0-1BF1F.png │ ├── 754850443909267567-E2D08.gif │ ├── 773490663245348864-8086D.png │ ├── 809922172625748019-3756A.png │ ├── 851893157188599838-C23B5.png │ ├── 851893827027075142-F23DF.png │ ├── 851893827089727568-5FD38.png │ ├── 851893827315826708-F59C0.png │ ├── 910676187288846397-518CD.png │ ├── 918262047433691247-911FE.png │ ├── 927703596944998430-0A6FB.png │ ├── 9503f567dabd2781b3d25827ceb83075-313E1.png │ ├── 970d2e2f00cd7ef2134a1a3f21326349-B9EEF.png │ ├── 977133670429261884-CA8EA.png │ ├── 9bf2181404e658cab4039c07df56213f-EC9DE.png │ ├── 9dae367c2914db90ec5f86da55c97b23-D9765.png │ ├── C4-banner-7C19B.png │ ├── PolygonScan-logo-circle-6FDB5.jpg │ ├── a36cb7b26a8fd70f4f296a94c3cf5a9d-2B57B.png │ ├── aa8a8cf504d883f45a141e445c26ec7b-0AED8.png │ ├── aba7f6b23cd80ec9e8655016ce6ef443-929E9.png │ ├── ae70f07a06c1c7e983291bb14a1bed7f-98458.png │ ├── b07292365785e0b7bd0dab7dd7d9e09f-62888.png │ ├── bugs-bunny-looney-tunes-BD3E0.mp4 │ ├── c47e074275f9b2c05d095d0847bc18dd-CF0A6.png │ ├── c9cb30134c634c9e02d0c64df4922803-1AAF2.png │ ├── cb23e87e4eb33d228ed3294f90188951-D6A20.png │ ├── d38725fdb3f22651bef3993cf902a61d-F5783.png │ ├── d6f1f56d219902e83e7f37cc225ffd0f-D4278.png │ ├── d7e057b14c0a958df354613bb7522913-1FD52.png │ ├── d7e24e0994d277993ed07845cac9ca8b-E4084.png │ ├── dfacc77d68962a991f5a4c3b7859b4c2-0A44D.png │ ├── e8e42b0753ed4170607ecff76b81d17e-80898.png │ ├── ggsans-italic-400-E988B.woff2 │ ├── ggsans-italic-500-0777F.woff2 │ ├── ggsans-italic-600-CB411.woff2 │ ├── ggsans-italic-700-891AC.woff2 │ ├── ggsans-italic-800-D36B0.woff2 │ ├── ggsans-normal-400-1456D.woff2 │ ├── ggsans-normal-500-89CE5.woff2 │ ├── ggsans-normal-600-C1EA8.woff2 │ ├── ggsans-normal-700-1949A.woff2 │ ├── ggsans-normal-800-58487.woff2 │ ├── highlight.min-D8D27.js │ ├── lottie.min-99657.js │ ├── michael-scott-wink-82DFE.mp4 │ ├── oh-hey-ryan-reynolds-13ABB.mp4 │ ├── solarized-dark.min-BA98F.css │ ├── spongebob-sad-96BA8.mp4 │ ├── unknown-086F2.png │ ├── unknown-CB0DC.png │ └── will-ferrell-stressed-out-73DD6.mp4 ├── Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].txt └── Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].txt_Files │ ├── C4-banner-7C19B.png │ ├── PolygonScan-logo-circle-6FDB5.jpg │ ├── bugs-bunny-looney-tunes-6263B.png │ ├── maxresdefault-7ADB4.jpg │ ├── michael-scott-wink-76E4C.png │ ├── oh-hey-ryan-reynolds-80E2E.png │ ├── spongebob-sad-A44ED.png │ ├── unknown-086F2.png │ ├── unknown-CB0DC.png │ └── will-ferrell-stressed-out-65DCF.png ├── foundry.toml ├── gas-stories.txt ├── hardhat.config.ts ├── hardhat.gas-stories.config.ts ├── helpers ├── index.ts └── utils.ts ├── package.json ├── slither.config.json ├── slither ├── NFTCollection.md ├── NFTCollectionFactory.md ├── NFTDropCollection.md └── NFTDropMarket.md ├── src ├── helpers │ ├── bytes.ts │ ├── process.ts │ └── providerHelpers.ts ├── testBehaviors │ ├── index.ts │ └── supportsInterface.behavior.ts └── types │ └── openzeppelin__test-helpers.d.ts ├── test-gas-stories ├── collections.ts ├── gas-stories.ts └── nftDropCollectionFixedPriceSale.ts ├── test ├── NFTDropMarket │ ├── fixedPrice │ │ ├── create.ts │ │ ├── drop.ts │ │ ├── funds.ts │ │ ├── getSaleDetails.ts │ │ ├── getSellerOf.ts │ │ ├── mintFromFixedPriceSale.ts │ │ └── mintWithFETH.ts │ └── initializer.ts ├── collections │ ├── NFTCollection │ │ ├── CollectionContract.ts │ │ ├── mint.ts │ │ ├── paymentFactory │ │ │ └── DrainTokens.ts │ │ ├── royaltyInfo.ts │ │ ├── selfDestruct.ts │ │ └── totalSupply.ts │ ├── NFTCollectionFactory │ │ ├── CollectionDropFactory.ts │ │ ├── collectionFactory.ts │ │ ├── createNFTDropEvents.ts │ │ ├── initializer.ts │ │ └── predictNFTDropCollectionAddress.ts │ ├── NFTDropCollection │ │ ├── admin.ts │ │ ├── reveal.ts │ │ ├── royaltyInterfaces.ts │ │ ├── supportsInterfaces.ts │ │ └── update.ts │ └── mixins │ │ └── SequentialMintCollectionInitialize.ts ├── feth │ ├── deposit.ts │ ├── marketInteractions.ts │ └── withdraw.ts ├── fixtures │ └── nftCollectionFactory.ts ├── foundry │ ├── BytesLibrary.t.sol │ └── FixedPriceDrop.sol └── helpers │ ├── collectionContract.ts │ ├── constants.ts │ ├── deploy.ts │ ├── feth.ts │ ├── logs.ts │ ├── mint.ts │ ├── snapshot.ts │ ├── splits.ts │ ├── testData.ts │ └── time.ts ├── tsconfig.json └── yarn.lock /.eslintignore: -------------------------------------------------------------------------------- 1 | # folders 2 | artifacts/ 3 | build/ 4 | cache/ 5 | coverage/ 6 | dist/ 7 | docs/ 8 | lib/ 9 | node_modules/ 10 | src/typechain/ 11 | subgraph/config/ 12 | subgraph/build/ 13 | subgraph/generated/ 14 | subgraph/graph-node/ 15 | versions/ 16 | deployments/ 17 | 18 | # files 19 | .solcover.js 20 | coverage.json 21 | -------------------------------------------------------------------------------- /.eslintrc.yaml: -------------------------------------------------------------------------------- 1 | extends: 2 | - eslint:recommended 3 | - plugin:@typescript-eslint/eslint-recommended 4 | - plugin:@typescript-eslint/recommended 5 | - prettier 6 | parser: "@typescript-eslint/parser" 7 | parserOptions: 8 | project: tsconfig.json 9 | plugins: 10 | - "@typescript-eslint" 11 | - "no-only-tests" 12 | root: true 13 | rules: 14 | "@typescript-eslint/no-floating-promises": 15 | - error 16 | - ignoreIIFE: true 17 | ignoreVoid: true 18 | "@typescript-eslint/no-inferrable-types": "off" 19 | "@typescript-eslint/no-unused-vars": 20 | - error 21 | - argsIgnorePattern: _ 22 | varsIgnorePattern: _ 23 | "sort-imports": 24 | - error 25 | - ignoreCase: true 26 | ignoreDeclarationSort: true 27 | "no-only-tests/no-only-tests": "error" 28 | overrides: 29 | - files: subgraph/**/*.ts 30 | rules: 31 | prefer-const: 0 32 | "@typescript-eslint/ban-types": 0 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | typechain 4 | coverage 5 | coverage.json 6 | out 7 | 8 | #Hardhat files 9 | cache 10 | artifacts 11 | 12 | yarn-error.log 13 | 14 | .openzeppelin/ 15 | 16 | .DS_Store 17 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/forge-std"] 2 | path = lib/forge-std 3 | url = https://github.com/foundry-rs/forge-std 4 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # folders 2 | artifacts/ 3 | build/ 4 | cache/ 5 | coverage/ 6 | dist/ 7 | docs/ 8 | lib/ 9 | node_modules/ 10 | typechain-types/ 11 | src/typechain/ 12 | .vscode/ 13 | deployments/ 14 | 15 | # files 16 | coverage.json 17 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "avoid", 3 | "bracketSpacing": true, 4 | "endOfLine": "auto", 5 | "printWidth": 120, 6 | "singleQuote": false, 7 | "tabWidth": 2, 8 | "trailingComma": "all", 9 | "overrides": [ 10 | { 11 | "files": "*.sol", 12 | "options": { 13 | "tabWidth": 2 14 | } 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /.solcover.js: -------------------------------------------------------------------------------- 1 | const shell = require("shelljs"); 2 | 3 | module.exports = { 4 | istanbulReporter: ["html"], 5 | onCompileComplete: async function (_config) { 6 | await run("typechain"); 7 | }, 8 | onIstanbulComplete: async function (_config) { 9 | // We need to do this because solcover generates bespoke artifacts. 10 | shell.rm("-rf", "./artifacts"); 11 | shell.rm("-rf", "./src/typechain"); 12 | }, 13 | skipFiles: [ 14 | "mocks", 15 | "test", 16 | "FNDMiddleware", 17 | "archive", 18 | "FETH", 19 | "PercentSplitETH", 20 | "FoundationTreasury", 21 | "libraries/BytesLibrary", 22 | "libraries/LockedBalance", 23 | "mixins/treasury", 24 | ], 25 | istanbulReporter: ["lcov", "html", "text-summary"], 26 | mocha: { 27 | fgrep: "[skip-on-coverage]", 28 | invert: true, 29 | }, 30 | }; 31 | -------------------------------------------------------------------------------- /.solhint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solhint:recommended", 3 | "plugins": ["prettier"], 4 | "rules": { 5 | "code-complexity": ["error", 7], 6 | "compiler-version": ["error", "^0.8.12"], 7 | "const-name-snakecase": "off", 8 | "constructor-syntax": "error", 9 | "func-visibility": [ 10 | "error", 11 | { 12 | "ignoreConstructors": true 13 | } 14 | ], 15 | "max-line-length": ["error", 120], 16 | "not-rely-on-time": "off", 17 | "prettier/prettier": [ 18 | "error", 19 | { 20 | "endOfLine": "auto" 21 | } 22 | ], 23 | "reason-string": [ 24 | "warn", 25 | { 26 | "maxLength": 64 27 | } 28 | ], 29 | "var-name-mixedcase": "off", 30 | "no-inline-assembly": "off" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /contracts/FoundationTreasury.sol: -------------------------------------------------------------------------------- 1 | /* 2 | ・ 3 | * ★ 4 | ・ 。 5 |  ・ ゚☆ 。 6 |     * ★ ゚・。 * 。 7 |   * ☆ 。・゚*.。 8 |    ゚ *.。☆。★ ・ 9 | *  ゚。·*・。 ゚* 10 |    ☆゚・。°*. ゚ 11 |   ・ ゚*。・゚★。 12 |   ・ *゚。   * 13 |  ・゚*。★・ 14 | ☆∴。 * 15 |     * ★ 。 16 | ` .-:::::-.` `-::---...``` 17 | `-:` .:+ssssoooo++//:.` .-/+shhhhhhhhhhhhhyyyssooo: 18 | .--::. .+ossso+/////++/:://-` .////+shhhhhhhhhhhhhhhhhhhhhy 19 | `-----::. `/+////+++///+++/:--:/+/- -////+shhhhhhhhhhhhhhhhhhhhhy 20 | `------:::-` `//-.``.-/+ooosso+:-.-/oso- -////+shhhhhhhhhhhhhhhhhhhhhy 21 | .--------:::-` :+:.` .-/osyyyyyyso++syhyo.-////+shhhhhhhhhhhhhhhhhhhhhy 22 | `-----------:::-. +o+:-.-:/oyhhhhhhdhhhhhdddy:-////+shhhhhhhhhhhhhhhhhhhhhy 23 | .------------::::-- `oys+/::/+shhhhhhhdddddddddy/-////+shhhhhhhhhhhhhhhhhhhhhy 24 | .--------------:::::-` +ys+////+yhhhhhhhddddddddhy:-////+yhhhhhhhhhhhhhhhhhhhhhy 25 | `----------------::::::-`.ss+/:::+oyhhhhhhhhhhhhhhho`-////+shhhhhhhhhhhhhhhhhhhhhy 26 | .------------------:::::::.-so//::/+osyyyhhhhhhhhhys` -////+shhhhhhhhhhhhhhhhhhhhhy 27 | `.-------------------::/:::::..+o+////+oosssyyyyyyys+` .////+shhhhhhhhhhhhhhhhhhhhhy 28 | .--------------------::/:::.` -+o++++++oooosssss/. `-//+shhhhhhhhhhhhhhhhhhhhyo 29 | .------- ``````.......--` `-/+ooooosso+/-` `./++++///:::--...``hhhhyo 30 | ````` 31 | *  32 | ・ 。 33 |     ・  ゚☆ 。 34 |     * ★ ゚・。 * 。 35 |   * ☆ 。・゚*.。 36 |    ゚ *.。☆。★ ・ 37 | *  ゚。·*・。 ゚* 38 |    ☆゚・。°*. ゚ 39 |   ・ ゚*。・゚★。 40 |   ・ *゚。   * 41 |  ・゚*。★・ 42 | ☆∴。 * 43 | ・ 。 44 | */ 45 | 46 | // SPDX-License-Identifier: MIT OR Apache-2.0 47 | 48 | pragma solidity ^0.8.12; 49 | 50 | import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; 51 | 52 | import "./mixins/treasury/AdminRole.sol"; 53 | import "./mixins/treasury/CollateralManagement.sol"; 54 | import "./mixins/treasury/OperatorRole.sol"; 55 | 56 | /** 57 | * @title Manage revenue and roles for Foundation. 58 | * @notice All fees generated by the market are forwarded to this contract. 59 | * It also defines the Admin and Operator roles which are used in other contracts. 60 | */ 61 | contract FoundationTreasury is Initializable, AdminRole, OperatorRole, CollateralManagement { 62 | /** 63 | * @notice Called one time after deployment to initialize the contract. 64 | * @param admin The account to add as the initial admin. 65 | */ 66 | function initialize(address admin) external initializer { 67 | AdminRole._initializeAdminRole(admin); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /contracts/interfaces/IAdminRole.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.12; 4 | 5 | /** 6 | * @notice Interface for AdminRole which wraps the default admin role from 7 | * OpenZeppelin's AccessControl for easy integration. 8 | */ 9 | interface IAdminRole { 10 | function isAdmin(address account) external view returns (bool); 11 | } 12 | -------------------------------------------------------------------------------- /contracts/interfaces/ICollectionFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.12; 4 | 5 | import "./IRoles.sol"; 6 | import "./IProxyCall.sol"; 7 | 8 | interface ICollectionFactory { 9 | function rolesContract() external returns (IRoles); 10 | } 11 | -------------------------------------------------------------------------------- /contracts/interfaces/IERC20Approve.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.12; 4 | 5 | interface IERC20Approve { 6 | function approve(address spender, uint256 amount) external returns (bool); 7 | } 8 | -------------------------------------------------------------------------------- /contracts/interfaces/IERC20IncreaseAllowance.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.12; 4 | 5 | interface IERC20IncreaseAllowance { 6 | function increaseAllowance(address spender, uint256 addedValue) external returns (bool); 7 | } 8 | -------------------------------------------------------------------------------- /contracts/interfaces/IFethMarket.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.12; 4 | 5 | /** 6 | * @notice Interface for functions the market uses in FETH. 7 | */ 8 | interface IFethMarket { 9 | function depositFor(address account) external payable; 10 | 11 | function marketLockupFor(address account, uint256 amount) external payable returns (uint256 expiration); 12 | 13 | function marketWithdrawFrom(address from, uint256 amount) external; 14 | 15 | function marketWithdrawLocked( 16 | address account, 17 | uint256 expiration, 18 | uint256 amount 19 | ) external; 20 | 21 | function marketUnlockFor( 22 | address account, 23 | uint256 expiration, 24 | uint256 amount 25 | ) external; 26 | 27 | function marketChangeLockup( 28 | address unlockFrom, 29 | uint256 unlockExpiration, 30 | uint256 unlockAmount, 31 | address lockupFor, 32 | uint256 lockupAmount 33 | ) external payable returns (uint256 expiration); 34 | } 35 | -------------------------------------------------------------------------------- /contracts/interfaces/IGetFees.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.12; 4 | 5 | /** 6 | * @notice An interface for communicating fees to 3rd party marketplaces. 7 | * @dev Originally implemented in mainnet contract 0x44d6e8933f8271abcf253c72f9ed7e0e4c0323b3 8 | */ 9 | interface IGetFees { 10 | /** 11 | * @notice Get the recipient addresses to which creator royalties should be sent. 12 | * @dev The expected royalty amounts are communicated with `getFeeBps`. 13 | * @param tokenId The ID of the NFT to get royalties for. 14 | * @return recipients An array of addresses to which royalties should be sent. 15 | */ 16 | function getFeeRecipients(uint256 tokenId) external view returns (address payable[] memory recipients); 17 | 18 | /** 19 | * @notice Get the creator royalty amounts to be sent to each recipient, in basis points. 20 | * @dev The expected recipients are communicated with `getFeeRecipients`. 21 | * @param tokenId The ID of the NFT to get royalties for. 22 | * @return royaltiesInBasisPoints The array of fees to be sent to each recipient, in basis points. 23 | */ 24 | function getFeeBps(uint256 tokenId) external view returns (uint256[] memory royaltiesInBasisPoints); 25 | } 26 | -------------------------------------------------------------------------------- /contracts/interfaces/IGetRoyalties.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.12; 4 | 5 | interface IGetRoyalties { 6 | /** 7 | * @notice Get the creator royalties to be sent. 8 | * @dev The data is the same as when calling `getFeeRecipients` and `getFeeBps` separately. 9 | * @param tokenId The ID of the NFT to get royalties for. 10 | * @return recipients An array of addresses to which royalties should be sent. 11 | * @return royaltiesInBasisPoints The array of fees to be sent to each recipient, in basis points. 12 | */ 13 | function getRoyalties(uint256 tokenId) 14 | external 15 | view 16 | returns (address payable[] memory recipients, uint256[] memory royaltiesInBasisPoints); 17 | } 18 | -------------------------------------------------------------------------------- /contracts/interfaces/INFTCollectionInitializer.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.12; 4 | 5 | interface INFTCollectionInitializer { 6 | function initialize( 7 | address payable _creator, 8 | string memory _name, 9 | string memory _symbol 10 | ) external; 11 | } 12 | -------------------------------------------------------------------------------- /contracts/interfaces/INFTDropCollectionInitializer.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.12; 4 | 5 | interface INFTDropCollectionInitializer { 6 | function initialize( 7 | address payable _creator, 8 | string calldata _name, 9 | string calldata _symbol, 10 | string calldata _baseURI, 11 | bytes32 _postRevealBaseURIHash, 12 | uint32 _maxTokenId, 13 | address _approvedMinter, 14 | address payable _paymentAddress 15 | ) external; 16 | } 17 | -------------------------------------------------------------------------------- /contracts/interfaces/INFTDropCollectionMint.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.12; 4 | 5 | /** 6 | * @notice The required interface for collections to support the NFTDropMarket. 7 | * @dev This interface must be registered as a ERC165 supported interface to support the NFTDropMarket. 8 | */ 9 | interface INFTDropCollectionMint { 10 | function mintCountTo(uint16 count, address to) external returns (uint256 firstTokenId); 11 | 12 | function numberOfTokensAvailableToMint() external view returns (uint256 count); 13 | } 14 | -------------------------------------------------------------------------------- /contracts/interfaces/IOperatorRole.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.12; 4 | 5 | /** 6 | * @notice Interface for OperatorRole which wraps a role from 7 | * OpenZeppelin's AccessControl for easy integration. 8 | */ 9 | interface IOperatorRole { 10 | function isOperator(address account) external view returns (bool); 11 | } 12 | -------------------------------------------------------------------------------- /contracts/interfaces/IOwnable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.12; 4 | 5 | interface IOwnable { 6 | /** 7 | * @dev Returns the address of the current owner. 8 | */ 9 | function owner() external view returns (address); 10 | } 11 | -------------------------------------------------------------------------------- /contracts/interfaces/IProxyCall.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.12; 4 | 5 | interface IProxyCall { 6 | function proxyCallAndReturnAddress(address externalContract, bytes memory callData) 7 | external 8 | returns (address payable result); 9 | } 10 | -------------------------------------------------------------------------------- /contracts/interfaces/IRoles.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.12; 4 | 5 | /** 6 | * @notice Interface for a contract which implements admin roles. 7 | */ 8 | interface IRoles { 9 | function isAdmin(address account) external view returns (bool); 10 | 11 | function isOperator(address account) external view returns (bool); 12 | } 13 | -------------------------------------------------------------------------------- /contracts/interfaces/IRoyaltyInfo.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.12; 4 | 5 | /** 6 | * @notice Interface for EIP-2981: NFT Royalty Standard. 7 | * For more see: https://eips.ethereum.org/EIPS/eip-2981. 8 | */ 9 | interface IRoyaltyInfo { 10 | /** 11 | * @notice Get the creator royalties to be sent. 12 | * @param tokenId The ID of the NFT to get royalties for. 13 | * @param salePrice The total price of the sale. 14 | * @return receiver The address to which royalties should be sent. 15 | * @return royaltyAmount The total amount that should be sent to the `receiver`. 16 | */ 17 | function royaltyInfo(uint256 tokenId, uint256 salePrice) 18 | external 19 | view 20 | returns (address receiver, uint256 royaltyAmount); 21 | } 22 | -------------------------------------------------------------------------------- /contracts/interfaces/ITokenCreator.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.12; 4 | 5 | interface ITokenCreator { 6 | /** 7 | * @notice Returns the creator of this NFT collection. 8 | * @param tokenId The ID of the NFT to get the creator payment address for. 9 | * @return creator The creator of this collection. 10 | */ 11 | function tokenCreator(uint256 tokenId) external view returns (address payable creator); 12 | } 13 | -------------------------------------------------------------------------------- /contracts/libraries/AddressLibrary.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.12; 4 | 5 | import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol"; 6 | 7 | struct CallWithoutValue { 8 | address target; 9 | bytes callData; 10 | } 11 | 12 | /** 13 | * @title A library for address helpers not already covered by the OZ library library. 14 | */ 15 | library AddressLibrary { 16 | using AddressUpgradeable for address; 17 | using AddressUpgradeable for address payable; 18 | 19 | /** 20 | * @notice Calls an external contract with arbitrary data and parse the return value into an address. 21 | * @param externalContract The address of the contract to call. 22 | * @param callData The data to send to the contract. 23 | * @return contractAddress The address of the contract returned by the call. 24 | */ 25 | function callAndReturnContractAddress(address externalContract, bytes memory callData) 26 | internal 27 | returns (address payable contractAddress) 28 | { 29 | bytes memory returnData = externalContract.functionCall(callData); 30 | contractAddress = abi.decode(returnData, (address)); 31 | require(contractAddress.isContract(), "InternalProxyCall: did not return a contract"); 32 | } 33 | 34 | function callAndReturnContractAddress(CallWithoutValue memory call) 35 | internal 36 | returns (address payable contractAddress) 37 | { 38 | return callAndReturnContractAddress(call.target, call.callData); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /contracts/libraries/ArrayLibrary.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.12; 4 | 5 | /** 6 | * @title Helper functions for arrays. 7 | */ 8 | library ArrayLibrary { 9 | /** 10 | * @notice Reduces the size of an array if it's greater than the specified max size, 11 | * using the first maxSize elements. 12 | */ 13 | function capLength(address payable[] memory data, uint256 maxLength) internal pure { 14 | if (data.length > maxLength) { 15 | assembly { 16 | mstore(data, maxLength) 17 | } 18 | } 19 | } 20 | 21 | /** 22 | * @notice Reduces the size of an array if it's greater than the specified max size, 23 | * using the first maxSize elements. 24 | */ 25 | function capLength(uint256[] memory data, uint256 maxLength) internal pure { 26 | if (data.length > maxLength) { 27 | assembly { 28 | mstore(data, maxLength) 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /contracts/libraries/BytesLibrary.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.12; 4 | 5 | error BytesLibrary_Expected_Address_Not_Found(); 6 | 7 | /** 8 | * @title A library for manipulation of byte arrays. 9 | */ 10 | library BytesLibrary { 11 | /** 12 | * @dev Replace the address at the given location in a byte array if the contents at that location 13 | * match the expected address. 14 | */ 15 | function replaceAtIf( 16 | bytes memory data, 17 | uint256 startLocation, 18 | address expectedAddress, 19 | address newAddress 20 | ) internal pure { 21 | bytes memory expectedData = abi.encodePacked(expectedAddress); 22 | bytes memory newData = abi.encodePacked(newAddress); 23 | unchecked { 24 | // An address is 20 bytes long 25 | for (uint256 i = 0; i < 20; ++i) { 26 | uint256 dataLocation = startLocation + i; 27 | if (data[dataLocation] != expectedData[i]) { 28 | revert BytesLibrary_Expected_Address_Not_Found(); 29 | } 30 | data[dataLocation] = newData[i]; 31 | } 32 | } 33 | } 34 | 35 | /** 36 | * @dev Checks if the call data starts with the given function signature. 37 | */ 38 | function startsWith(bytes memory callData, bytes4 functionSig) internal pure returns (bool) { 39 | // A signature is 4 bytes long 40 | if (callData.length < 4) { 41 | return false; 42 | } 43 | unchecked { 44 | for (uint256 i = 0; i < 4; ++i) { 45 | if (callData[i] != functionSig[i]) { 46 | return false; 47 | } 48 | } 49 | } 50 | 51 | return true; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /contracts/mixins/nftDropMarket/NFTDropMarketCore.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.12; 4 | 5 | import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; 6 | 7 | /** 8 | * @title A place for common modifiers and functions used by various market mixins, if any. 9 | * @dev This also leaves a gap which can be used to add a new mixin to the top of the inheritance tree. 10 | */ 11 | abstract contract NFTDropMarketCore { 12 | /** 13 | * @notice This empty reserved space is put in place to allow future versions to add new 14 | * variables without shifting down storage in the inheritance chain. 15 | * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps 16 | */ 17 | uint256[1000] private __gap; 18 | } 19 | -------------------------------------------------------------------------------- /contracts/mixins/roles/AdminRole.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.12; 4 | 5 | import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; 6 | import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol"; 7 | 8 | /** 9 | * @title Defines a role for admin accounts. 10 | * @dev Wraps the default admin role from OpenZeppelin's AccessControl for easy integration. 11 | */ 12 | abstract contract AdminRole is Initializable, AccessControlUpgradeable { 13 | function _initializeAdminRole(address admin) internal onlyInitializing { 14 | // Grant the role to a specified account 15 | _grantRole(DEFAULT_ADMIN_ROLE, admin); 16 | } 17 | 18 | modifier onlyAdmin() { 19 | require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "AdminRole: caller does not have the Admin role"); 20 | _; 21 | } 22 | 23 | /** 24 | * @notice Adds an account as an approved admin. 25 | * @dev Only callable by existing admins, as enforced by `grantRole`. 26 | * @param account The address to be approved. 27 | */ 28 | function grantAdmin(address account) external { 29 | grantRole(DEFAULT_ADMIN_ROLE, account); 30 | } 31 | 32 | /** 33 | * @notice Removes an account from the set of approved admins. 34 | * @dev Only callable by existing admins, as enforced by `revokeRole`. 35 | * @param account The address to be removed. 36 | */ 37 | function revokeAdmin(address account) external { 38 | revokeRole(DEFAULT_ADMIN_ROLE, account); 39 | } 40 | 41 | /** 42 | * @notice Checks if the account provided is an admin. 43 | * @param account The address to check. 44 | * @return approved True if the account is an admin. 45 | * @dev This call is used by the royalty registry contract. 46 | */ 47 | function isAdmin(address account) public view returns (bool approved) { 48 | approved = hasRole(DEFAULT_ADMIN_ROLE, account); 49 | } 50 | 51 | /** 52 | * @notice This empty reserved space is put in place to allow future versions to add new 53 | * variables without shifting down storage in the inheritance chain. 54 | * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps 55 | */ 56 | uint256[1000] private __gap; 57 | } 58 | -------------------------------------------------------------------------------- /contracts/mixins/roles/MinterRole.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.12; 4 | 5 | import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; 6 | import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol"; 7 | 8 | import "./AdminRole.sol"; 9 | 10 | /** 11 | * @title Defines a role for minter accounts. 12 | * @dev Wraps a role from OpenZeppelin's AccessControl for easy integration. 13 | */ 14 | abstract contract MinterRole is Initializable, AccessControlUpgradeable, AdminRole { 15 | /** 16 | * @notice The `role` type used for approve minters. 17 | * @return `keccak256("MINTER_ROLE")` 18 | */ 19 | bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); 20 | 21 | modifier onlyMinterOrAdmin() { 22 | require(isMinter(msg.sender) || isAdmin(msg.sender), "MinterRole: Must have the minter or admin role"); 23 | _; 24 | } 25 | 26 | function _initializeMinterRole(address minter) internal onlyInitializing { 27 | // Grant the role to a specified account 28 | _grantRole(MINTER_ROLE, minter); 29 | } 30 | 31 | /** 32 | * @notice Adds an account as an approved minter. 33 | * @dev Only callable by admins, as enforced by `grantRole`. 34 | * @param account The address to be approved. 35 | */ 36 | function grantMinter(address account) external { 37 | grantRole(MINTER_ROLE, account); 38 | } 39 | 40 | /** 41 | * @notice Removes an account from the set of approved minters. 42 | * @dev Only callable by admins, as enforced by `revokeRole`. 43 | * @param account The address to be removed. 44 | */ 45 | function revokeMinter(address account) external { 46 | revokeRole(MINTER_ROLE, account); 47 | } 48 | 49 | /** 50 | * @notice Checks if the account provided is an minter. 51 | * @param account The address to check. 52 | * @return approved True if the account is an minter. 53 | */ 54 | function isMinter(address account) public view returns (bool approved) { 55 | approved = hasRole(MINTER_ROLE, account); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /contracts/mixins/shared/Constants.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.12; 4 | 5 | /// Constant values shared across mixins. 6 | 7 | /** 8 | * @dev 100% in basis points. 9 | */ 10 | uint256 constant BASIS_POINTS = 10000; 11 | 12 | /** 13 | * @dev The default admin role defined by OZ ACL modules. 14 | */ 15 | bytes32 constant DEFAULT_ADMIN_ROLE = 0x00; 16 | 17 | /** 18 | * @dev Cap the number of royalty recipients. 19 | * A cap is required to ensure gas costs are not too high when a sale is settled. 20 | */ 21 | uint256 constant MAX_ROYALTY_RECIPIENTS = 5; 22 | 23 | /** 24 | * @dev The minimum increase of 10% required when making an offer or placing a bid. 25 | */ 26 | uint256 constant MIN_PERCENT_INCREMENT_DENOMINATOR = BASIS_POINTS / 1000; 27 | 28 | /** 29 | * @dev The gas limit used when making external read-only calls. 30 | * This helps to ensure that external calls does not prevent the market from executing. 31 | */ 32 | uint256 constant READ_ONLY_GAS_LIMIT = 40000; 33 | 34 | /** 35 | * @dev Default royalty cut paid out on secondary sales. 36 | * Set to 10% of the secondary sale. 37 | */ 38 | uint96 constant ROYALTY_IN_BASIS_POINTS = 1000; 39 | 40 | /** 41 | * @dev 10%, expressed as a denominator for more efficient calculations. 42 | */ 43 | uint256 constant ROYALTY_RATIO = BASIS_POINTS / ROYALTY_IN_BASIS_POINTS; 44 | 45 | /** 46 | * @dev The gas limit to send ETH to multiple recipients, enough for a 5-way split. 47 | */ 48 | uint256 constant SEND_VALUE_GAS_LIMIT_MULTIPLE_RECIPIENTS = 210000; 49 | 50 | /** 51 | * @dev The gas limit to send ETH to a single recipient, enough for a contract with a simple receiver. 52 | */ 53 | uint256 constant SEND_VALUE_GAS_LIMIT_SINGLE_RECIPIENT = 20000; 54 | -------------------------------------------------------------------------------- /contracts/mixins/shared/ContractFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.12; 4 | 5 | import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol"; 6 | 7 | import "../../interfaces/ICollectionFactory.sol"; 8 | 9 | /** 10 | * @title Stores a reference to the factory which is used to create contract proxies. 11 | */ 12 | abstract contract ContractFactory { 13 | using AddressUpgradeable for address; 14 | 15 | /** 16 | * @notice The address of the factory which was used to create this contract. 17 | * @return The factory contract address. 18 | */ 19 | address public immutable contractFactory; 20 | 21 | modifier onlyContractFactory() { 22 | require(msg.sender == contractFactory, "ContractFactory: Caller is not the factory"); 23 | _; 24 | } 25 | 26 | /** 27 | * @notice Initialize the template's immutable variables. 28 | * @param _contractFactory The factory which will be used to create these contracts. 29 | */ 30 | constructor(address _contractFactory) { 31 | require(_contractFactory.isContract(), "ContractFactory: Factory is not a contract"); 32 | contractFactory = _contractFactory; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /contracts/mixins/shared/FETHNode.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.12; 4 | 5 | import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol"; 6 | 7 | import "../../interfaces/IFethMarket.sol"; 8 | 9 | error FETHNode_FETH_Address_Is_Not_A_Contract(); 10 | error FETHNode_Only_FETH_Can_Transfer_ETH(); 11 | 12 | /** 13 | * @title A mixin for interacting with the FETH contract. 14 | */ 15 | abstract contract FETHNode { 16 | using AddressUpgradeable for address; 17 | using AddressUpgradeable for address payable; 18 | 19 | /// @notice The FETH ERC-20 token for managing escrow and lockup. 20 | IFethMarket internal immutable feth; 21 | 22 | constructor(address _feth) { 23 | if (!_feth.isContract()) { 24 | revert FETHNode_FETH_Address_Is_Not_A_Contract(); 25 | } 26 | 27 | feth = IFethMarket(_feth); 28 | } 29 | 30 | /** 31 | * @notice Only used by FETH. Any direct transfer from users will revert. 32 | */ 33 | receive() external payable { 34 | if (msg.sender != address(feth)) { 35 | revert FETHNode_Only_FETH_Can_Transfer_ETH(); 36 | } 37 | } 38 | 39 | /** 40 | * @notice Withdraw the msg.sender's available FETH balance if they requested more than the msg.value provided. 41 | * @dev This may revert if the msg.sender is non-receivable. 42 | * This helper should not be used anywhere that may lead to locked assets. 43 | * @param totalAmount The total amount of ETH required (including the msg.value). 44 | * @param shouldRefundSurplus If true, refund msg.value - totalAmount to the msg.sender. 45 | */ 46 | function _tryUseFETHBalance(uint256 totalAmount, bool shouldRefundSurplus) internal { 47 | if (totalAmount > msg.value) { 48 | // Withdraw additional ETH required from the user's available FETH balance. 49 | unchecked { 50 | // The if above ensures delta will not underflow. 51 | uint256 delta = totalAmount - msg.value; 52 | // Withdraw ETH from the user's account in the FETH token contract, 53 | // making the funds available in this contract as ETH. 54 | feth.marketWithdrawFrom(msg.sender, delta); 55 | } 56 | } else if (shouldRefundSurplus && totalAmount < msg.value) { 57 | // Return any surplus ETH to the user. 58 | unchecked { 59 | // The if above ensures this will not underflow 60 | payable(msg.sender).sendValue(msg.value - totalAmount); 61 | } 62 | } 63 | } 64 | 65 | /** 66 | * @notice Gets the FETH contract used to escrow offer funds. 67 | * @return fethAddress The FETH contract address. 68 | */ 69 | function getFethAddress() external view returns (address fethAddress) { 70 | fethAddress = address(feth); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /contracts/mixins/shared/FoundationTreasuryNode.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.12; 4 | 5 | import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol"; 6 | 7 | import "../../interfaces/IAdminRole.sol"; 8 | import "../../interfaces/IOperatorRole.sol"; 9 | 10 | error FoundationTreasuryNode_Address_Is_Not_A_Contract(); 11 | error FoundationTreasuryNode_Caller_Not_Admin(); 12 | error FoundationTreasuryNode_Caller_Not_Operator(); 13 | 14 | /** 15 | * @title A mixin that stores a reference to the Foundation treasury contract. 16 | * @notice The treasury collects fees and defines admin/operator roles. 17 | */ 18 | abstract contract FoundationTreasuryNode { 19 | using AddressUpgradeable for address payable; 20 | 21 | /// @dev This value was replaced with an immutable version. 22 | address payable private __gap_was_treasury; 23 | 24 | /// @notice The address of the treasury contract. 25 | address payable private immutable treasury; 26 | 27 | /// @notice Requires the caller is a Foundation admin. 28 | modifier onlyFoundationAdmin() { 29 | if (!IAdminRole(treasury).isAdmin(msg.sender)) { 30 | revert FoundationTreasuryNode_Caller_Not_Admin(); 31 | } 32 | _; 33 | } 34 | 35 | /// @notice Requires the caller is a Foundation operator. 36 | modifier onlyFoundationOperator() { 37 | if (!IOperatorRole(treasury).isOperator(msg.sender)) { 38 | revert FoundationTreasuryNode_Caller_Not_Operator(); 39 | } 40 | _; 41 | } 42 | 43 | /** 44 | * @notice Set immutable variables for the implementation contract. 45 | * @dev Assigns the treasury contract address. 46 | */ 47 | constructor(address payable _treasury) { 48 | if (!_treasury.isContract()) { 49 | revert FoundationTreasuryNode_Address_Is_Not_A_Contract(); 50 | } 51 | treasury = _treasury; 52 | } 53 | 54 | /** 55 | * @notice Gets the Foundation treasury contract. 56 | * @dev This call is used in the royalty registry contract. 57 | * @return treasuryAddress The address of the Foundation treasury contract. 58 | */ 59 | function getFoundationTreasury() public view returns (address payable treasuryAddress) { 60 | return treasury; 61 | } 62 | 63 | /** 64 | * @notice This empty reserved space is put in place to allow future versions to add new 65 | * variables without shifting down storage in the inheritance chain. 66 | * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps 67 | */ 68 | uint256[2000] private __gap; 69 | } 70 | -------------------------------------------------------------------------------- /contracts/mixins/shared/Gap10000.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.12; 4 | 5 | /** 6 | * @title A placeholder contract leaving room for new mixins to be added to the future. 7 | */ 8 | abstract contract Gap10000 { 9 | /** 10 | * @notice This empty reserved space is put in place to allow future versions to add new 11 | * variables without shifting down storage in the inheritance chain. 12 | * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps 13 | */ 14 | uint256[10000] private __gap; 15 | } 16 | -------------------------------------------------------------------------------- /contracts/mixins/shared/MarketSharedCore.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.12; 4 | 5 | import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; 6 | 7 | import "./FETHNode.sol"; 8 | 9 | /** 10 | * @title A place for common modifiers and functions used by various market mixins, if any. 11 | * @dev This also leaves a gap which can be used to add a new mixin to the top of the inheritance tree. 12 | */ 13 | abstract contract MarketSharedCore is FETHNode { 14 | /** 15 | * @notice Checks who the seller for an NFT is if listed in this market. 16 | * @param nftContract The address of the NFT contract. 17 | * @param tokenId The id of the NFT. 18 | * @return seller The seller which listed this NFT for sale, or address(0) if not listed. 19 | */ 20 | function getSellerOf(address nftContract, uint256 tokenId) external view returns (address payable seller) { 21 | return _getSellerOf(nftContract, tokenId); 22 | } 23 | 24 | /** 25 | * @notice Checks who the seller for an NFT is if listed in this market. 26 | */ 27 | function _getSellerOf(address nftContract, uint256 tokenId) internal view virtual returns (address payable seller); 28 | 29 | /** 30 | * @notice Checks who the seller for an NFT is if listed in this market. 31 | */ 32 | function _getSellerOrOwnerOf(address nftContract, uint256 tokenId) 33 | internal 34 | view 35 | virtual 36 | returns (address payable sellerOrOwner); 37 | 38 | /** 39 | * @notice This empty reserved space is put in place to allow future versions to add new 40 | * variables without shifting down storage in the inheritance chain. 41 | * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps 42 | */ 43 | uint256[500] private __gap; 44 | } 45 | -------------------------------------------------------------------------------- /contracts/mixins/shared/OZERC165Checker.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.12; 4 | 5 | /** 6 | * From https://github.com/OpenZeppelin/openzeppelin-contracts 7 | * Copying the method below which is currently unreleased. 8 | */ 9 | 10 | import "@openzeppelin/contracts/utils/introspection/IERC165.sol"; 11 | 12 | /** 13 | * @title Library to query ERC165 support. 14 | * @dev Library used to query support of an interface declared via {IERC165}. 15 | * 16 | * Note that these functions return the actual result of the query: they do not 17 | * `revert` if an interface is not supported. It is up to the caller to decide 18 | * what to do in these cases. 19 | */ 20 | library OZERC165Checker { 21 | /** 22 | * @notice Query if a contract implements an interface, does not check ERC165 support 23 | * @param account The address of the contract to query for support of an interface 24 | * @param interfaceId The interface identifier, as specified in ERC-165 25 | * @return true if the contract at account indicates support of the interface with 26 | * identifier interfaceId, false otherwise 27 | * @dev Assumes that account contains a contract that supports ERC165, otherwise 28 | * the behavior of this method is undefined. This precondition can be checked 29 | * with {supportsERC165}. 30 | * Interface identification is specified in ERC-165. 31 | */ 32 | function supportsERC165InterfaceUnchecked(address account, bytes4 interfaceId) internal view returns (bool) { 33 | bytes memory encodedParams = abi.encodeWithSelector(IERC165.supportsInterface.selector, interfaceId); 34 | (bool success, bytes memory result) = account.staticcall{ gas: 30000 }(encodedParams); 35 | if (result.length < 32) return false; 36 | return success && abi.decode(result, (uint256)) > 0; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /contracts/mixins/shared/SendValueWithFallbackWithdraw.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.12; 4 | 5 | import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol"; 6 | 7 | import "./FETHNode.sol"; 8 | 9 | /** 10 | * @title A mixin for sending ETH with a fallback withdraw mechanism. 11 | * @notice Attempt to send ETH and if the transfer fails or runs out of gas, store the balance 12 | * in the FETH token contract for future withdrawal instead. 13 | * @dev This mixin was recently switched to escrow funds in FETH. 14 | * Once we have confirmed all pending balances have been withdrawn, we can remove the escrow tracking here. 15 | */ 16 | abstract contract SendValueWithFallbackWithdraw is FETHNode { 17 | using AddressUpgradeable for address payable; 18 | 19 | /// @dev Removing old unused variables in an upgrade safe way. 20 | uint256 private __gap_was_pendingWithdrawals; 21 | 22 | /** 23 | * @notice Emitted when escrowed funds are withdrawn to FETH. 24 | * @param user The account which has withdrawn ETH. 25 | * @param amount The amount of ETH which has been withdrawn. 26 | */ 27 | event WithdrawalToFETH(address indexed user, uint256 amount); 28 | 29 | /** 30 | * @notice Attempt to send a user or contract ETH. 31 | * If it fails store the amount owned for later withdrawal in FETH. 32 | * @dev This may fail when sending ETH to a contract that is non-receivable or exceeds the gas limit specified. 33 | */ 34 | function _sendValueWithFallbackWithdraw( 35 | address payable user, 36 | uint256 amount, 37 | uint256 gasLimit 38 | ) internal { 39 | if (amount == 0) { 40 | return; 41 | } 42 | // Cap the gas to prevent consuming all available gas to block a tx from completing successfully 43 | // solhint-disable-next-line avoid-low-level-calls 44 | (bool success, ) = user.call{ value: amount, gas: gasLimit }(""); 45 | if (!success) { 46 | // Store the funds that failed to send for the user in the FETH token 47 | feth.depositFor{ value: amount }(user); 48 | emit WithdrawalToFETH(user, amount); 49 | } 50 | } 51 | 52 | /** 53 | * @notice This empty reserved space is put in place to allow future versions to add new 54 | * variables without shifting down storage in the inheritance chain. 55 | * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps 56 | */ 57 | uint256[999] private __gap; 58 | } 59 | -------------------------------------------------------------------------------- /contracts/mixins/treasury/AdminRole.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.12; 4 | 5 | import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; 6 | 7 | import "./OZAccessControlUpgradeable.sol"; 8 | 9 | /** 10 | * @title Defines a role for Foundation admin accounts. 11 | * @dev Wraps the default admin role from OpenZeppelin's AccessControl for easy integration. 12 | */ 13 | abstract contract AdminRole is Initializable, OZAccessControlUpgradeable { 14 | function _initializeAdminRole(address admin) internal onlyInitializing { 15 | // Grant the role to a specified account 16 | _setupRole(DEFAULT_ADMIN_ROLE, admin); 17 | } 18 | 19 | modifier onlyAdmin() { 20 | require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "AdminRole: caller does not have the Admin role"); 21 | _; 22 | } 23 | 24 | /** 25 | * @notice Adds the account to the list of approved admins. 26 | * @dev Only callable by admins as enforced by `grantRole`. 27 | * @param account The address to be approved. 28 | */ 29 | function grantAdmin(address account) external { 30 | grantRole(DEFAULT_ADMIN_ROLE, account); 31 | } 32 | 33 | /** 34 | * @notice Removes the account from the list of approved admins. 35 | * @dev Only callable by admins as enforced by `revokeRole`. 36 | * @param account The address to be removed from the approved list. 37 | */ 38 | function revokeAdmin(address account) external { 39 | revokeRole(DEFAULT_ADMIN_ROLE, account); 40 | } 41 | 42 | /** 43 | * @notice Returns one of the admins by index. 44 | * @param index The index of the admin to return from 0 to getAdminMemberCount() - 1. 45 | * @return account The address of the admin. 46 | */ 47 | function getAdminMember(uint256 index) external view returns (address account) { 48 | account = getRoleMember(DEFAULT_ADMIN_ROLE, index); 49 | } 50 | 51 | /** 52 | * @notice Checks how many accounts have been granted admin access. 53 | * @return count The number of accounts with admin access. 54 | */ 55 | function getAdminMemberCount() external view returns (uint256 count) { 56 | count = getRoleMemberCount(DEFAULT_ADMIN_ROLE); 57 | } 58 | 59 | /** 60 | * @notice Checks if the account provided is an admin. 61 | * @param account The address to check. 62 | * @return approved True if the account is an admin. 63 | * @dev This call is used by the royalty registry contract. 64 | */ 65 | function isAdmin(address account) external view returns (bool approved) { 66 | approved = hasRole(DEFAULT_ADMIN_ROLE, account); 67 | } 68 | 69 | /** 70 | * @notice This empty reserved space is put in place to allow future versions to add new 71 | * variables without shifting down storage in the inheritance chain. 72 | * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps 73 | */ 74 | uint256[1000] private __gap; 75 | } 76 | -------------------------------------------------------------------------------- /contracts/mixins/treasury/CollateralManagement.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.12; 4 | 5 | import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol"; 6 | 7 | import "./AdminRole.sol"; 8 | 9 | error CollateralManagement_Cannot_Withdraw_To_Address_Zero(); 10 | error CollateralManagement_Cannot_Withdraw_To_Self(); 11 | 12 | /** 13 | * @title Enables deposits and withdrawals. 14 | */ 15 | abstract contract CollateralManagement is AdminRole { 16 | using AddressUpgradeable for address payable; 17 | 18 | /** 19 | * @notice Emitted when funds are withdrawn from this contract. 20 | * @param to The address which received the ETH withdrawn. 21 | * @param amount The amount of ETH which was withdrawn. 22 | */ 23 | event FundsWithdrawn(address indexed to, uint256 amount); 24 | 25 | /** 26 | * @notice Accept native currency payments (i.e. fees) 27 | */ 28 | // solhint-disable-next-line no-empty-blocks 29 | receive() external payable {} 30 | 31 | /** 32 | * @notice Allows an admin to withdraw funds. 33 | * @param to Address to receive the withdrawn funds 34 | * @param amount Amount to withdrawal or 0 to withdraw all available funds 35 | */ 36 | function withdrawFunds(address payable to, uint256 amount) external onlyAdmin { 37 | if (amount == 0) { 38 | amount = address(this).balance; 39 | } 40 | if (to == address(0)) { 41 | revert CollateralManagement_Cannot_Withdraw_To_Address_Zero(); 42 | } else if (to == address(this)) { 43 | revert CollateralManagement_Cannot_Withdraw_To_Self(); 44 | } 45 | to.sendValue(amount); 46 | 47 | emit FundsWithdrawn(to, amount); 48 | } 49 | 50 | /** 51 | * @notice This empty reserved space is put in place to allow future versions to add new 52 | * variables without shifting down storage in the inheritance chain. 53 | * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps 54 | */ 55 | uint256[1000] private __gap; 56 | } 57 | -------------------------------------------------------------------------------- /contracts/mixins/treasury/OperatorRole.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.12; 4 | 5 | import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; 6 | 7 | import "./OZAccessControlUpgradeable.sol"; 8 | 9 | /** 10 | * @title Defines a role for Foundation operator accounts. 11 | * @dev Wraps a role from OpenZeppelin's AccessControl for easy integration. 12 | */ 13 | abstract contract OperatorRole is Initializable, OZAccessControlUpgradeable { 14 | bytes32 private constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE"); 15 | 16 | /** 17 | * @notice Adds the account to the list of approved operators. 18 | * @dev Only callable by admins as enforced by `grantRole`. 19 | * @param account The address to be approved. 20 | */ 21 | function grantOperator(address account) external { 22 | grantRole(OPERATOR_ROLE, account); 23 | } 24 | 25 | /** 26 | * @notice Removes the account from the list of approved operators. 27 | * @dev Only callable by admins as enforced by `revokeRole`. 28 | * @param account The address to be removed from the approved list. 29 | */ 30 | function revokeOperator(address account) external { 31 | revokeRole(OPERATOR_ROLE, account); 32 | } 33 | 34 | /** 35 | * @notice Returns one of the operator by index. 36 | * @param index The index of the operator to return from 0 to getOperatorMemberCount() - 1. 37 | * @return account The address of the operator. 38 | */ 39 | function getOperatorMember(uint256 index) external view returns (address account) { 40 | account = getRoleMember(OPERATOR_ROLE, index); 41 | } 42 | 43 | /** 44 | * @notice Checks how many accounts have been granted operator access. 45 | * @return count The number of accounts with operator access. 46 | */ 47 | function getOperatorMemberCount() external view returns (uint256 count) { 48 | count = getRoleMemberCount(OPERATOR_ROLE); 49 | } 50 | 51 | /** 52 | * @notice Checks if the account provided is an operator. 53 | * @param account The address to check. 54 | * @return approved True if the account is an operator. 55 | */ 56 | function isOperator(address account) external view returns (bool approved) { 57 | approved = hasRole(OPERATOR_ROLE, account); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /contracts/mocks/BasicERC721WithAccessControlMock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.12; 4 | 5 | import "@openzeppelin/contracts/access/AccessControl.sol"; 6 | import "./BasicERC721.sol"; 7 | 8 | contract BasicERC721WithAccessControlMock is BasicERC721, AccessControl { 9 | constructor() { 10 | _setupRole(DEFAULT_ADMIN_ROLE, msg.sender); 11 | } 12 | 13 | function supportsInterface(bytes4 interfaceId) 14 | public 15 | view 16 | virtual 17 | override(BasicERC721, AccessControl) 18 | returns (bool) 19 | { 20 | return super.supportsInterface(interfaceId); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /contracts/mocks/BasicERC721WithoutOwnerOfRevert.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.12; 4 | 5 | import "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol"; 6 | 7 | import "./BasicERC721WithAccessControlMock.sol"; 8 | import "../interfaces/INFTDropCollectionMint.sol"; 9 | 10 | /** 11 | * @notice A basic ERC721 which has ownerOf return address(0) instead of reverting when token does not exist. 12 | */ 13 | contract BasicERC721WithoutOwnerOfRevert is 14 | INFTDropCollectionMint, 15 | ERC165Upgradeable, 16 | BasicERC721WithAccessControlMock 17 | { 18 | uint256 private id = 0; 19 | 20 | function ownerOf(uint256 tokenId) public view virtual override returns (address owner) { 21 | return _owners[tokenId]; 22 | } 23 | 24 | function supportsInterface(bytes4 interfaceId) 25 | public 26 | view 27 | override(ERC165Upgradeable, BasicERC721WithAccessControlMock) 28 | returns (bool) 29 | { 30 | if (type(INFTDropCollectionMint).interfaceId == interfaceId) { 31 | return true; 32 | } 33 | return super.supportsInterface(interfaceId); 34 | } 35 | 36 | function mintCountTo(uint16 count, address to) external returns (uint256 firstTokenId) { 37 | firstTokenId = id + 1; 38 | id += count; 39 | _owners[firstTokenId] = to; 40 | _balances[to] += count; 41 | } 42 | 43 | function numberOfTokensAvailableToMint() external view override returns (uint256 count) { 44 | return 100 - id; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /contracts/mocks/EmptyMockContract.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.12; 4 | 5 | contract EmptyMockContract { 6 | // Something must be included in order to generate the typechain file 7 | event DummyEvent(); 8 | } 9 | -------------------------------------------------------------------------------- /contracts/mocks/FETHMarketMock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.12; 4 | 5 | import "../interfaces/IFethMarket.sol"; 6 | 7 | contract FETHMarketMock { 8 | IFethMarket public feth; 9 | 10 | receive() external payable { 11 | require(msg.sender == address(feth), "Only receive from FETH"); 12 | } 13 | 14 | function setFeth(address _feth) public { 15 | feth = IFethMarket(_feth); 16 | } 17 | 18 | function marketLockupFor(address account, uint256 amount) public payable { 19 | feth.marketLockupFor{ value: msg.value }(account, amount); 20 | } 21 | 22 | function marketWithdrawLocked( 23 | address account, 24 | uint256 expiration, 25 | uint256 amount 26 | ) public { 27 | feth.marketWithdrawLocked(account, expiration, amount); 28 | } 29 | 30 | function marketWithdrawFrom(address account, uint256 amount) public { 31 | feth.marketWithdrawFrom(account, amount); 32 | } 33 | 34 | function marketUnlockFor( 35 | address account, 36 | uint256 expiration, 37 | uint256 amount 38 | ) public { 39 | feth.marketUnlockFor(account, expiration, amount); 40 | } 41 | 42 | function marketChangeLockup( 43 | address unlockFrom, 44 | uint256 unlockExpiration, 45 | uint256 unlockAmount, 46 | address depositFor, 47 | uint256 depositAmount 48 | ) external payable { 49 | feth.marketChangeLockup{ value: msg.value }(unlockFrom, unlockExpiration, unlockAmount, depositFor, depositAmount); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /contracts/mocks/MockNFT.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.12; 4 | 5 | import "@openzeppelin/contracts/access/Ownable.sol"; 6 | import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; 7 | 8 | contract MockNFT is Ownable, ERC721 { 9 | uint256 private nextTokenId; 10 | 11 | constructor() 12 | ERC721("MockNFT", "mNFT") // solhint-disable-next-line no-empty-blocks 13 | {} 14 | 15 | function mint() external onlyOwner { 16 | _mint(msg.sender, ++nextTokenId); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /contracts/mocks/MockTreasury.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.12; 4 | 5 | contract MockTreasury { 6 | // solhint-disable no-empty-blocks 7 | receive() external payable {} 8 | 9 | function isAdmin( 10 | address /*account*/ 11 | ) external pure returns (bool) { 12 | return true; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /contracts/mocks/NFTDropCollectionUnknownCreatorMock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.12; 4 | 5 | import "@openzeppelin/contracts/access/AccessControl.sol"; 6 | import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; 7 | 8 | import "../interfaces/INFTDropCollectionMint.sol"; 9 | 10 | contract NFTDropCollectionUnknownCreatorMock is INFTDropCollectionMint, ERC165, AccessControl { 11 | bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); 12 | bool private sold; 13 | 14 | constructor(address approvedMinter) { 15 | _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); 16 | _grantRole(MINTER_ROLE, msg.sender); 17 | if (approvedMinter != address(0)) { 18 | _grantRole(MINTER_ROLE, approvedMinter); 19 | } 20 | } 21 | 22 | function mintCountTo( 23 | uint16, /* count */ 24 | address /* to */ 25 | ) external returns (uint256 firstTokenId) { 26 | sold = true; 27 | return 1; 28 | } 29 | 30 | function numberOfTokensAvailableToMint() external view returns (uint256 count) { 31 | return sold ? 97 : 100; 32 | } 33 | 34 | function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, AccessControl) returns (bool) { 35 | if (interfaceId == type(INFTDropCollectionMint).interfaceId) { 36 | return true; 37 | } 38 | return super.supportsInterface(interfaceId); 39 | } 40 | 41 | function balanceOf( 42 | address /*account*/ 43 | ) external view returns (uint256) { 44 | return sold ? 3 : 0; 45 | } 46 | 47 | function ownerOf( 48 | uint256 /* tokenId */ 49 | ) external view returns (address) { 50 | if (sold) { 51 | return address(0x1); 52 | } 53 | return address(0); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /contracts/mocks/NFTDropCollectionWithoutAccessControl.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.12; 4 | 5 | import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; 6 | 7 | import "../interfaces/INFTDropCollectionMint.sol"; 8 | 9 | contract NFTDropCollectionWithoutAccessControl is INFTDropCollectionMint, ERC165 { 10 | bool private sold; 11 | 12 | function mintCountTo( 13 | uint16, /* count */ 14 | address /* to */ 15 | ) external returns (uint256 firstTokenId) { 16 | sold = true; 17 | return 1; 18 | } 19 | 20 | function numberOfTokensAvailableToMint() external pure returns (uint256 count) { 21 | return 100; 22 | } 23 | 24 | function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165) returns (bool) { 25 | if (interfaceId == type(INFTDropCollectionMint).interfaceId) { 26 | return true; 27 | } 28 | return super.supportsInterface(interfaceId); 29 | } 30 | 31 | function balanceOf( 32 | address /*account*/ 33 | ) external pure returns (uint256) { 34 | return 0; 35 | } 36 | 37 | function ownerOf( 38 | uint256 /* tokenId */ 39 | ) external view returns (address) { 40 | if (sold) { 41 | return address(0x1); 42 | } 43 | return address(0); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /contracts/mocks/NonReceivableMock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.12; 4 | 5 | import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol"; 6 | 7 | contract NonReceivableMock { 8 | using AddressUpgradeable for address; 9 | 10 | function callContract(address _contract, bytes memory _callData) public payable { 11 | _contract.functionCallWithValue(_callData, msg.value); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /contracts/mocks/ReceivableMock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.12; 4 | 5 | contract ReceivableMock { 6 | // solhint-disable no-empty-blocks 7 | receive() external payable {} 8 | } 9 | -------------------------------------------------------------------------------- /contracts/mocks/RoyaltyRegistry/MockRoyaltyRegistry.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.12; 4 | 5 | import "@manifoldxyz/royalty-registry-solidity/contracts/IRoyaltyRegistry.sol"; 6 | import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; 7 | 8 | contract MockRoyaltyRegistry is IRoyaltyRegistry, ERC165 { 9 | function setRoyaltyLookupAddress(address tokenAddress, address royaltyAddress) 10 | external 11 | virtual 12 | // solhint-disable-next-line no-empty-blocks 13 | { 14 | 15 | } 16 | 17 | function getRoyaltyLookupAddress(address tokenAddress) external view virtual returns (address) { 18 | return tokenAddress; 19 | } 20 | 21 | function overrideAllowed( 22 | address /* tokenAddress */ 23 | ) external view virtual returns (bool) { 24 | return true; 25 | } 26 | 27 | function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) { 28 | return interfaceId == type(IRoyaltyRegistry).interfaceId || super.supportsInterface(interfaceId); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /contracts/mocks/WETH9.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GNU-3.0 2 | 3 | pragma solidity ^0.8.12; 4 | 5 | contract WETH9 { 6 | string public name = "Wrapped Ether"; 7 | string public symbol = "WETH"; 8 | uint8 public decimals = 18; 9 | 10 | event Approval(address indexed src, address indexed guy, uint256 wad); 11 | event Transfer(address indexed src, address indexed dst, uint256 wad); 12 | event Deposit(address indexed dst, uint256 wad); 13 | event Withdrawal(address indexed src, uint256 wad); 14 | 15 | mapping(address => uint256) public balanceOf; 16 | mapping(address => mapping(address => uint256)) public allowance; 17 | 18 | receive() external payable { 19 | deposit(); 20 | } 21 | 22 | function deposit() public payable { 23 | balanceOf[msg.sender] += msg.value; 24 | emit Deposit(msg.sender, msg.value); 25 | } 26 | 27 | function withdraw(uint256 wad) public { 28 | // solhint-disable-next-line reason-string 29 | require(balanceOf[msg.sender] >= wad); 30 | balanceOf[msg.sender] -= wad; 31 | payable(msg.sender).transfer(wad); 32 | emit Withdrawal(msg.sender, wad); 33 | } 34 | 35 | function totalSupply() public view returns (uint256) { 36 | return address(this).balance; 37 | } 38 | 39 | function approve(address guy, uint256 wad) public returns (bool) { 40 | allowance[msg.sender][guy] = wad; 41 | emit Approval(msg.sender, guy, wad); 42 | return true; 43 | } 44 | 45 | function transfer(address dst, uint256 wad) public returns (bool) { 46 | return transferFrom(msg.sender, dst, wad); 47 | } 48 | 49 | function transferFrom( 50 | address src, 51 | address dst, 52 | uint256 wad 53 | ) public returns (bool) { 54 | // solhint-disable-next-line reason-string 55 | require(balanceOf[src] >= wad); 56 | 57 | if (src != msg.sender && allowance[src][msg.sender] != type(uint256).max) { 58 | // solhint-disable-next-line reason-string 59 | require(allowance[src][msg.sender] >= wad); 60 | allowance[src][msg.sender] -= wad; 61 | } 62 | 63 | balanceOf[src] -= wad; 64 | balanceOf[dst] += wad; 65 | 66 | emit Transfer(src, dst, wad); 67 | 68 | return true; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /contracts/mocks/collections/SequentialMintCollectionMock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.12; 4 | 5 | import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; 6 | 7 | import "../../mixins/collections/SequentialMintCollection.sol"; 8 | 9 | contract SequentialMintCollectionMock is Initializable, SequentialMintCollection { 10 | function initializeWithModifier(address payable _creator, uint32 _maxTokenId) external initializer { 11 | SequentialMintCollection._initializeSequentialMintCollection(_creator, _maxTokenId); 12 | } 13 | 14 | function initializeWithoutModifier(address payable _creator, uint32 _maxTokenId) external { 15 | SequentialMintCollection._initializeSequentialMintCollection(_creator, _maxTokenId); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/0266e5039778e2135b8ce9b7c0243400-A3510.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/0266e5039778e2135b8ce9b7c0243400-A3510.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/123c800a205b56354c1bd121a4b1b969-621EA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/123c800a205b56354c1bd121a4b1b969-621EA.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/1f355-981A7.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/1f374-505CF.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/1f389-5C738.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/1f3c1-445DC.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/1f3c6-621A1.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/1f43a-EB486.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/1f440-6C64D.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/1f44b-8A059.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/1f44c-59547.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/1f44d-27259.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/1f44f-3D381.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/1f49c-71A75.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/1f4af-4CFF5.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/1f4b0-53FFF.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/1f4c6-44E30.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/1f4db-4C577.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/1f4dc-AC641.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/1f506-55112.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/1f50d-195C0.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/1f525-8FE4F.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/1f554-A741F.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/1f602-168C5.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/1f604-BF863.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/1f605-42B43.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/1f606-BE94E.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/1f609-9EC67.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/1f60b-B5303.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/1f629-B734A.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/1f62d-02603.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/1f642-83E8A.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/1f64f-22B8D.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/1f680-A35CE.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/1f6a8-A8AB3.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/1f7e5-2145C.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/1f911-F346C.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/1f914-15707.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/1f919-1f3fb-2BA09.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/1f923-5854E.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/1f926-E188B.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/1f929-12865.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/1f972-F415D.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/1f973-88B39.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/1f97a-1F57B.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/1f9d1-5BC80.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/2696-15F4A.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/2705-0589F.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/2764-A3D25.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/2764-fe0f-200d-1f525-0B2B8.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/2a9faff195fe333526cfe6ae6fce1420-927E9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/2a9faff195fe333526cfe6ae6fce1420-927E9.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/32-20e3-EF1FD.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/39a730e4124c3b42df900de7d9d32973-2B76A.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/39a730e4124c3b42df900de7d9d32973-2B76A.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/3b01c38b7c5b905fd8e8a1d72f7d7492-6DE9E.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/3b01c38b7c5b905fd8e8a1d72f7d7492-6DE9E.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/4c405c8d602d7b3489d628bf20bfb843-60774.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/4c405c8d602d7b3489d628bf20bfb843-60774.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/4d708938ebe45c5d7a5f46ecbd3b144e-C87A6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/4d708938ebe45c5d7a5f46ecbd3b144e-C87A6.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/5b069e074423293b86222b4350052da5-77740.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/5b069e074423293b86222b4350052da5-77740.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/5f978694dec96afc1640b5426003aef1-3E74E.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/5f978694dec96afc1640b5426003aef1-3E74E.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/62208180fa28d8609e6f66d9ad7fca37-00B6C.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/62208180fa28d8609e6f66d9ad7fca37-00B6C.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/67594ee4b4d1fc03bca468327a0d145b-0C614.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/67594ee4b4d1fc03bca468327a0d145b-0C614.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/7295c56e5b235d31dcba650a186322b0-1BF1F.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/7295c56e5b235d31dcba650a186322b0-1BF1F.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/754850443909267567-E2D08.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/754850443909267567-E2D08.gif -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/773490663245348864-8086D.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/773490663245348864-8086D.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/809922172625748019-3756A.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/809922172625748019-3756A.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/851893157188599838-C23B5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/851893157188599838-C23B5.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/851893827027075142-F23DF.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/851893827027075142-F23DF.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/851893827089727568-5FD38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/851893827089727568-5FD38.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/851893827315826708-F59C0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/851893827315826708-F59C0.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/910676187288846397-518CD.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/910676187288846397-518CD.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/918262047433691247-911FE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/918262047433691247-911FE.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/927703596944998430-0A6FB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/927703596944998430-0A6FB.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/9503f567dabd2781b3d25827ceb83075-313E1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/9503f567dabd2781b3d25827ceb83075-313E1.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/970d2e2f00cd7ef2134a1a3f21326349-B9EEF.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/970d2e2f00cd7ef2134a1a3f21326349-B9EEF.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/977133670429261884-CA8EA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/977133670429261884-CA8EA.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/9bf2181404e658cab4039c07df56213f-EC9DE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/9bf2181404e658cab4039c07df56213f-EC9DE.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/9dae367c2914db90ec5f86da55c97b23-D9765.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/9dae367c2914db90ec5f86da55c97b23-D9765.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/C4-banner-7C19B.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/C4-banner-7C19B.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/PolygonScan-logo-circle-6FDB5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/PolygonScan-logo-circle-6FDB5.jpg -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/a36cb7b26a8fd70f4f296a94c3cf5a9d-2B57B.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/a36cb7b26a8fd70f4f296a94c3cf5a9d-2B57B.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/aa8a8cf504d883f45a141e445c26ec7b-0AED8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/aa8a8cf504d883f45a141e445c26ec7b-0AED8.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/aba7f6b23cd80ec9e8655016ce6ef443-929E9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/aba7f6b23cd80ec9e8655016ce6ef443-929E9.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/ae70f07a06c1c7e983291bb14a1bed7f-98458.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/ae70f07a06c1c7e983291bb14a1bed7f-98458.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/b07292365785e0b7bd0dab7dd7d9e09f-62888.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/b07292365785e0b7bd0dab7dd7d9e09f-62888.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/bugs-bunny-looney-tunes-BD3E0.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/bugs-bunny-looney-tunes-BD3E0.mp4 -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/c47e074275f9b2c05d095d0847bc18dd-CF0A6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/c47e074275f9b2c05d095d0847bc18dd-CF0A6.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/c9cb30134c634c9e02d0c64df4922803-1AAF2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/c9cb30134c634c9e02d0c64df4922803-1AAF2.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/cb23e87e4eb33d228ed3294f90188951-D6A20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/cb23e87e4eb33d228ed3294f90188951-D6A20.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/d38725fdb3f22651bef3993cf902a61d-F5783.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/d38725fdb3f22651bef3993cf902a61d-F5783.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/d6f1f56d219902e83e7f37cc225ffd0f-D4278.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/d6f1f56d219902e83e7f37cc225ffd0f-D4278.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/d7e057b14c0a958df354613bb7522913-1FD52.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/d7e057b14c0a958df354613bb7522913-1FD52.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/d7e24e0994d277993ed07845cac9ca8b-E4084.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/d7e24e0994d277993ed07845cac9ca8b-E4084.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/dfacc77d68962a991f5a4c3b7859b4c2-0A44D.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/dfacc77d68962a991f5a4c3b7859b4c2-0A44D.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/e8e42b0753ed4170607ecff76b81d17e-80898.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/e8e42b0753ed4170607ecff76b81d17e-80898.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/ggsans-italic-400-E988B.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/ggsans-italic-400-E988B.woff2 -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/ggsans-italic-500-0777F.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/ggsans-italic-500-0777F.woff2 -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/ggsans-italic-600-CB411.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/ggsans-italic-600-CB411.woff2 -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/ggsans-italic-700-891AC.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/ggsans-italic-700-891AC.woff2 -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/ggsans-italic-800-D36B0.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/ggsans-italic-800-D36B0.woff2 -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/ggsans-normal-400-1456D.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/ggsans-normal-400-1456D.woff2 -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/ggsans-normal-500-89CE5.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/ggsans-normal-500-89CE5.woff2 -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/ggsans-normal-600-C1EA8.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/ggsans-normal-600-C1EA8.woff2 -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/ggsans-normal-700-1949A.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/ggsans-normal-700-1949A.woff2 -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/ggsans-normal-800-58487.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/ggsans-normal-800-58487.woff2 -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/michael-scott-wink-82DFE.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/michael-scott-wink-82DFE.mp4 -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/oh-hey-ryan-reynolds-13ABB.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/oh-hey-ryan-reynolds-13ABB.mp4 -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/solarized-dark.min-BA98F.css: -------------------------------------------------------------------------------- 1 | .hljs{display:block;overflow-x:auto;padding:.5em;background:#002b36;color:#839496}.hljs-comment,.hljs-quote{color:#586e75}.hljs-keyword,.hljs-selector-tag,.hljs-addition{color:#859900}.hljs-number,.hljs-string,.hljs-meta .hljs-meta-string,.hljs-literal,.hljs-doctag,.hljs-regexp{color:#2aa198}.hljs-title,.hljs-section,.hljs-name,.hljs-selector-id,.hljs-selector-class{color:#268bd2}.hljs-attribute,.hljs-attr,.hljs-variable,.hljs-template-variable,.hljs-class .hljs-title,.hljs-type{color:#b58900}.hljs-symbol,.hljs-bullet,.hljs-subst,.hljs-meta,.hljs-meta .hljs-keyword,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-link{color:#cb4b16}.hljs-built_in,.hljs-deletion{color:#dc322f}.hljs-formula{background:#073642}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:bold} -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/spongebob-sad-96BA8.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/spongebob-sad-96BA8.mp4 -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/unknown-086F2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/unknown-086F2.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/unknown-CB0DC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/unknown-CB0DC.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/will-ferrell-stressed-out-73DD6.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].html_Files/will-ferrell-stressed-out-73DD6.mp4 -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].txt_Files/C4-banner-7C19B.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].txt_Files/C4-banner-7C19B.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].txt_Files/PolygonScan-logo-circle-6FDB5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].txt_Files/PolygonScan-logo-circle-6FDB5.jpg -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].txt_Files/bugs-bunny-looney-tunes-6263B.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].txt_Files/bugs-bunny-looney-tunes-6263B.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].txt_Files/maxresdefault-7ADB4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].txt_Files/maxresdefault-7ADB4.jpg -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].txt_Files/michael-scott-wink-76E4C.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].txt_Files/michael-scott-wink-76E4C.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].txt_Files/oh-hey-ryan-reynolds-80E2E.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].txt_Files/oh-hey-ryan-reynolds-80E2E.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].txt_Files/spongebob-sad-A44ED.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].txt_Files/spongebob-sad-A44ED.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].txt_Files/unknown-086F2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].txt_Files/unknown-086F2.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].txt_Files/unknown-CB0DC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].txt_Files/unknown-CB0DC.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].txt_Files/will-ferrell-stressed-out-65DCF.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2022-08-foundation/4bba2108615701e429bcfa0c5079eb5469811ee6/discord-export/Code4rena - ARCHIVE-Q3-2022 - ☑foundation-aug11 [1006281012931739698].txt_Files/will-ferrell-stressed-out-65DCF.png -------------------------------------------------------------------------------- /foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = 'contracts' 3 | test = 'test/foundry' 4 | libs = ['lib','node_modules'] 5 | remappings = ['forge-std/=lib/forge-std/src/','ds-test/=lib/forge-std/lib/ds-test/src/'] 6 | optimizer = true 7 | optimizer_runs = 1337 8 | 9 | # See more config options https://github.com/foundry-rs/foundry/tree/master/config -------------------------------------------------------------------------------- /gas-stories.txt: -------------------------------------------------------------------------------- 1 | Market 2 | ========================================================= 3 | NFTDropMarketFixedPriceSale 4 | ··························· 5 | [Collector] Mint 6 | 134,500 1st mint 7 | 146,800 1st mint w. buy referrer 8 | 117,400 2nd mint 9 | 129,700 2nd mint w. buy referrer 10 | 11 | [Collector] Mint Batch 12 | 359,100 1st Mint 10 13 | 371,400 1st Mint 10 w. buy referrer 14 | 342,000 2nd Mint 10 15 | 354,300 2nd Mint 10 w. buy referrer 16 | 17 | createFixedPriceSale 18 | 73,400 19 | 20 | NFT 21 | ========================================================= 22 | Collection 23 | ··························· 24 | Approve 25 | 48,900 26 | 27 | Burn 28 | 56,300 Last NFT 29 | 56,300 with other NFTs 30 | 31 | Mint 32 | 170,500 1st mint 33 | 195,200 1st mint & approve 34 | 153,400 2nd mint 35 | 158,200 2nd mint & approve [approval is redundant] 36 | 37 | Self Destruct 38 | 32,500 39 | 40 | Factory 41 | ··························· 42 | Create Collection 43 | 174,600 44 | 45 | Create NFTDropCollection 46 | 319,300 47 | 294,300 w.o Market as Minter 48 | 49 | NFTDropCollection 50 | ··························· 51 | [Creator] Mint 52 | 81,700 1st mint 53 | 64,600 2nd mint 54 | 55 | Burn 56 | 39,300 Last NFT 57 | 44,100 with other NFTs 58 | 59 | Self Destruct 60 | 34,500 61 | 62 | Update 63 | 33,200 MaxTokenId 64 | 41,200 PreReveal Content 65 | 34,700 Reveal Collection 66 | 67 | -------------------------------------------------------------------------------- /hardhat.config.ts: -------------------------------------------------------------------------------- 1 | import "@nomicfoundation/hardhat-chai-matchers"; 2 | import { HardhatUserConfig } from "hardhat/types"; 3 | import "@typechain/hardhat"; 4 | import "solidity-coverage"; 5 | import "hardhat-deploy"; 6 | import "hardhat-deploy-ethers"; 7 | import "@openzeppelin/hardhat-upgrades"; 8 | import "@nomiclabs/hardhat-etherscan"; 9 | import "hardhat-preprocessor"; 10 | import "hardhat-tracer"; 11 | import "hardhat-storage-layout"; 12 | import "hardhat-gas-reporter"; 13 | 14 | const config: HardhatUserConfig = { 15 | solidity: { 16 | version: "0.8.16", 17 | settings: { 18 | optimizer: { 19 | enabled: true, 20 | runs: 1337, 21 | }, 22 | }, 23 | }, 24 | typechain: { 25 | outDir: "src/typechain", 26 | target: "ethers-v5", 27 | externalArtifacts: ["node_modules/@manifoldxyz/royalty-registry-solidity/build/contracts/*.json"], 28 | }, 29 | gasReporter: { 30 | currency: "USD", 31 | gasPrice: 100, 32 | excludeContracts: ["mocks/"], 33 | }, 34 | }; 35 | 36 | export default config; 37 | -------------------------------------------------------------------------------- /hardhat.gas-stories.config.ts: -------------------------------------------------------------------------------- 1 | import { removeConsoleLog } from "hardhat-preprocessor"; 2 | import config from "./hardhat.config"; 3 | 4 | export default { 5 | ...config, 6 | preprocess: { 7 | eachLine: removeConsoleLog(() => true), 8 | }, 9 | paths: { 10 | ...config.paths, 11 | tests: "./test-gas-stories", 12 | }, 13 | }; 14 | -------------------------------------------------------------------------------- /helpers/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./utils"; 2 | -------------------------------------------------------------------------------- /helpers/utils.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber, providers } from "ethers"; 2 | import { ethers } from "hardhat"; 3 | import { ONE_HOUR } from "../test/helpers/constants"; 4 | 5 | export async function sleep(ms: number): Promise { 6 | return new Promise(resolve => setTimeout(resolve, ms)); 7 | } 8 | 9 | export async function sleepUntil(check: () => Promise): Promise { 10 | for (let i = 1; ; i++) { 11 | if (await check()) return; 12 | console.log("Waiting for tx to mine..."); 13 | await sleep(1000 * i); 14 | } 15 | } 16 | 17 | // As of 0.5.9 the OZ test helper for time does not work with Hardhat 18 | export async function increaseTime(seconds: number): Promise { 19 | const provider: providers.JsonRpcProvider = ethers.provider; 20 | await provider.send("evm_increaseTime", [seconds]); 21 | await advanceBlock(); 22 | } 23 | 24 | export async function increaseTimeTo(timestamp: number | BigNumber, shouldAdvanceBlock = true): Promise { 25 | timestamp = BigNumber.isBigNumber(timestamp) ? timestamp.toNumber() : timestamp; 26 | const provider: providers.JsonRpcProvider = ethers.provider; 27 | await provider.send("evm_setNextBlockTimestamp", [timestamp]); 28 | if (shouldAdvanceBlock) { 29 | await advanceBlock(); 30 | } 31 | } 32 | 33 | export async function increaseTimeToNextHour(): Promise { 34 | const time = await getBlockTime(); 35 | const timeToMoveTo = Math.ceil((time + 1) / ONE_HOUR) * ONE_HOUR; 36 | await increaseTimeTo(timeToMoveTo); 37 | } 38 | 39 | export async function getBlockTime(block: string | number = "latest"): Promise { 40 | const provider: providers.JsonRpcProvider = ethers.provider; 41 | return (await provider.getBlock(block)).timestamp; 42 | } 43 | 44 | export async function advanceBlock() { 45 | const provider: providers.JsonRpcProvider = ethers.provider; 46 | await provider.send("evm_mine", []); 47 | } 48 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "build": "hardhat typechain", 4 | "coverage": "hardhat coverage --solcoverjs ./.solcover.js --temp artifacts --testfiles \"./test/**/*.ts\"", 5 | "lint": "yarn prettier && yarn lint:sol && yarn lint:ts", 6 | "lint:sol": "solhint --config ./.solhint.json --fix \"contracts/**/*.sol\"", 7 | "lint:ts": "eslint --config ./.eslintrc.yaml --fix", 8 | "prettier": "prettier --config .prettierrc --write --list-different \"**/*.{js,json,md,sol,ts}\"", 9 | "test": "hardhat test", 10 | "test-gas-stories": "hardhat --config hardhat.gas-stories.config.ts test" 11 | }, 12 | "devDependencies": { 13 | "@ensdomains/eth-ens-namehash": "2.0.15", 14 | "@ethersproject/abstract-signer": "5.6.2", 15 | "@ethersproject/bignumber": "5.6.2", 16 | "@manifoldxyz/royalty-registry-solidity": "1.0.9", 17 | "@nomicfoundation/hardhat-chai-matchers": "1.0.2", 18 | "@nomicfoundation/hardhat-toolbox": "1.0.2", 19 | "@nomiclabs/hardhat-ethers": "2.1.0", 20 | "@nomiclabs/hardhat-etherscan": "3.1.0", 21 | "@openzeppelin/contracts": "4.7.2", 22 | "@openzeppelin/contracts-upgradeable": "4.7.2", 23 | "@openzeppelin/hardhat-upgrades": "1.19.1", 24 | "@openzeppelin/test-helpers": "0.5.15", 25 | "@primitivefi/hardhat-dodoc": "0.2.3", 26 | "@typechain/ethers-v5": "9.0.0", 27 | "@typechain/hardhat": "6.1.2", 28 | "@types/chai": "4.3.3", 29 | "@types/fs-extra": "9.0.13", 30 | "@types/mocha": "9.1.1", 31 | "@types/node": "18.6.4", 32 | "@typescript-eslint/eslint-plugin": "5.32.0", 33 | "@typescript-eslint/parser": "5.32.0", 34 | "chai": "4.3.6", 35 | "chalk": "4.1.2", 36 | "coveralls": "3.1.1", 37 | "defender-admin-client": "1.25.0", 38 | "dotenv": "16.0.1", 39 | "eslint": "8.21.0", 40 | "eslint-config-prettier": "8.5.0", 41 | "eslint-plugin-no-only-tests": "^3.0.0", 42 | "ethereum-waffle": "3.4.4", 43 | "ethereumjs-util": "7.1.5", 44 | "ethers": "5.6.9", 45 | "fs-extra": "10.1.0", 46 | "hardhat": "2.10.1", 47 | "hardhat-contract-sizer": "2.6.1", 48 | "hardhat-deploy": "0.11.12", 49 | "hardhat-deploy-ethers": "0.3.0-beta.13", 50 | "hardhat-gas-reporter": "1.0.8", 51 | "hardhat-output-validator": "0.1.19", 52 | "hardhat-preprocessor": "0.1.4", 53 | "hardhat-storage-layout": "0.1.6", 54 | "hardhat-tracer": "1.1.0-rc.6", 55 | "mocha": "10.0.0", 56 | "npm-package-json-lint": "6.3.0", 57 | "npm-package-json-lint-config-default": "5.0.0", 58 | "prettier": "2.7.1", 59 | "prettier-plugin-solidity": "1.0.0-dev.23", 60 | "shelljs": "0.8.5", 61 | "solhint": "3.3.7", 62 | "solhint-plugin-prettier": "0.0.5", 63 | "solidity-coverage": "0.8.0-rc.1", 64 | "ts-node": "10.9.1", 65 | "typechain": "8.1.0", 66 | "typescript": "4.6.4" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /slither.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "filter_paths": "(mocks/|test/|node_modules/)" 3 | } 4 | -------------------------------------------------------------------------------- /slither/NFTCollection.md: -------------------------------------------------------------------------------- 1 | Summary 2 | 3 | - [unprotected-upgrade](#unprotected-upgrade) (1 results) (High) 4 | - [dead-code](#dead-code) (1 results) (Informational) 5 | - [solc-version](#solc-version) (14 results) (Informational) 6 | - [naming-convention](#naming-convention) (4 results) (Informational) 7 | 8 | ## unprotected-upgrade 9 | 10 | Impact: High 11 | Confidence: High 12 | 13 | - [ ] ID-0 14 | [NFTCollection](https://github.com/code-423n4/2022-08-foundation/blob/main/contracts/NFTCollection.sol#L28-L338) is an upgradeable contract that does not protect its initiliaze functions: [INFTCollectionInitializer.initialize(address,string,string)](https://github.com/code-423n4/2022-08-foundation/blob/main/contracts/interfaces/INFTCollectionInitializer.sol#L6-L10)[NFTCollection.initialize(address,string,string)](https://github.com/code-423n4/2022-08-foundation/blob/main/contracts/NFTCollection.sol#L105-L112). Anyone can delete the contract with: [NFTCollection.selfDestruct()](https://github.com/code-423n4/2022-08-foundation/blob/main/contracts/NFTCollection.sol#L230-L232) 15 | https://github.com/code-423n4/2022-08-foundation/blob/main/contracts/NFTCollection.sol#L28-L338 16 | 17 | > The template is initialized by the factory when `adminUpdateNFTCollectionImplementation` is called and instances are initialized by the factory when `createNFTCollection` is used to create them. So we are not vulnerable here. 18 | 19 | ## dead-code 20 | 21 | Impact: Informational 22 | Confidence: Medium 23 | 24 | - [ ] ID-1 25 | [AddressLibrary.callAndReturnContractAddress(CallWithoutValue)](https://github.com/code-423n4/2022-08-foundation/blob/main/contracts/libraries/AddressLibrary.sol#L34-L39) is never used and should be removed 26 | 27 | https://github.com/code-423n4/2022-08-foundation/blob/main/contracts/libraries/AddressLibrary.sol#L34-L39 28 | 29 | > Invalid. This is just a result of how I updated the repo in order to generate these results. 30 | 31 | ## solc-version 32 | 33 | Impact: Informational 34 | Confidence: High 35 | 36 | > We have opted into using the most recent version of Solidity. 37 | 38 | ## naming-convention 39 | 40 | Impact: Informational 41 | Confidence: High 42 | 43 | > With the exception of leading underscores used on some param names, our variables should consistently be using camel case format. 44 | -------------------------------------------------------------------------------- /slither/NFTDropCollection.md: -------------------------------------------------------------------------------- 1 | Summary 2 | 3 | - [unprotected-upgrade](#unprotected-upgrade) (1 results) (High) 4 | - [events-maths](#events-maths) (1 results) (Low) 5 | - [solc-version](#solc-version) (16 results) (Informational) 6 | - [naming-convention](#naming-convention) (13 results) (Informational) 7 | 8 | ## unprotected-upgrade 9 | 10 | Impact: High 11 | Confidence: High 12 | 13 | - [ ] ID-0 14 | [NFTDropCollection](https://github.com/code-423n4/2022-08-foundation/blob/main/contracts/NFTDropCollection.sol#L28-L305) is an upgradeable contract that does not protect its initiliaze functions: [INFTDropCollectionInitializer.initialize(address,string,string,string,bytes32,uint32,address,address)](https://github.com/code-423n4/2022-08-foundation/blob/main/contracts/interfaces/INFTDropCollectionInitializer.sol#L6-L15)[NFTDropCollection.initialize(address,string,string,string,bytes32,uint32,address,address)](https://github.com/code-423n4/2022-08-foundation/blob/main/contracts/NFTDropCollection.sol#L120-L152). Anyone can delete the contract with: [NFTDropCollection.selfDestruct()](https://github.com/code-423n4/2022-08-foundation/blob/main/contracts/NFTDropCollection.sol#L209-L211) 15 | https://github.com/code-423n4/2022-08-foundation/blob/main/contracts/NFTDropCollection.sol#L28-L305 16 | 17 | > The template is initialized by the factory when `adminUpdateNFTDropCollectionImplementation` is called and instances are initialized by the factory when `createNFTDropCollection` is used to create them. So we are not vulnerable here. 18 | 19 | ## events-maths 20 | 21 | Impact: Low 22 | Confidence: Medium 23 | 24 | - [ ] ID-1 25 | [NFTDropCollection.mintCountTo(uint16,address)](https://github.com/code-423n4/2022-08-foundation/blob/main/contracts/NFTDropCollection.sol#L171-L187) should emit an event for: - [latestTokenId = latestTokenId + count](https://github.com/code-423n4/2022-08-foundation/blob/main/contracts/NFTDropCollection.sol#L178) 26 | 27 | https://github.com/code-423n4/2022-08-foundation/blob/main/contracts/NFTDropCollection.sol#L171-L187 28 | 29 | > This information is implicitly available from the most recent mint event (`Transfer` where from is address(0)). 30 | 31 | ## solc-version 32 | 33 | Impact: Informational 34 | Confidence: High 35 | 36 | > We have opted into using the most recent version of Solidity. 37 | 38 | ## naming-convention 39 | 40 | Impact: Informational 41 | Confidence: High 42 | 43 | > With the exception of leading underscores used on some param names, our variables should consistently be using camel case format. 44 | -------------------------------------------------------------------------------- /src/helpers/bytes.ts: -------------------------------------------------------------------------------- 1 | import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; 2 | 3 | /** 4 | * Returns the byte position of the account's address within an array of bytes. 5 | */ 6 | export function positionOfAddress(bytes: string, account: SignerWithAddress | string): number { 7 | let address: string; 8 | if (typeof account === "string") { 9 | address = account; 10 | } else { 11 | address = account.address; 12 | } 13 | return bytes.indexOf(address.substr(2).toLowerCase()) / 2 - 1; 14 | } 15 | -------------------------------------------------------------------------------- /src/helpers/process.ts: -------------------------------------------------------------------------------- 1 | import { execSync, spawn } from "child_process"; 2 | 3 | export async function sleep(ms: number): Promise { 4 | return new Promise(resolve => setTimeout(resolve, ms)); 5 | } 6 | 7 | let processCounter = 0; 8 | export function start(command: string): void { 9 | const processId = processCounter++; 10 | console.log(`Starting ${processId}: ${command}`); 11 | const process = spawn("/bin/sh", ["-c", `${command}`]); 12 | 13 | process.stdout.on("data", data => { 14 | console.log(`> ${processId}: ${data}`); 15 | }); 16 | 17 | process.stderr.on("data", data => { 18 | console.error(`> ${processId}: ${data}`); 19 | }); 20 | 21 | process.on("close", code => { 22 | console.log(`> ${processId} process exited with code ${code}`); 23 | }); 24 | 25 | process.on("error", error => { 26 | console.error(`> ${processId} failed to start: ${error}`); 27 | }); 28 | } 29 | 30 | export function run(command: string): void { 31 | console.log(`Running: ${command}`); 32 | execSync(command); 33 | } 34 | -------------------------------------------------------------------------------- /src/helpers/providerHelpers.ts: -------------------------------------------------------------------------------- 1 | import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; 2 | import { BigNumber } from "ethers"; 3 | import { ethers, network } from "hardhat"; 4 | 5 | export async function setAccountETHBalance( 6 | address: SignerWithAddress, 7 | newBalance: BigNumber = ethers.utils.parseEther("1000"), 8 | ) { 9 | const balance = ethers.utils.hexStripZeros(newBalance.toHexString()); 10 | await network.provider.send("hardhat_setBalance", [address.address, balance]); 11 | } 12 | -------------------------------------------------------------------------------- /src/testBehaviors/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./supportsInterface.behavior"; 2 | -------------------------------------------------------------------------------- /src/types/openzeppelin__test-helpers.d.ts: -------------------------------------------------------------------------------- 1 | declare module "@openzeppelin/test-helpers"; 2 | -------------------------------------------------------------------------------- /test-gas-stories/collections.ts: -------------------------------------------------------------------------------- 1 | import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; 2 | import { ContractTransaction } from "ethers"; 3 | import { ethers } from "hardhat"; 4 | import { NFTCollectionFactory } from "../src/typechain"; 5 | import { getNFTCollection } from "../test/helpers/collectionContract"; 6 | import { TestContracts } from "../test/helpers/deploy"; 7 | import { testIpfsPath } from "../test/helpers/testData"; 8 | import { story } from "./gas-stories"; 9 | 10 | describe("Collections", () => { 11 | let creator: SignerWithAddress; 12 | let mockMarket: SignerWithAddress; 13 | let contracts: TestContracts; 14 | let nftCollectionFactoryV2: NFTCollectionFactory; 15 | let tx: ContractTransaction; 16 | 17 | beforeEach(async function () { 18 | [, creator, mockMarket] = await ethers.getSigners(); 19 | contracts = this.contracts; 20 | nftCollectionFactoryV2 = contracts.nftCollectionFactoryV2!; // eslint-disable-line @typescript-eslint/no-non-null-assertion 21 | }); 22 | 23 | it("Create & mint", async () => { 24 | tx = await nftCollectionFactoryV2.connect(creator).createNFTCollection("TEST", "TEST", 42); 25 | await story("NFT", "Factory", "Create Collection", "", tx); 26 | const collection = await getNFTCollection(tx, creator); 27 | 28 | tx = await collection.connect(creator).mint(testIpfsPath[1]); 29 | await story("NFT", "Collection", "Mint", "1st mint", tx); 30 | 31 | tx = await collection.connect(creator).mint(testIpfsPath[2]); 32 | await story("NFT", "Collection", "Mint", "2nd mint", tx); 33 | 34 | tx = await collection.connect(creator).setApprovalForAll(mockMarket.address, true); 35 | await story("NFT", "Collection", "Approve", "", tx); 36 | 37 | tx = await collection.connect(creator).burn(2); 38 | await story("NFT", "Collection", "Burn", "with other NFTs", tx); 39 | tx = await collection.connect(creator).burn(1); 40 | await story("NFT", "Collection", "Burn", "Last NFT", tx); 41 | 42 | tx = await collection.connect(creator).selfDestruct(); 43 | await story("NFT", "Collection", "Self Destruct", "", tx); 44 | }); 45 | 46 | it("Mint & approve", async () => { 47 | tx = await nftCollectionFactoryV2.connect(creator).createNFTCollection("TEST", "TEST", 42); 48 | const collection = await getNFTCollection(tx, creator); 49 | 50 | tx = await collection.connect(creator).mintAndApprove(testIpfsPath[1], mockMarket.address); 51 | await story("NFT", "Collection", "Mint", "1st mint & approve", tx); 52 | 53 | tx = await collection.connect(creator).mintAndApprove(testIpfsPath[2], mockMarket.address); 54 | await story("NFT", "Collection", "Mint", "2nd mint & approve", tx, "approval is redundant"); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /test/NFTDropMarket/fixedPrice/mintFromFixedPriceSale.ts: -------------------------------------------------------------------------------- 1 | import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; 2 | import { ContractTransaction } from "ethers"; 3 | import { ethers } from "hardhat"; 4 | import { deployAll, TestContracts } from "../../helpers/deploy"; 5 | import { assertAllLogs } from "../../helpers/logs"; 6 | import { snapshotEach } from "../../helpers/snapshot"; 7 | 8 | describe("NFTDropMarket / FixedPrice / mintFromFixedPriceSale", () => { 9 | const price = ethers.utils.parseEther("0.42"); 10 | const limitPerAccount = 3; 11 | 12 | let contracts: TestContracts; 13 | let deployer: SignerWithAddress; 14 | let creator: SignerWithAddress; 15 | let collector: SignerWithAddress; 16 | let tx: ContractTransaction; 17 | 18 | snapshotEach(async () => { 19 | [deployer, creator, collector] = await ethers.getSigners(); 20 | contracts = await deployAll(deployer, creator); 21 | await contracts.nftDropMarket 22 | .connect(creator) 23 | .createFixedPriceSale(contracts.nftDropCollection.address, price, limitPerAccount); 24 | }); 25 | 26 | describe("Mint 3", () => { 27 | const tokenId = 1; // first token 28 | const count = 3; 29 | 30 | beforeEach(async () => { 31 | tx = await contracts.nftDropMarket 32 | .connect(collector) 33 | .mintFromFixedPriceSale(contracts.nftDropCollection.address, count, ethers.constants.AddressZero, { 34 | value: price.mul(count), 35 | }); 36 | }); 37 | 38 | it("Emits events", async () => { 39 | await assertAllLogs(tx, [ 40 | // Mint tokens 41 | { 42 | contract: contracts.nftDropCollection, 43 | eventName: "Transfer", 44 | args: [ethers.constants.AddressZero, collector.address, tokenId], 45 | }, 46 | { 47 | contract: contracts.nftDropCollection, 48 | eventName: "Transfer", 49 | args: [ethers.constants.AddressZero, collector.address, tokenId + 1], 50 | }, 51 | { 52 | contract: contracts.nftDropCollection, 53 | eventName: "Transfer", 54 | args: [ethers.constants.AddressZero, collector.address, tokenId + 2], 55 | }, 56 | // Record sale 57 | { 58 | contract: contracts.nftDropMarket, 59 | eventName: "MintFromFixedPriceDrop", 60 | args: [ 61 | // Collection 62 | contracts.nftDropCollection.address, 63 | // Buyer 64 | collector.address, 65 | // First tokenId 66 | tokenId, 67 | // Count 68 | count, 69 | // Total fees 5% 70 | price.mul(count).mul(5).div(100), 71 | // Creator rev 95% 72 | price.mul(count).mul(95).div(100), 73 | ], 74 | }, 75 | ]); 76 | }); 77 | }); 78 | }); 79 | -------------------------------------------------------------------------------- /test/NFTDropMarket/initializer.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { deployAll, TestContracts } from "../helpers/deploy"; 3 | import { snapshotEach } from "../helpers/snapshot"; 4 | 5 | describe("NFTDropMarket / initializer", () => { 6 | let contracts: TestContracts; 7 | 8 | snapshotEach(async () => { 9 | contracts = await deployAll(); 10 | }); 11 | 12 | it("Cannot initialize again", async () => { 13 | await expect(contracts.nftDropMarket.initialize()).to.be.revertedWith( 14 | "Initializable: contract is already initialized", 15 | ); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /test/collections/NFTCollection/mint.ts: -------------------------------------------------------------------------------- 1 | import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; 2 | import { expect } from "chai"; 3 | import { ethers } from "hardhat"; 4 | import { deployAll, TestContracts } from "../../helpers/deploy"; 5 | import { snapshotEach } from "../../helpers/snapshot"; 6 | import { testIpfsPath } from "../../helpers/testData"; 7 | 8 | describe("NFTCollection / mint", () => { 9 | let contracts: TestContracts; 10 | let deployer, creator, rando: SignerWithAddress; 11 | 12 | snapshotEach(async () => { 13 | [deployer, creator, rando] = await ethers.getSigners(); 14 | contracts = await deployAll(deployer, creator); 15 | }); 16 | 17 | it("Rando cannot mint from the collection", async () => { 18 | await expect(contracts.collection.connect(rando).mint(testIpfsPath[0])).to.be.revertedWith( 19 | "SequentialMintCollection: Caller is not creator", 20 | ); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /test/collections/NFTCollection/paymentFactory/DrainTokens.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "hardhat"; 2 | import { NFTCollectionFactory, NFTCollection, NFTCollection__factory, WETH9 } from "../../../../src/typechain"; 3 | import { expect } from "chai"; 4 | import { deployAll, deployWETH9 } from "../../../helpers/deploy"; 5 | import { testIpfsPath } from "../../../helpers/testData"; 6 | import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/dist/src/signer-with-address"; 7 | import { snapshotEach } from "../../../helpers/snapshot"; 8 | 9 | /** 10 | * Confirms that a specific attack to drain tokens stored by the NFT contract is not possible. 11 | */ 12 | describe("NFTCollection / paymentFactory / drainTokens", () => { 13 | let deployer: SignerWithAddress; 14 | let creator: SignerWithAddress; 15 | let rando: SignerWithAddress; 16 | let nft: NFTCollection; 17 | let weth9: WETH9; 18 | let nftCollectionFactoryV2: NFTCollectionFactory; 19 | const nftWethBalanceBefore = ethers.utils.parseEther("1"); 20 | 21 | snapshotEach(async () => { 22 | [deployer, creator, rando] = await ethers.getSigners(); 23 | ({ nftCollectionFactoryV2 } = await deployAll(deployer, creator)); 24 | weth9 = await deployWETH9(deployer); 25 | const NAME = "NAME"; 26 | const SYMBOL = "SYM"; 27 | const NONCE = 0; 28 | await nftCollectionFactoryV2.connect(creator).createNFTCollection(NAME, SYMBOL, NONCE); 29 | const nftAddress = await nftCollectionFactoryV2.predictNFTCollectionAddress(creator.address, NONCE); 30 | nft = NFTCollection__factory.connect(nftAddress, creator); 31 | await weth9.connect(rando).deposit({ value: nftWethBalanceBefore.mul(2) }); 32 | await weth9.connect(rando).transfer(nft.address, nftWethBalanceBefore); 33 | }); 34 | 35 | it("WETH balance before", async () => { 36 | const balance7 = await weth9.balanceOf(nft.address); 37 | expect(balance7).to.eq(nftWethBalanceBefore); 38 | }); 39 | 40 | it("Funds cannot be drained with the factory", async () => { 41 | // Attempt to transfer 1 wei 42 | const callData = weth9.interface.encodeFunctionData("transfer", [rando.address, 1]); 43 | await expect(nft.mintWithCreatorPaymentFactory(testIpfsPath[0], weth9.address, callData)).to.be.revertedWith( 44 | "InternalProxyCall: did not return a contract", 45 | ); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /test/collections/NFTCollection/royaltyInfo.ts: -------------------------------------------------------------------------------- 1 | import { snapshotEach } from "../../helpers/snapshot"; 2 | import { ethers } from "hardhat"; 3 | import { FoundationTreasury, NFTCollection, NFTCollectionFactory } from "../../../src/typechain"; 4 | import { expect } from "chai"; 5 | import { deployAll, deployCollectionImplementationsAndFactory } from "../../helpers/deploy"; 6 | import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; 7 | import { ContractTransaction } from "ethers"; 8 | import { getNFTCollection } from "../../helpers/collectionContract"; 9 | import { shouldSupportInterfaces } from "../../../src/testBehaviors"; 10 | 11 | describe("NFTCollection / royaltyInfo", () => { 12 | let deployer: SignerWithAddress; 13 | let creator: SignerWithAddress; 14 | let nft: NFTCollection; 15 | let tx: ContractTransaction; 16 | let nftCollectionFactoryV2: NFTCollectionFactory; 17 | let treasury: FoundationTreasury; 18 | const NONCE = 42; 19 | 20 | snapshotEach(async function () { 21 | [deployer, creator] = await ethers.getSigners(); 22 | ({ treasury } = await deployAll(deployer, creator)); 23 | ({ nftCollectionFactoryV2 } = await deployCollectionImplementationsAndFactory(deployer, treasury)); 24 | tx = await nftCollectionFactoryV2.connect(creator).createNFTCollection("NAME", "SYMBOL", NONCE); 25 | nft = await getNFTCollection(tx, creator); 26 | this.nft = nft; 27 | }); 28 | 29 | shouldSupportInterfaces(["ERC2981"]); 30 | 31 | it("Sold for nothing.", async () => { 32 | const fees = await nft.royaltyInfo(1, 0); 33 | expect(fees.receiver).to.eq(creator.address); 34 | expect(fees.royaltyAmount).to.eq(0); 35 | }); 36 | 37 | it("Sold for small number.", async () => { 38 | const fees = await nft.royaltyInfo(1, 1); 39 | expect(fees.receiver).to.eq(creator.address); 40 | expect(fees.royaltyAmount).to.eq(0); 41 | }); 42 | 43 | it("Sold for 999.", async () => { 44 | const fees = await nft.royaltyInfo(1, 999); 45 | expect(fees.receiver).to.eq(creator.address); 46 | expect(fees.royaltyAmount).to.eq(99); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /test/collections/NFTCollectionFactory/collectionFactory.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { deployAll, TestContracts } from "../../helpers/deploy"; 3 | import { snapshotEach } from "../../helpers/snapshot"; 4 | 5 | describe("NFTcollectionFactory / collectionFactory", () => { 6 | let contracts: TestContracts; 7 | 8 | snapshotEach(async () => { 9 | contracts = await deployAll(); 10 | }); 11 | 12 | it("NFTCollection exposes factory address", async () => { 13 | expect(await contracts.collection.contractFactory()).to.eq(contracts.nftCollectionFactoryV2.address); 14 | }); 15 | 16 | it("NFTDropCollection exposes factory", async () => { 17 | expect(await contracts.nftDropCollection.contractFactory()).to.eq(contracts.nftCollectionFactoryV2.address); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /test/collections/NFTCollectionFactory/initializer.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { deployAll, TestContracts } from "../../helpers/deploy"; 3 | import { snapshotEach } from "../../helpers/snapshot"; 4 | 5 | describe("NFTCollectionFactory / initializer", () => { 6 | let contracts: TestContracts; 7 | 8 | snapshotEach(async () => { 9 | contracts = await deployAll(); 10 | }); 11 | 12 | it("Cannot initialize again", async () => { 13 | await expect(contracts.nftCollectionFactoryV2.initialize(100)).to.be.revertedWith( 14 | "Initializable: contract is already initialized", 15 | ); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /test/collections/NFTCollectionFactory/predictNFTDropCollectionAddress.ts: -------------------------------------------------------------------------------- 1 | import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; 2 | import { expect } from "chai"; 3 | import { ContractTransaction } from "ethers"; 4 | import { ethers } from "hardhat"; 5 | import { getArgsCreateNFTDropCollection } from "../../fixtures/nftCollectionFactory"; 6 | import { getNFTDropCollection } from "../../helpers/collectionContract"; 7 | import { deployAll, TestContracts } from "../../helpers/deploy"; 8 | import { snapshotEach } from "../../helpers/snapshot"; 9 | 10 | describe("NFTDropCollection / predictNFTDropCollectionAddress", () => { 11 | let deployer: SignerWithAddress; 12 | let creator: SignerWithAddress; 13 | let creator2: SignerWithAddress; 14 | let contracts: TestContracts; 15 | let tx: ContractTransaction; 16 | 17 | snapshotEach(async () => { 18 | [deployer, creator, creator2] = await ethers.getSigners(); 19 | contracts = await deployAll(deployer, creator); 20 | }); 21 | 22 | describe("Create with creator / nonce 0", () => { 23 | const nonce = 0; 24 | 25 | beforeEach(async () => { 26 | tx = await contracts.nftCollectionFactoryV2 27 | .connect(creator) 28 | .createNFTDropCollection(...getArgsCreateNFTDropCollection(contracts, { nonce })); 29 | }); 30 | 31 | it("Address matches predicted", async () => { 32 | const predicted = await contracts.nftCollectionFactoryV2.predictNFTDropCollectionAddress(creator.address, nonce); 33 | const actual = await getNFTDropCollection(tx); 34 | expect(predicted).to.eq(actual.address); 35 | }); 36 | }); 37 | 38 | describe("Create with creator / nonce 42", () => { 39 | const nonce = 42; 40 | 41 | beforeEach(async () => { 42 | tx = await contracts.nftCollectionFactoryV2 43 | .connect(creator) 44 | .createNFTDropCollection(...getArgsCreateNFTDropCollection(contracts, { nonce })); 45 | }); 46 | 47 | it("Address matches predicted", async () => { 48 | const predicted = await contracts.nftCollectionFactoryV2.predictNFTDropCollectionAddress(creator.address, nonce); 49 | const actual = await getNFTDropCollection(tx); 50 | expect(predicted).to.eq(actual.address); 51 | }); 52 | }); 53 | 54 | describe("Create with creator2 / nonce max", () => { 55 | const nonce = ethers.constants.MaxUint256; 56 | 57 | beforeEach(async () => { 58 | tx = await contracts.nftCollectionFactoryV2 59 | .connect(creator2) 60 | .createNFTDropCollection(...getArgsCreateNFTDropCollection(contracts, { nonce })); 61 | }); 62 | 63 | it("Address matches predicted", async () => { 64 | const predicted = await contracts.nftCollectionFactoryV2.predictNFTDropCollectionAddress(creator2.address, nonce); 65 | const actual = await getNFTDropCollection(tx); 66 | expect(predicted).to.eq(actual.address); 67 | }); 68 | }); 69 | }); 70 | -------------------------------------------------------------------------------- /test/collections/NFTDropCollection/supportsInterfaces.ts: -------------------------------------------------------------------------------- 1 | import { shouldSupportInterfaces } from "../../../src/testBehaviors"; 2 | import { deployAll } from "../../helpers/deploy"; 3 | import { snapshotEach } from "../../helpers/snapshot"; 4 | 5 | describe("NFTDropCollection / supportsInterfaces", () => { 6 | snapshotEach(async function () { 7 | const contracts = await deployAll(); 8 | this.nft = contracts.nftDropCollection; 9 | }); 10 | 11 | shouldSupportInterfaces(["GetRoyalties", "TokenCreator", "WithSecondarySales", "ERC2981", "NFTDropCollectionMint"]); 12 | }); 13 | -------------------------------------------------------------------------------- /test/collections/mixins/SequentialMintCollectionInitialize.ts: -------------------------------------------------------------------------------- 1 | import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; 2 | import { expect } from "chai"; 3 | import { ethers } from "hardhat"; 4 | import { SequentialMintCollectionMock, SequentialMintCollectionMock__factory } from "../../../src/typechain"; 5 | import { snapshotEach } from "../../helpers/snapshot"; 6 | 7 | describe("Collections / Mixins / SequentialMintCollectionInitialize", () => { 8 | const maxTokenId = 1; 9 | 10 | let mock: SequentialMintCollectionMock; 11 | let deployer: SignerWithAddress; 12 | let creator: SignerWithAddress; 13 | 14 | snapshotEach(async () => { 15 | [deployer, creator] = await ethers.getSigners(); 16 | mock = await new SequentialMintCollectionMock__factory(deployer).deploy(); 17 | }); 18 | 19 | describe("After init", () => { 20 | beforeEach(async () => { 21 | await mock.initializeWithModifier(creator.address, maxTokenId); 22 | }); 23 | 24 | it("Cannot init again", async () => { 25 | await expect(mock.initializeWithModifier(creator.address, maxTokenId)).to.be.revertedWith( 26 | "Initializable: contract is already initialized", 27 | ); 28 | }); 29 | }); 30 | 31 | it("Cannot init mixin without top-level modifier", async () => { 32 | await expect(mock.initializeWithoutModifier(creator.address, maxTokenId)).to.be.revertedWith( 33 | "Initializable: contract is not initializing", 34 | ); 35 | }); 36 | 37 | it("Cannot init with an empty creator", async () => { 38 | await expect(mock.initializeWithModifier(ethers.constants.AddressZero, maxTokenId)).to.be.revertedWith( 39 | "SequentialMintCollection: Creator cannot be the zero address", 40 | ); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /test/feth/marketInteractions.ts: -------------------------------------------------------------------------------- 1 | import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; 2 | import { expect } from "chai"; 3 | import { ContractTransaction } from "ethers"; 4 | import { ethers } from "hardhat"; 5 | import { FETH, FETHMarketMock, FETHMarketMock__factory } from "../../src/typechain"; 6 | import { deployFETH } from "../helpers/deploy"; 7 | import { getFethExpectedExpiration } from "../helpers/feth"; 8 | import { snapshotEach } from "../helpers/snapshot"; 9 | 10 | describe("FETH / marketInteractions", function () { 11 | let feth: FETH; 12 | let mockMarket: FETHMarketMock; 13 | let deployer: SignerWithAddress; 14 | let user: SignerWithAddress; 15 | let rando: SignerWithAddress; 16 | let tx: ContractTransaction; 17 | const amount = ethers.utils.parseEther("1"); 18 | let expiry: number; 19 | 20 | snapshotEach(async () => { 21 | [deployer, user, rando] = await ethers.getSigners(); 22 | const MarketMock = new FETHMarketMock__factory(deployer); 23 | mockMarket = await MarketMock.deploy(); 24 | feth = await deployFETH({ deployer, marketAddress: mockMarket.address, dropMarketAddress: mockMarket.address }); 25 | await mockMarket.setFeth(feth.address); 26 | }); 27 | 28 | // This demonstrates how you can use the mock to test FETH directly. 29 | // The other calls are available in a similar fashion. 30 | describe("`marketLockupFor`", () => { 31 | beforeEach(async () => { 32 | tx = await mockMarket.connect(user).marketLockupFor(user.address, amount, { value: amount }); 33 | expiry = await getFethExpectedExpiration(tx); 34 | }); 35 | 36 | it("Emits BalanceLocked", async () => { 37 | await expect(tx).to.emit(feth, "BalanceLocked").withArgs(user.address, expiry, amount, amount); 38 | }); 39 | 40 | it("Has no available balance", async () => { 41 | const balance = await feth.balanceOf(user.address); 42 | expect(balance).to.eq(0); 43 | }); 44 | 45 | it("Has total balance", async () => { 46 | const balance = await feth.totalBalanceOf(user.address); 47 | expect(balance).to.eq(amount); 48 | }); 49 | 50 | it("Has lockup", async () => { 51 | const lockups = await feth.getLockups(user.address); 52 | expect(lockups.amounts[0]).to.eq(amount); 53 | expect(lockups.expiries[0]).to.eq(expiry); 54 | }); 55 | 56 | it("Transfers ETH", async () => { 57 | await expect(tx).to.changeEtherBalances([feth, user], [amount, amount.mul(-1)]); 58 | }); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /test/feth/withdraw.ts: -------------------------------------------------------------------------------- 1 | import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; 2 | import { expect } from "chai"; 3 | import { ContractTransaction } from "ethers"; 4 | import { ethers } from "hardhat"; 5 | import { FETH } from "../../src/typechain"; 6 | import { deployAll } from "../helpers/deploy"; 7 | import { snapshotEach } from "../helpers/snapshot"; 8 | 9 | describe("FETH / withdraw", function () { 10 | let feth: FETH; 11 | let deployer: SignerWithAddress; 12 | let user: SignerWithAddress; 13 | let operator: SignerWithAddress; 14 | let tx: ContractTransaction; 15 | const balance = ethers.utils.parseEther("1"); 16 | 17 | snapshotEach(async () => { 18 | [deployer, user, operator] = await ethers.getSigners(); 19 | ({ feth } = await deployAll(deployer)); 20 | tx = await feth.connect(user).deposit({ value: balance }); 21 | }); 22 | 23 | describe("`withdrawAvailableBalance`", () => { 24 | beforeEach(async () => { 25 | tx = await feth.connect(user).withdrawAvailableBalance(); 26 | }); 27 | 28 | it("Emits ETHWithdrawn", async () => { 29 | await expect(tx).to.emit(feth, "ETHWithdrawn").withArgs(user.address, user.address, balance); 30 | }); 31 | 32 | it("Transfers ETH", async () => { 33 | await expect(tx).to.changeEtherBalances([feth, user], [balance.mul(-1), balance]); 34 | }); 35 | 36 | it("Has no FETH remaining", async () => { 37 | const balanceOf = await feth.balanceOf(user.address); 38 | expect(balanceOf).to.eq(0); 39 | }); 40 | 41 | it("Cannot withdraw again", async () => { 42 | await expect(feth.connect(user).withdrawAvailableBalance()).to.be.revertedWithCustomError( 43 | feth, 44 | "FETH_No_Funds_To_Withdraw", 45 | ); 46 | }); 47 | }); 48 | 49 | describe("`withdrawFrom`", () => { 50 | const amount = ethers.utils.parseEther(".25"); 51 | 52 | beforeEach(async () => { 53 | // The operator requires approval 54 | await feth.connect(user).approve(operator.address, amount); 55 | tx = await feth.connect(operator).withdrawFrom(user.address, operator.address, amount); 56 | }); 57 | 58 | it("Emits ETHWithdrawn", async () => { 59 | await expect(tx).to.emit(feth, "ETHWithdrawn").withArgs(user.address, operator.address, amount); 60 | }); 61 | 62 | it("Transfers ETH", async () => { 63 | await expect(tx).to.changeEtherBalances([feth, operator], [amount.mul(-1), amount]); 64 | }); 65 | 66 | it("Has less FETH available", async () => { 67 | const balanceOf = await feth.balanceOf(user.address); 68 | expect(balanceOf).to.eq(balance.sub(amount)); 69 | }); 70 | }); 71 | }); 72 | -------------------------------------------------------------------------------- /test/foundry/BytesLibrary.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.12; 4 | 5 | import "../../contracts/libraries/BytesLibrary.sol"; 6 | 7 | contract TestBytesLibrary { 8 | using BytesLibrary for bytes; 9 | 10 | function testStartsWithPrefix() public pure { 11 | bytes memory value = "test1234"; 12 | 13 | assert(value.startsWith("test")); 14 | 15 | assert(!value.startsWith(" tes")); 16 | assert(!value.startsWith("1234")); 17 | 18 | // Checks if starts with bytes4 19 | assert(!value.startsWith("")); 20 | assert(!value.startsWith("t")); 21 | } 22 | 23 | function testStartsWithFullString() public pure { 24 | bytes memory value = "test"; 25 | 26 | assert(value.startsWith("test")); 27 | 28 | assert(!value.startsWith(" tes")); 29 | assert(!value.startsWith("1234")); 30 | 31 | // Checks if starts with bytes4 32 | assert(!value.startsWith("")); 33 | assert(!value.startsWith("t")); 34 | } 35 | 36 | function testStartsWithTooShort() public pure { 37 | bytes memory value = "tes"; 38 | 39 | assert(!value.startsWith("tes")); 40 | assert(!value.startsWith(" tes")); 41 | assert(!value.startsWith("")); 42 | assert(!value.startsWith("t")); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /test/foundry/FixedPriceDrop.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.12; 4 | 5 | import "forge-std/Test.sol"; 6 | 7 | import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; 8 | 9 | import "../../contracts/NFTDropMarket.sol"; 10 | import "../../contracts/FETH.sol"; 11 | import "../../contracts/NFTDropCollection.sol"; 12 | import "../../contracts/NFTCollectionFactory.sol"; 13 | import "../../contracts/mocks/MockTreasury.sol"; 14 | import "../../contracts/mocks/RoyaltyRegistry/MockRoyaltyRegistry.sol"; 15 | 16 | contract TestFixedPriceDrop is Test { 17 | address admin = address(99); 18 | address creator = address(1); 19 | address collector = address(2); 20 | 21 | MockTreasury treasury; 22 | NFTDropMarket nftDropMarket; 23 | FETH feth; 24 | MockRoyaltyRegistry royaltyRegistry; 25 | NFTCollectionFactory nftCollectionFactory; 26 | 27 | function setUp() public { 28 | /** Pre-reqs **/ 29 | treasury = new MockTreasury(); 30 | 31 | /** Deploy Collection Factory **/ 32 | nftCollectionFactory = new NFTCollectionFactory(address(treasury)); 33 | nftCollectionFactory.initialize(2); 34 | NFTDropCollection nftDropCollectionTemplate = new NFTDropCollection(address(nftCollectionFactory)); 35 | nftCollectionFactory.adminUpdateNFTDropCollectionImplementation(address(nftDropCollectionTemplate)); 36 | 37 | /** Deploy Market **/ 38 | // Deploy the proxy with a placeholder implementation. 39 | TransparentUpgradeableProxy dropMarketProxy = new TransparentUpgradeableProxy(address(treasury), admin, ""); 40 | feth = new FETH(payable(dropMarketProxy), payable(dropMarketProxy), 24 hours); 41 | royaltyRegistry = new MockRoyaltyRegistry(); 42 | 43 | NFTDropMarket dropMarketImplementation = new NFTDropMarket( 44 | payable(treasury), 45 | address(feth), 46 | address(royaltyRegistry) 47 | ); 48 | vm.prank(admin); 49 | dropMarketProxy.upgradeTo(address(dropMarketImplementation)); 50 | nftDropMarket = NFTDropMarket(payable(dropMarketProxy)); 51 | nftDropMarket.initialize(); 52 | } 53 | 54 | function testHappyCase() public { 55 | /** Create drop collection **/ 56 | uint256 nonce = 42; 57 | uint32 maxTokenId = 100; 58 | vm.prank(creator); 59 | nftCollectionFactory.createNFTDropCollection( 60 | "Name", 61 | "SYM", 62 | "ipfs://sample", 63 | 0x0, 64 | maxTokenId, 65 | address(nftDropMarket), 66 | nonce 67 | ); 68 | NFTDropCollection nftDropCollection = NFTDropCollection( 69 | nftCollectionFactory.predictNFTDropCollectionAddress(creator, nonce) 70 | ); 71 | 72 | /** List for sale **/ 73 | uint80 price = 0.5 ether; 74 | uint16 limitPerAccount = 10; 75 | vm.prank(creator); 76 | nftDropMarket.createFixedPriceSale(address(nftDropCollection), price, limitPerAccount); 77 | 78 | /** Mint from sale **/ 79 | uint16 count = 3; 80 | vm.deal(collector, 999 ether); 81 | vm.prank(collector); 82 | nftDropMarket.mintFromFixedPriceSale{ value: price * count }(address(nftDropCollection), count, payable(0)); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /test/helpers/collectionContract.ts: -------------------------------------------------------------------------------- 1 | import { Provider } from "@ethersproject/abstract-provider"; 2 | import { ContractTransaction, Signer } from "ethers"; 3 | import { ethers } from "hardhat"; 4 | import { 5 | NFTCollection, 6 | NFTCollection__factory, 7 | NFTCollectionFactory__factory, 8 | NFTDropCollection, 9 | NFTDropCollection__factory, 10 | } from "../../src/typechain"; 11 | import { NFTDropCollectionCreatedEvent } from "../../src/typechain/NFTCollectionFactory"; 12 | 13 | export async function getNFTCollection( 14 | tx: ContractTransaction, 15 | signerOrProvider?: Signer | Provider, 16 | ): Promise { 17 | const receipt = await tx.wait(); 18 | const factoryInterface = NFTCollectionFactory__factory.createInterface(); 19 | const log = receipt.logs.find( 20 | log => 21 | log.topics[0] == 22 | factoryInterface.getEventTopic( 23 | factoryInterface.events["NFTCollectionCreated(address,address,uint256,string,string,uint256)"], 24 | ), 25 | ); 26 | if (!log) { 27 | throw new Error("No CollectionCreated event found"); 28 | } 29 | const event = factoryInterface.parseLog(log); 30 | const address = event.args.collection; 31 | return NFTCollection__factory.connect(address, signerOrProvider ?? ethers.provider); 32 | } 33 | 34 | export async function getNFTDropCollection( 35 | tx: ContractTransaction, 36 | signerOrProvider?: Signer | Provider, 37 | ): Promise { 38 | const receipt = await tx.wait(); 39 | const event = receipt.events?.find(e => e.event === "NFTDropCollectionCreated") as NFTDropCollectionCreatedEvent; 40 | const address = event.args.collection; 41 | return NFTDropCollection__factory.connect(address, signerOrProvider ?? ethers.provider); 42 | } 43 | -------------------------------------------------------------------------------- /test/helpers/constants.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber, ethers } from "ethers"; 2 | 3 | export const BASIS_POINTS = BigNumber.from(10000); 4 | export const ONE_HOUR = 60 * 60; 5 | export const ONE_DAY = 24 * ONE_HOUR; 6 | export const ONE_ETH = ethers.utils.parseEther("1"); 7 | -------------------------------------------------------------------------------- /test/helpers/feth.ts: -------------------------------------------------------------------------------- 1 | import { ContractTransaction } from "ethers"; 2 | import { ONE_DAY, ONE_HOUR } from "./constants"; 3 | import { getBlockTime } from "./time"; 4 | 5 | export async function getFethExpectedExpiration(tx: ContractTransaction): Promise { 6 | const receipt = await tx.wait(); 7 | const timestamp = await getBlockTime(receipt.blockNumber); 8 | return getFethExpirationFromSeconds(timestamp); 9 | } 10 | 11 | export function getFethExpirationFromSeconds(timestampInSeconds: number): number { 12 | return Math.ceil(timestampInSeconds / ONE_HOUR) * ONE_HOUR + ONE_DAY; 13 | } 14 | -------------------------------------------------------------------------------- /test/helpers/logs.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { Contract, ContractTransaction } from "ethers"; 3 | 4 | export type EventLog = { 5 | contract: Contract; 6 | eventName: string; 7 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 8 | args: any[]; 9 | }; 10 | 11 | /** 12 | * Checks the tx for the exact logs specified including order, count, and args. 13 | */ 14 | export async function assertAllLogs( 15 | tx: ContractTransaction | Promise, 16 | logs: EventLog[], 17 | ): Promise { 18 | if (tx instanceof Promise) { 19 | tx = await tx; 20 | } 21 | const receipt = await tx.wait(); 22 | expect(receipt.logs.length).to.eq( 23 | logs.length, 24 | `Log count mismatch. Expected ${logs.length} logs, got ${receipt.logs.length}: ${JSON.stringify(receipt.logs)}`, 25 | ); 26 | for (let i = 0; i < logs.length; i++) { 27 | const emittedLog = receipt.logs[i]; 28 | const expectedLog = logs[i]; 29 | expect(emittedLog.address).to.eq( 30 | expectedLog.contract.address, 31 | `Log address mismatch at index ${i}; expected ${expectedLog.contract.address}, got ${emittedLog.address}`, 32 | ); 33 | // Throws if the event is not found 34 | const fragment = expectedLog.contract.interface.getEvent(expectedLog.eventName); 35 | const topic0 = expectedLog.contract.interface.getEventTopic(fragment); 36 | // Confirms order by event name 37 | expect(emittedLog.topics[0]).to.eq( 38 | topic0, 39 | `Topic 0 mismatch at index ${i}. Expected ${expectedLog.eventName} (${topic0}), got ${emittedLog.topics[0]}`, 40 | ); 41 | 42 | // For simplicity, check for args with the chai matcher 43 | // -- this means we do not validate the order args emit when the same event name is emitted multiple times 44 | await expect(tx) 45 | .to.emit(expectedLog.contract, expectedLog.eventName) 46 | .withArgs(...expectedLog.args); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /test/helpers/mint.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber, ContractTransaction } from "ethers"; 2 | 3 | export async function getMintedTokenId(transaction: ContractTransaction): Promise { 4 | const receipt = await transaction.wait(); 5 | const log = receipt.events?.find(e => e.event === "Minted"); 6 | if (!log?.args) { 7 | throw new Error("No `Minted` event detected"); 8 | } 9 | return log.args[1]; 10 | } 11 | -------------------------------------------------------------------------------- /test/helpers/snapshot.ts: -------------------------------------------------------------------------------- 1 | import { providers } from "ethers"; 2 | import { ethers } from "hardhat"; 3 | import { Context } from "mocha"; 4 | 5 | export function snapshotEach(funcBeforeSnapshot: (this: Context) => Promise): void { 6 | const provider: providers.JsonRpcProvider = ethers.provider; 7 | let snapshotId: string; 8 | 9 | before(async function () { 10 | await funcBeforeSnapshot.call(this); 11 | snapshotId = await provider.send("evm_snapshot", []); 12 | }); 13 | 14 | beforeEach(async function () { 15 | await provider.send("evm_revert", [snapshotId]); 16 | snapshotId = await provider.send("evm_snapshot", []); 17 | }); 18 | 19 | after(async function () { 20 | // Clean up state when tests finish 21 | await provider.send("evm_revert", [snapshotId]); 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /test/helpers/splits.ts: -------------------------------------------------------------------------------- 1 | import { BigNumberish, ContractTransaction, Signer } from "ethers"; 2 | import { PercentSplitETH, PercentSplitETH__factory } from "../../src/typechain"; 3 | import { Provider } from "@ethersproject/providers"; 4 | import { ethers } from "hardhat"; 5 | 6 | export type Royalty = { recipient: string; percentInBasisPoints: BigNumberish }; 7 | 8 | export async function getSplitInstance( 9 | transaction: ContractTransaction, 10 | signerOrProvider: Signer | Provider, 11 | royalties?: Royalty[], 12 | ): Promise { 13 | const receipt = await transaction.wait(); 14 | const log = receipt.events?.find(e => e.event === "PercentSplitCreated"); 15 | let splitAddress = log?.args?.contractAddress; 16 | if (!splitAddress) { 17 | if (transaction.to && royalties) { 18 | const splitFactory = PercentSplitETH__factory.connect(transaction.to, signerOrProvider); 19 | splitAddress = await splitFactory.getPredictedSplitAddress(royalties); 20 | if (splitAddress) { 21 | return PercentSplitETH__factory.connect(splitAddress, signerOrProvider); 22 | } 23 | } 24 | throw new Error("Split address not found"); 25 | } 26 | return PercentSplitETH__factory.connect(splitAddress, signerOrProvider); 27 | } 28 | 29 | export async function getExpectedPercentSplit( 30 | contracts: { percentSplitFactory: PercentSplitETH }, 31 | shares: Royalty[], 32 | signerOrProvider?: Signer | Provider, 33 | ): Promise { 34 | const expectedAddress = await contracts.percentSplitFactory.getPredictedSplitAddress(shares); 35 | return PercentSplitETH__factory.connect(expectedAddress, signerOrProvider ?? ethers.provider); 36 | } 37 | 38 | export async function getSplitShares(address: string): Promise { 39 | const split = PercentSplitETH__factory.connect(address, ethers.provider); 40 | try { 41 | const shares = await split.getShares(); 42 | if (shares.length === 0) { 43 | return undefined; 44 | } 45 | return shares.map(s => ({ recipient: s.recipient, percentInBasisPoints: s.percentInBasisPoints })); 46 | } catch { 47 | // Not a valid split contract 48 | return undefined; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /test/helpers/time.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber, providers } from "ethers"; 2 | import { ethers } from "hardhat"; 3 | import { ONE_HOUR } from "./constants"; 4 | 5 | // As of 0.5.9 the OZ test helper for time does not work with Hardhat 6 | export async function increaseTime(seconds: number): Promise { 7 | const provider: providers.JsonRpcProvider = ethers.provider; 8 | await provider.send("evm_increaseTime", [seconds]); 9 | await advanceBlock(); 10 | } 11 | 12 | export async function increaseTimeTo(timestamp: number | BigNumber, shouldAdvanceBlock = true): Promise { 13 | timestamp = timestamp instanceof BigNumber ? timestamp.toNumber() : timestamp; 14 | const provider: providers.JsonRpcProvider = ethers.provider; 15 | await provider.send("evm_setNextBlockTimestamp", [timestamp]); 16 | if (shouldAdvanceBlock) { 17 | await advanceBlock(); 18 | } 19 | } 20 | 21 | export async function increaseTimeToNextHour(): Promise { 22 | const time = await getBlockTime(); 23 | const timeToMoveTo = Math.ceil(time / ONE_HOUR) * ONE_HOUR; 24 | await increaseTimeTo(timeToMoveTo); 25 | } 26 | 27 | export async function getBlockTime(block: string | number = "latest"): Promise { 28 | const provider: providers.JsonRpcProvider = ethers.provider; 29 | return (await provider.getBlock(block)).timestamp; 30 | } 31 | 32 | export async function advanceBlock() { 33 | const provider: providers.JsonRpcProvider = ethers.provider; 34 | await provider.send("evm_mine", []); 35 | } 36 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "esModuleInterop": true, 4 | "forceConsistentCasingInFileNames": true, 5 | "lib": ["es5", "es6"], 6 | "jsx": "react", 7 | "module": "commonjs", 8 | "moduleResolution": "node", 9 | "noImplicitAny": true, 10 | "outDir": "dist", 11 | "resolveJsonModule": true, 12 | "sourceMap": true, 13 | "strict": true, 14 | "target": "es5" 15 | }, 16 | "exclude": ["artifacts/**/*", "node_modules"], 17 | "include": ["hardhat*.config.ts", "test/**/*", "src/**/*", "test-gas-stories/**/*"] 18 | } 19 | --------------------------------------------------------------------------------