├── .gas-snapshot
├── .github
└── workflows
│ ├── benchmark.yml
│ ├── formatter.yml
│ └── test.yml
├── .gitignore
├── .gitmodules
├── README.md
├── assets
├── core-extension-flow-example.png
└── readme-hero-image.png
├── coverage.md
├── design-document.md
├── foundry.toml
├── package.json
├── remappings.txt
├── script
├── benchmark-ext
│ └── erc721
│ │ ├── BenchmarkERC721.s.sol
│ │ ├── BenchmarkERC721Base.sol
│ │ ├── BenchmarkERC721Manifold.sol
│ │ └── BenchmarkERC721ThirdwebLegacy.sol
└── superchain
│ ├── DeployTWCloneFactory.s.sol
│ ├── IL2ToL2CrossDomainMessenger.sol
│ ├── README.md
│ ├── SuperChainBridge.sol
│ ├── SuperChainERC20Setup.s.sol
│ └── SuperChainInterop.s.sol
├── src
├── Core.sol
├── Module.sol
├── Role.sol
├── TWCloneFactory.sol
├── callback
│ ├── AfterWithdrawCallback.sol
│ ├── BeforeApproveCallbackERC20.sol
│ ├── BeforeApproveCallbackERC721.sol
│ ├── BeforeApproveForAllCallback.sol
│ ├── BeforeBatchMintCallbackERC1155.sol
│ ├── BeforeBatchTransferCallbackERC1155.sol
│ ├── BeforeBurnCallbackERC1155.sol
│ ├── BeforeBurnCallbackERC20.sol
│ ├── BeforeBurnCallbackERC721.sol
│ ├── BeforeDistributeCallback.sol
│ ├── BeforeMintCallbackERC1155.sol
│ ├── BeforeMintCallbackERC20.sol
│ ├── BeforeMintCallbackERC721.sol
│ ├── BeforeMintWithSignatureCallbackERC1155.sol
│ ├── BeforeMintWithSignatureCallbackERC20.sol
│ ├── BeforeMintWithSignatureCallbackERC721.sol
│ ├── BeforeTransferCallbackERC1155.sol
│ ├── BeforeTransferCallbackERC20.sol
│ ├── BeforeTransferCallbackERC721.sol
│ ├── OnTokenURICallback.sol
│ ├── UpdateMetadataCallbackERC1155.sol
│ ├── UpdateMetadataCallbackERC721.sol
│ └── UpdateTokenIdERC1155.sol
├── core
│ ├── MintFeeManagerCore.sol
│ ├── SplitFeesCore.sol
│ ├── SplitWallet.sol
│ └── token
│ │ ├── ERC1155Base.sol
│ │ ├── ERC1155Core.sol
│ │ ├── ERC1155CoreInitializable.sol
│ │ ├── ERC20Base.sol
│ │ ├── ERC20Core.sol
│ │ ├── ERC20CoreInitializable.sol
│ │ ├── ERC721Base.sol
│ │ ├── ERC721Core.sol
│ │ └── ERC721CoreInitializable.sol
├── interface
│ ├── ICore.sol
│ ├── IERC165.sol
│ ├── IERC20.sol
│ ├── IERC20Metadata.sol
│ ├── IERC7572.sol
│ ├── IInstallationCallback.sol
│ ├── IMintFeeManager.sol
│ ├── IModule.sol
│ ├── IModuleConfig.sol
│ └── ISplitWallet.sol
├── libraries
│ ├── Cast.sol
│ ├── ShortString.sol
│ └── Split.sol
└── module
│ ├── MintFeeManagerModule.sol
│ ├── SplitFeesModule.sol
│ └── token
│ ├── crosschain
│ ├── CrossChain.sol
│ └── SuperChainInterop.sol
│ ├── metadata
│ ├── BatchMetadataERC1155.sol
│ ├── BatchMetadataERC721.sol
│ ├── OpenEditionMetadataERC1155.sol
│ └── OpenEditionMetadataERC721.sol
│ ├── minting
│ ├── ClaimableERC1155.sol
│ ├── ClaimableERC20.sol
│ ├── ClaimableERC721.sol
│ ├── MintableERC1155.sol
│ ├── MintableERC20.sol
│ └── MintableERC721.sol
│ ├── royalty
│ ├── RoyaltyERC1155.sol
│ └── RoyaltyERC721.sol
│ ├── tokenId
│ └── SequentialTokenIdERC1155.sol
│ └── transferable
│ ├── CreatorTokenERC20.sol
│ ├── TransferableERC1155.sol
│ ├── TransferableERC20.sol
│ └── TransferableERC721.sol
└── test
├── Interface.t.sol
├── ModularCore.t.sol
├── TWCloneFactory.t.sol
├── benchmark
└── CoreBenchmark.t.sol
├── core
├── ERC1155Core.t.sol
├── ERC20Core.t.sol
└── ERC721Core.t.sol
├── mock
└── MockMintFeeManager.sol
├── module
├── MintFeeManager.t.sol
├── SplitFeeModule.t.sol
├── SplitWallet.t.sol
├── crosschain
│ └── SuperChainInterop.t.sol
├── metadata
│ ├── BatchMetadataERC1155.t.sol
│ ├── BatchMetadataERC721.t.sol
│ ├── OpenEditionMetadataERC1155.t.sol
│ └── OpenEditionMetadataERC721.t.sol
├── minting
│ ├── ClaimableERC1155.t.sol
│ ├── ClaimableERC20.t.sol
│ ├── ClaimableERC721.t.sol
│ ├── MintableERC1155.t.sol
│ ├── MintableERC20.t.sol
│ └── MintableERC721.t.sol
├── royalty
│ ├── RoyaltyERC1155.t.sol
│ ├── RoyaltyERC721.t.sol
│ └── RoyaltyUtils.sol
├── tokenId
│ └── SequentialTokenIdERC1155.t.sol
└── transferable
│ ├── CreatorTokenERC20.t.sol
│ ├── CreatorTokenUtils.sol
│ ├── TransferableERC1155.t.sol
│ ├── TransferableERC20.t.sol
│ └── TransferableERC721.t.sol
└── utils
├── ExtensionProxyFactory.sol
└── TestPlus.sol
/.gas-snapshot:
--------------------------------------------------------------------------------
1 | CoreBenchmark:test_core_callCallbackFunction_required() (gas: 67409)
2 | CoreBenchmark:test_core_callFunction_callback_callbackFunctionRequired() (gas: 65131)
3 | CoreBenchmark:test_core_callFunction_notPermissionedDelegate() (gas: 39115)
4 | CoreBenchmark:test_core_callFunction_notPermissionedExternal() (gas: 39093)
5 | CoreBenchmark:test_deployCore() (gas: 2139892)
6 | CoreBenchmark:test_deployModule() (gas: 350173)
7 | CoreBenchmark:test_installModule() (gas: 352153)
8 | CoreBenchmark:test_module_callFunction_notPermissionedDelegate() (gas: 29381)
9 | CoreBenchmark:test_module_callFunction_notPermissionedExternal() (gas: 29379)
10 | CoreBenchmark:test_uninstallModule() (gas: 91332)
--------------------------------------------------------------------------------
/.github/workflows/benchmark.yml:
--------------------------------------------------------------------------------
1 | name: benchmark
2 |
3 | # Controls when the workflow will run
4 | on:
5 | # Triggers the workflow on push or pull request events but only for the main branch
6 | push:
7 | branches: [main]
8 | pull_request:
9 | branches: [main]
10 |
11 | # cancel previous runs if new commits are pushed to the branch
12 | concurrency:
13 | group: ${{ github.workflow }}-${{ github.ref }}
14 | cancel-in-progress: true
15 |
16 | env:
17 | FOUNDRY_PROFILE: ci
18 |
19 | jobs:
20 | check:
21 | strategy:
22 | fail-fast: true
23 |
24 | name: Foundry
25 | runs-on: ubuntu-latest
26 | steps:
27 | - uses: actions/checkout@v4
28 | with:
29 | submodules: recursive
30 |
31 | - name: Install Foundry
32 | uses: foundry-rs/foundry-toolchain@v1
33 | with:
34 | version: nightly
35 |
36 | - name: Run Forge build
37 | run: |
38 | forge --version
39 | forge build --sizes
40 | id: build
41 |
42 | - name: Create Forge Gas Report
43 | run: |
44 | forge snapshot --diff --isolate --mp 'test/benchmark/*'
45 | id: benchmark
46 |
--------------------------------------------------------------------------------
/.github/workflows/formatter.yml:
--------------------------------------------------------------------------------
1 | name: formatter
2 |
3 | # Controls when the workflow will run
4 | on:
5 | # Triggers the workflow on push or pull request events but only for the main branch
6 | push:
7 | branches: [main]
8 | pull_request:
9 | branches: [main]
10 |
11 | # cancel previous runs if new commits are pushed to the branch
12 | concurrency:
13 | group: ${{ github.workflow }}-${{ github.ref }}
14 | cancel-in-progress: true
15 |
16 | env:
17 | FOUNDRY_PROFILE: ci
18 |
19 | jobs:
20 | check:
21 | strategy:
22 | fail-fast: true
23 |
24 | name: Foundry
25 | runs-on: ubuntu-latest
26 | steps:
27 | - uses: actions/checkout@v4
28 | with:
29 | submodules: recursive
30 |
31 | - name: Install Foundry
32 | uses: foundry-rs/foundry-toolchain@v1
33 | with:
34 | version: nightly
35 |
36 | - name: Formatter
37 | run: |
38 | forge fmt --check test src script
39 | id: formatter
40 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: test
2 |
3 | # Controls when the workflow will run
4 | on:
5 | # Triggers the workflow on push or pull request events but only for the main branch
6 | push:
7 | branches: [main]
8 | pull_request:
9 | branches: [main]
10 |
11 | # cancel previous runs if new commits are pushed to the branch
12 | concurrency:
13 | group: ${{ github.workflow }}-${{ github.ref }}
14 | cancel-in-progress: true
15 |
16 | env:
17 | FOUNDRY_PROFILE: ci
18 |
19 | jobs:
20 | check:
21 | strategy:
22 | fail-fast: true
23 |
24 | name: Foundry
25 | runs-on: ubuntu-latest
26 | steps:
27 | - uses: actions/checkout@v4
28 | with:
29 | submodules: recursive
30 |
31 | - name: Install Foundry
32 | uses: foundry-rs/foundry-toolchain@v1
33 | with:
34 | version: nightly
35 |
36 | - name: Run Forge build
37 | run: |
38 | forge --version
39 | forge build --sizes
40 | id: build
41 |
42 | - name: Run Forge tests
43 | run: |
44 | forge test -vvv
45 | id: test
46 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiler files
2 | cache/
3 | out/
4 |
5 | # Ignores development broadcast logs
6 | !/broadcast
7 | /broadcast/*/31337/
8 | /broadcast/**/dry-run/
9 |
10 | # Docs
11 | docs/
12 |
13 | # Dotenv file
14 | .env
15 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "lib/forge-std"]
2 | path = lib/forge-std
3 | url = https://github.com/foundry-rs/forge-std
4 | [submodule "lib/solady"]
5 | path = lib/solady
6 | url = https://github.com/vectorized/solady
7 | [submodule "lib/erc721a"]
8 | path = lib/erc721a
9 | url = https://github.com/chiru-labs/erc721a
10 | [submodule "lib/ERC721A-Upgradeable"]
11 | path = lib/ERC721A-Upgradeable
12 | url = https://github.com/chiru-labs/ERC721A-Upgradeable
13 | [submodule "lib/creator-token-contracts"]
14 | path = lib/creator-token-contracts
15 | url = https://github.com/limitbreakinc/creator-token-contracts
16 | [submodule "lib/creator-token-standards"]
17 | path = lib/creator-token-standards
18 | url = https://github.com/limitbreakinc/creator-token-standards
19 | [submodule "lib/PermitC"]
20 | path = lib/PermitC
21 | url = https://github.com/limitbreakinc/PermitC
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Modular Contracts
7 | Write smart contracts for which you can add, remove, upgrade or switch out the exact parts you want.
8 |
9 |
10 | A Modular Contract is built of two kinds of parts: a _Modular Core_ and its _Modular Modules_.
11 |
12 | 
13 |
14 | A developer writes a **_Core_** smart contract as the foundation that can be customized by adding new parts and updating or removing these parts over time. These ‘parts’ are **_Module_** smart contracts which any third-party developer can independently develop with reference to the **_Core_** smart contract as the known foundation to build around.
15 |
16 | # Install and Use
17 |
18 | This project can currently be installed as a dependency in [foundry](https://book.getfoundry.sh/) projects. To install, run:
19 |
20 | ```bash
21 | forge install https://github.com/thirdweb-dev/modular-contracts
22 | ```
23 |
24 | Add the following in a `remappings.txt` file:
25 |
26 | ```
27 | @modular-contracts/=lib/modular-contracts/src/
28 | ```
29 |
30 | Import `Core` inherit to build a Modular Core contract (e.g. ERC-721 Core):
31 |
32 | ```solidity
33 | import {Core} from "@modular-contracts/Core.sol";
34 | import {ERC721A} from "@erc721a/extensions/ERC721AQueryable.sol";
35 |
36 | contract ModularNFTCollection is ERC721A, Core {}
37 | ```
38 |
39 | Import `Module` to create an Module for your Core contract (e.g. `Soulbound`):
40 |
41 | ```solidity
42 | import {Module} from "@modular-contracts/Module.sol";
43 |
44 | contract SoulboundERC721 is Module {}
45 | ```
46 |
47 | # Run this repo
48 |
49 | Clone the repo:
50 |
51 | ```bash
52 | git clone https://github.com/thirdweb-dev/modular-contracts.git
53 | ```
54 |
55 | Install dependencies:
56 |
57 | ```bash
58 | # Install dependecies
59 | forge install
60 | ```
61 |
62 |
77 |
78 | # Feedback
79 |
80 | If you have any feedback, please create an issue or reach out to us at support@thirdweb.com.
81 |
82 | # Authors
83 |
84 | - [thirdweb](https://thirdweb.com)
85 |
86 | # License
87 |
88 | [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt)
89 |
--------------------------------------------------------------------------------
/assets/core-extension-flow-example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thirdweb-dev/modular-contracts/201fef1107bee8b89beb78b973103601e072d476/assets/core-extension-flow-example.png
--------------------------------------------------------------------------------
/assets/readme-hero-image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thirdweb-dev/modular-contracts/201fef1107bee8b89beb78b973103601e072d476/assets/readme-hero-image.png
--------------------------------------------------------------------------------
/coverage.md:
--------------------------------------------------------------------------------
1 | Uncovered for src/module/token/royalty/RoyaltyERC1155.sol:
2 | - Function "encodeBytesOnUninstall" (location: source ID 105, line 166, chars 6329-6426, hits: 0)
3 | - Line (location: source ID 105, line 167, chars 6410-6419, hits: 0)
4 | - Statement (location: source ID 105, line 167, chars 6410-6419, hits: 0)
5 | - Branch (branch: 0, path: 1) (location: source ID 105, line 182, chars 6937-7182, hits: 0)
6 | - Branch (branch: 1, path: 1) (location: source ID 105, line 200, chars 7464-7917, hits: 0)
7 | - Line (location: source ID 105, line 211, chars 7871-7874, hits: 0)
8 | - Statement (location: source ID 105, line 211, chars 7871-7874, hits: 0)
9 | - Function "onUninstall" (location: source ID 105, line 229, chars 8450-8503, hits: 0)
10 | - Branch (branch: 2, path: 0) (location: source ID 105, line 286, chars 10597-10670, hits: 0)
11 | - Line (location: source ID 105, line 287, chars 10630-10659, hits: 0)
12 | - Statement (location: source ID 105, line 287, chars 10630-10659, hits: 0)
13 | - Function "getTransferValidationFunction" (location: source ID 105, line 307, chars 11282-11619, hits: 0)
14 | - Line (location: source ID 105, line 312, chars 11431-11581, hits: 0)
15 | - Statement (location: source ID 105, line 312, chars 11431-11581, hits: 0)
16 | - Line (location: source ID 105, line 317, chars 11591-11612, hits: 0)
17 | - Statement (location: source ID 105, line 317, chars 11591-11612, hits: 0)
18 | - Branch (branch: 3, path: 0) (location: source ID 105, line 332, chars 12045-12125, hits: 0)
19 | - Line (location: source ID 105, line 333, chars 12085-12114, hits: 0)
20 | - Statement (location: source ID 105, line 333, chars 12085-12114, hits: 0)
21 | - Statement (location: source ID 105, line 376, chars 13428-13454, hits: 0)
22 |
--------------------------------------------------------------------------------
/foundry.toml:
--------------------------------------------------------------------------------
1 | [profile.default]
2 | solc-version = "0.8.26"
3 | evm_version = "paris"
4 | optimizer = true
5 | optimizer_runs = 10000
6 | cache = true
7 | src = "src"
8 | out = "out"
9 | libs = ["lib"]
10 | ffi = true
11 | gas_reports = [
12 | "ERC20Core",
13 | "ERC721Core",
14 | "ERC1155Core"
15 | ]
16 | ignored_warnings_from = ["node_modules", "lib", "test"]
17 | fs_permissions = [{ access = "read", path = "./"}]
18 |
19 | [fmt]
20 | line_length = 120
21 | tab_width = 4
22 | sort_imports = true
23 | override_spacing = false
24 | bracket_spacing = false
25 | contract_new_lines = true
26 | single_line_statement_blocks = "multi"
27 | int_types = "long"
28 | quote_style = "double"
29 | number_underscore = "thousands"
30 | multiline_func_header = "attributes_first"
31 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "contracts-next",
3 | "version": "1.0.0",
4 | "repository": "https://github.com/thirdweb-dev/contracts-next.git",
5 | "author": "Krishang ",
6 | "license": "Apache-2.0",
7 | "devDependencies": {},
8 | "dependencies": {},
9 | "scripts": {}
10 | }
11 |
--------------------------------------------------------------------------------
/remappings.txt:
--------------------------------------------------------------------------------
1 | ds-test/=lib/forge-std/lib/ds-test/src/
2 | forge-std/=lib/forge-std/src/
3 | @solady/=lib/solady/src/
4 | @erc721a/=lib/erc721a/contracts/
5 | @erc721a-upgradeable/=lib/ERC721A-Upgradeable/contracts/
6 | @limitbreak/creator-token-standards/=lib/creator-token-standards/src/
7 | @limitbreak/permit-c/=lib/PermitC/src/
8 |
--------------------------------------------------------------------------------
/script/benchmark-ext/erc721/BenchmarkERC721.s.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | pragma solidity ^0.8.13;
3 |
4 | import {IERC721} from "./BenchmarkERC721Base.sol";
5 |
6 | import {BenchmarkERC721Manifold} from "./BenchmarkERC721Manifold.sol";
7 | import {BenchmarkERC721ThirdwebLegacy} from "./BenchmarkERC721ThirdwebLegacy.sol";
8 | import "forge-std/Script.sol";
9 |
10 | contract BenchmarkERC721 is Script {
11 |
12 | function benchmarkThirdwebLegacy() public {
13 | BenchmarkERC721ThirdwebLegacy benchmark = new BenchmarkERC721ThirdwebLegacy();
14 |
15 | address contractAddress = benchmark.deployContract(
16 | address(benchmark), "BenchmarkERC721", "B721", "ipfs://QmSkieqXz9voc614Q5LAhQY13LjX6bUzqp2dpDdJhARL7X/0"
17 | );
18 | benchmark.mintToken(contractAddress);
19 | IERC721(contractAddress).setApprovalForAll(address(benchmark), true);
20 | benchmark.transferTokenFrom(contractAddress);
21 | }
22 |
23 | function benchmarkManifold() public {
24 | BenchmarkERC721Manifold benchmark = new BenchmarkERC721Manifold();
25 | address contractAddress = benchmark.deployContract(address(0), "BenchmarkERC721", "B721", "");
26 | benchmark.mintToken(contractAddress);
27 | IERC721(contractAddress).setApprovalForAll(address(benchmark), true);
28 | benchmark.transferTokenFrom(contractAddress);
29 | }
30 |
31 | function benchmarkThirdwebNext() public {}
32 |
33 | function benchmarkZora() public {
34 | // premint
35 | // https://sepolia.explorer.zora.energy/tx/0x1daffd513abf5a8ab1b73bbc9c9dbb1fb576c64069a9a5b6afd2f34ccda5caf6
36 | }
37 |
38 | function run() external {
39 | vm.startBroadcast();
40 | benchmarkThirdwebLegacy();
41 | benchmarkManifold();
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/script/benchmark-ext/erc721/BenchmarkERC721Base.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | pragma solidity ^0.8.24;
3 |
4 | interface IERC721 {
5 |
6 | function transferFrom(address from, address to, uint256 tokenId) external;
7 |
8 | function setApprovalForAll(address operator, bool _approved) external;
9 |
10 | }
11 |
12 | abstract contract BenchmarkERC721Base {
13 |
14 | // Step 1: Deploy a contract through factory or create
15 | // Step 2: Setup the collection with token URI
16 | // Step 3: Mint an NFT
17 | // Step 4: Mint 100 NFTs
18 | // Step 5: Transfer 1 Token
19 | function deployContract(
20 | address deployerAddress,
21 | string memory name,
22 | string memory symbol,
23 | string memory contractURI
24 | ) external virtual returns (address contractAddress);
25 |
26 | function setupToken(address contractAddress) external virtual;
27 |
28 | function mintToken(address contractAddress) external virtual;
29 |
30 | function mintBatchTokens(address contractAddress) external virtual;
31 |
32 | function transferTokenFrom(address contractAddress) external virtual;
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/script/benchmark-ext/erc721/BenchmarkERC721Manifold.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | pragma solidity ^0.8.24;
3 |
4 | import "./BenchmarkERC721Base.sol";
5 |
6 | library StorageSlot {
7 |
8 | struct AddressSlot {
9 | address value;
10 | }
11 |
12 | function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
13 | /// @solidity memory-safe-assembly
14 | assembly {
15 | r.slot := slot
16 | }
17 | }
18 |
19 | }
20 |
21 | abstract contract Proxy {
22 |
23 | function _delegate(address implementation) internal virtual {
24 | assembly {
25 | calldatacopy(0, 0, calldatasize())
26 |
27 | let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)
28 |
29 | returndatacopy(0, 0, returndatasize())
30 |
31 | switch result
32 | case 0 { revert(0, returndatasize()) }
33 | default { return(0, returndatasize()) }
34 | }
35 | }
36 |
37 | function _implementation() internal view virtual returns (address);
38 |
39 | function _fallback() internal virtual {
40 | _delegate(_implementation());
41 | }
42 |
43 | fallback() external payable virtual {
44 | _fallback();
45 | }
46 |
47 | }
48 |
49 | contract ERC721Creator is Proxy {
50 |
51 | constructor(string memory name, string memory symbol) {
52 | assert(_IMPLEMENTATION_SLOT == bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1));
53 | StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = 0x07aee92b7C5977F5EC15d20BaC713A21f72F287B;
54 | (bool success,) = 0x07aee92b7C5977F5EC15d20BaC713A21f72F287B.delegatecall(
55 | abi.encodeWithSignature("initialize(string,string)", name, symbol)
56 | );
57 | require(success, "Initialization failed");
58 | }
59 |
60 | bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
61 |
62 | function implementation() public view returns (address) {
63 | return _implementation();
64 | }
65 |
66 | function _implementation() internal view override returns (address) {
67 | return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
68 | }
69 |
70 | }
71 |
72 | interface IManifoldNFT {
73 |
74 | function transferFrom(address from, address to, uint256 tokenId) external;
75 |
76 | function mintBase(address to, string calldata uri) external returns (uint256);
77 |
78 | function mintBaseBatch(address to, uint16 count) external returns (uint256[] memory);
79 |
80 | function mintBaseBatch(address to, string[] calldata uris) external returns (uint256[] memory);
81 |
82 | }
83 |
84 | contract BenchmarkERC721Manifold is BenchmarkERC721Base {
85 |
86 | function deployContract(
87 | address deployerAddress,
88 | string memory name,
89 | string memory symbol,
90 | string memory contractURI
91 | ) external override returns (address contractAddress) {
92 | //Reference:
93 | // https://sepolia.etherscan.io/address/0xfc958641e52563f071534495886a8ac590dcbfa2#code
94 | // https://sepolia.etherscan.io/tx/0xbc029723fbfd3cfef3c47c523bc9a4e7f073d6f0e89de3f5761701adbd4118a8
95 | contractAddress = address(new ERC721Creator(name, symbol));
96 | }
97 |
98 | function setupToken(address contractAddress) external override {}
99 |
100 | function mintToken(address contractAddress) external override {
101 | // ERC721 Mint token
102 | // https://sepolia.etherscan.io/tx/0x05e54d052b160cf46e8133c2cb4fb5ecfec754e791ae8dd7197a02d332531899
103 | // https://sepolia.etherscan.io/tx/0x6815e65fc7376ba80daa8cbe0bd1bd63476bf78639a554702f981b5545ba0732
104 | IManifoldNFT(contractAddress).mintBase(
105 | tx.origin, "https://studio.api.manifoldxyz.dev/asset_uploader/1/asset/3356006855/metadata/full"
106 | );
107 | }
108 |
109 | function mintBatchTokens(address contractAddress) external override {}
110 |
111 | function transferTokenFrom(address contractAddress) external override {
112 | IERC721(contractAddress).transferFrom(tx.origin, address(0xdead), 1);
113 | }
114 |
115 | }
116 |
--------------------------------------------------------------------------------
/script/benchmark-ext/erc721/BenchmarkERC721ThirdwebLegacy.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | pragma solidity ^0.8.24;
3 |
4 | import "./BenchmarkERC721Base.sol";
5 |
6 | interface IBenchmarkERC721 {
7 |
8 | function deployProxyByImplementation(address _implementation, bytes calldata _data, bytes32 _salt)
9 | external
10 | returns (address deployedProxy);
11 |
12 | function initialize(
13 | address _defaultAdmin,
14 | string memory _name,
15 | string memory _symbol,
16 | string memory _contractURI,
17 | address[] memory _trustedForwarders,
18 | address _saleRecipient,
19 | address _royaltyRecipient,
20 | uint128 _royaltyBps,
21 | uint128 _platformFeeBps,
22 | address _platformFeeRecipient
23 | ) external;
24 |
25 | }
26 |
27 | interface IThirdwebNFT {
28 |
29 | function mintTo(address _to, string calldata _uri) external returns (uint256);
30 |
31 | }
32 |
33 | contract BenchmarkERC721ThirdwebLegacy is BenchmarkERC721Base {
34 |
35 | function deployContract(
36 | address deployerAddress,
37 | string memory name,
38 | string memory symbol,
39 | string memory contractURI
40 | ) external override returns (address contractAddress) {
41 | bytes memory encodedInitializer = abi.encodeCall(
42 | IBenchmarkERC721.initialize,
43 | (
44 | deployerAddress,
45 | name,
46 | symbol,
47 | contractURI,
48 | new address[](0),
49 | deployerAddress,
50 | deployerAddress,
51 | 0,
52 | 0,
53 | deployerAddress
54 | )
55 | );
56 |
57 | contractAddress = IBenchmarkERC721(0x76F948E5F13B9A84A81E5681df8682BBf524805E).deployProxyByImplementation(
58 | 0xd534AC695ab818863FdE799afb2335F989C935e0,
59 | encodedInitializer,
60 | bytes32(0x3534313939303300000000000000000000000000000000000000000000000000)
61 | );
62 | }
63 |
64 | function setupToken(address contractAddress) external override {}
65 |
66 | function mintToken(address contractAddress) external override {
67 | IThirdwebNFT(contractAddress).mintTo(tx.origin, "ipfs://QmVKEzCzn2wnakB33f2Zqhdnk5LrQiAKbuTA95bFcmKuUR/0");
68 | }
69 |
70 | function mintBatchTokens(address contractAddress) external override {}
71 |
72 | function transferTokenFrom(address contractAddress) external override {
73 | IERC721(contractAddress).transferFrom(tx.origin, address(0xdead), 0);
74 | }
75 |
76 | }
77 |
--------------------------------------------------------------------------------
/script/superchain/DeployTWCloneFactory.s.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.8.20;
2 |
3 | import {Script} from "forge-std/Script.sol";
4 | import "lib/forge-std/src/console.sol";
5 | import {TWCloneFactory} from "src/TWCloneFactory.sol";
6 | import {SuperChainInterop} from "src/module/token/crosschain/SuperChainInterop.sol";
7 |
8 | interface ICreateX {
9 |
10 | function deployCreate2(bytes32 salt, bytes memory initCode) external returns (address newContract);
11 |
12 | }
13 |
14 | contract DeployTWCloneFactoryScript is Script {
15 |
16 | address createX = 0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed;
17 |
18 | function deployDeterministic(bytes32 salt, bytes memory creationCode) public returns (address) {
19 | address deployedAddress;
20 |
21 | // Deploy using CREATE2
22 | assembly {
23 | deployedAddress := create2(0, add(creationCode, 0x20), mload(creationCode), salt)
24 | }
25 |
26 | require(deployedAddress != address(0), "CREATE2 failed");
27 |
28 | return deployedAddress;
29 | }
30 |
31 | function run() external {
32 | uint256 testPrivateKey = vm.envUint("DEPLOYER_PRIVATE_KEY");
33 | vm.startBroadcast(testPrivateKey);
34 |
35 | bytes32 salt;
36 | bytes memory initCode = abi.encodePacked(type(TWCloneFactory).creationCode);
37 |
38 | address twCloneFactory = ICreateX(createX).deployCreate2(salt, initCode);
39 | console.log("TWCloneFactory deployed: ", twCloneFactory);
40 |
41 | vm.stopBroadcast();
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/script/superchain/IL2ToL2CrossDomainMessenger.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity ^0.8.0;
3 |
4 | /// @title IL2ToL2CrossDomainMessenger
5 | /// @notice Interface for the L2ToL2CrossDomainMessenger contract.
6 | interface IL2ToL2CrossDomainMessenger {
7 |
8 | /// @notice Mapping of message hashes to boolean receipt values. Note that a message will only
9 | /// be present in this mapping if it has successfully been relayed on this chain, and
10 | /// can therefore not be relayed again.
11 | /// @param _msgHash message hash to check.
12 | /// @return Returns true if the message corresponding to the `_msgHash` was successfully relayed.
13 | function successfulMessages(bytes32 _msgHash) external view returns (bool);
14 |
15 | /// @notice Retrieves the next message nonce. Message version will be added to the upper two
16 | /// bytes of the message nonce. Message version allows us to treat messages as having
17 | /// different structures.
18 | /// @return Nonce of the next message to be sent, with added message version.
19 | function messageNonce() external view returns (uint256);
20 |
21 | /// @notice Retrieves the sender of the current cross domain message.
22 | /// @return sender_ Address of the sender of the current cross domain message.
23 | function crossDomainMessageSender() external view returns (address sender_);
24 |
25 | /// @notice Retrieves the source of the current cross domain message.
26 | /// @return source_ Chain ID of the source of the current cross domain message.
27 | function crossDomainMessageSource() external view returns (uint256 source_);
28 |
29 | /// @notice Sends a message to some target address on a destination chain. Note that if the call
30 | /// always reverts, then the message will be unrelayable, and any ETH sent will be
31 | /// permanently locked. The same will occur if the target on the other chain is
32 | /// considered unsafe (see the _isUnsafeTarget() function).
33 | /// @param _destination Chain ID of the destination chain.
34 | /// @param _target Target contract or wallet address.
35 | /// @param _message Message to trigger the target address with.
36 | /// @return msgHash_ The hash of the message being sent, which can be used for tracking whether
37 | /// the message has successfully been relayed.
38 | function sendMessage(uint256 _destination, address _target, bytes calldata _message)
39 | external
40 | returns (bytes32 msgHash_);
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/script/superchain/README.md:
--------------------------------------------------------------------------------
1 | # SuperChain ERC20 Module
2 | The contracts and scripts in this folder help setup and run the integration testing of the SuperChainInterop module with supersim
3 |
4 | #### IL2ToL2CrossDomainMessenger.sol
5 | Interface for the L2 to L2 cross domain messenger
6 |
7 | #### SuperChainBridge.sol
8 | Proof of concept SuperChain Bridge that contains `sendERC20` function and `relayERC20` function
9 |
10 | #### SuperChainERC20Setup.s.sol
11 | Script that deterministically deploys the `SuperChainBridge` and `SuperChainERC20` contract
12 | (which is composed of the `ERC20Core` and `SuperChainInterop` module)
13 |
14 | #### SuperChainInterop.s.sol
15 | Script that mints tokens on the L2 and calls `sendERC20` on the SuperChainBridge contract
16 |
17 | # How to run the integration test
18 | 1. Start supersim with the autorelayer enabled.
19 | ```
20 | supersim --interop.autorelay
21 | ```
22 |
23 | 2. Deploy the SuperChainBridge and SuperChainERC20 contracts on both chains 901 and 902
24 | Take note of the SuperChainBridge and Core addresses on both chains
25 | ```
26 | # deploys to chain 901
27 | forge script --chain 901 script/superchain/SuperChainERC20Setup.s.sol:SuperChainERC20SetupScript --rpc-url http://127.0.0.1:9545 -vvvv --broadcast --evm-version cancun
28 |
29 | # deploys to chain 902
30 | forge script --chain 902 script/superchain/SuperChainERC20Setup.s.sol:SuperChainERC20SetupScript --rpc-url http://127.0.0.1:9546 -vvvv --broadcast --evm-version cancun
31 | ```
32 |
33 | 3. Mint and sent tokens through the superchain
34 | ```
35 | forge script --chain 901 script/superchain/SuperChainInterop.s.sol:SuperChainInteropScript --rpc-url http://127.0.0.1:9545 -vvvv --broadcast --evm-version cancun
36 | ```
37 |
38 | 4. Check the balance of SuperChainERC20 for the test account
39 | ```
40 | cast balance --erc20 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 --rpc-url http://127.0.0.1:9546
41 | ```
42 |
43 |
--------------------------------------------------------------------------------
/script/superchain/SuperChainBridge.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | pragma solidity ^0.8.20;
3 |
4 | import {IL2ToL2CrossDomainMessenger} from "./IL2ToL2CrossDomainMessenger.sol";
5 | import "lib/forge-std/src/console.sol";
6 | import {ICrossChainERC20} from "src/module/token/crosschain/SuperChainInterop.sol";
7 |
8 | contract SuperChainBridge {
9 |
10 | address public immutable MESSENGER;
11 |
12 | event SentERC20(address indexed _token, address indexed, address indexed _to, uint256 _amount, uint256 _chainId);
13 | event RelayedERC20(
14 | address indexed _token, address indexed _from, address indexed _to, uint256 _amount, uint256 _source
15 | );
16 |
17 | constructor(address _L2ToL2CrossDomainMesenger) {
18 | MESSENGER = _L2ToL2CrossDomainMesenger;
19 | }
20 |
21 | function sendERC20(address _token, address _to, uint256 _amount, uint256 _chainId)
22 | external
23 | returns (bytes32 msgHash_)
24 | {
25 | ICrossChainERC20(_token).crosschainBurn(msg.sender, _amount);
26 |
27 | bytes memory _message = abi.encodeCall(this.relayERC20, (_token, msg.sender, _to, _amount));
28 | msgHash_ = IL2ToL2CrossDomainMessenger(MESSENGER).sendMessage(_chainId, address(this), _message);
29 | console.log("message sent");
30 |
31 | emit SentERC20(address(_token), msg.sender, _to, _amount, _chainId);
32 | }
33 |
34 | function relayERC20(address _token, address _from, address _to, uint256 _amount) external {
35 | require(msg.sender == MESSENGER);
36 | require(IL2ToL2CrossDomainMessenger(MESSENGER).crossDomainMessageSender() == address(this));
37 |
38 | uint256 _source = IL2ToL2CrossDomainMessenger(MESSENGER).crossDomainMessageSource();
39 | console.log("message received");
40 |
41 | ICrossChainERC20(_token).crosschainMint(_to, _amount);
42 |
43 | emit RelayedERC20(address(_token), _from, _to, _amount, _source);
44 | }
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/script/superchain/SuperChainERC20Setup.s.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.8.20;
2 |
3 | import {Script} from "forge-std/Script.sol";
4 | import "lib/forge-std/src/console.sol";
5 |
6 | import {ERC20Core} from "src/core/token/ERC20Core.sol";
7 |
8 | import {SuperChainBridge} from "./SuperChainBridge.sol";
9 | import {SuperChainInterop} from "src/module/token/crosschain/SuperChainInterop.sol";
10 |
11 | contract Core is ERC20Core {
12 |
13 | constructor(string memory name, string memory symbol, string memory contractURI, address owner)
14 | payable
15 | ERC20Core(name, symbol, contractURI, owner, new address[](0), new bytes[](0))
16 | {}
17 |
18 | // disable mint callbacks for this script
19 | function _beforeMint(address to, uint256 amount, bytes calldata data) internal override {}
20 |
21 | }
22 |
23 | contract SuperChainERC20SetupScript is Script {
24 |
25 | SuperChainInterop public superchainInterop;
26 | address internal constant L2_TO_L2_CROSS_DOMAIN_MESSENGER = 0x4200000000000000000000000000000000000023;
27 |
28 | function deployDeterministic(bytes32 salt, bytes memory creationCode, bytes memory encodedArgs)
29 | public
30 | returns (address)
31 | {
32 | address deployedAddress;
33 |
34 | bytes memory creationCodeWithArgs = abi.encodePacked(creationCode, encodedArgs);
35 |
36 | // Deploy using CREATE2
37 | assembly {
38 | deployedAddress := create2(0, add(creationCodeWithArgs, 0x20), mload(creationCodeWithArgs), salt)
39 | }
40 |
41 | require(deployedAddress != address(0), "CREATE2 failed");
42 |
43 | return deployedAddress;
44 | }
45 |
46 | function run() external {
47 | uint256 testPrivateKey = vm.envUint("TEST_PRIVATE_KEY");
48 | address testAddress = vm.addr(testPrivateKey);
49 | vm.startBroadcast(testPrivateKey);
50 |
51 | bytes32 salt = keccak256("thirdweb");
52 |
53 | // deploy superchain bridge
54 | address superchainBridge =
55 | deployDeterministic(salt, type(SuperChainBridge).creationCode, abi.encode(L2_TO_L2_CROSS_DOMAIN_MESSENGER));
56 | console.log("SuperChainBridge deployed: ", address(superchainBridge));
57 |
58 | // deploy core and superchainInterop module
59 | superchainInterop = new SuperChainInterop();
60 | console.log("SuperChainInterop deployed: ", address(superchainInterop));
61 |
62 | address core = deployDeterministic(salt, type(Core).creationCode, abi.encode("test", "TEST", "", testAddress));
63 | console.log("Core deployed: ", core);
64 |
65 | // install module
66 | bytes memory encodedInstallParams = superchainInterop.encodeBytesOnInstall(address(superchainBridge));
67 | Core(payable(core)).installModule(address(superchainInterop), encodedInstallParams);
68 | console.log("SuperChainInterop installed");
69 |
70 | vm.stopBroadcast();
71 | }
72 |
73 | }
74 |
--------------------------------------------------------------------------------
/script/superchain/SuperChainInterop.s.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.8.20;
2 |
3 | import {Script} from "forge-std/Script.sol";
4 | import "lib/forge-std/src/console.sol";
5 |
6 | import {ERC20Core} from "src/core/token/ERC20Core.sol";
7 |
8 | import {SuperChainBridge} from "./SuperChainBridge.sol";
9 | import {SuperChainInterop} from "src/module/token/crosschain/SuperChainInterop.sol";
10 |
11 | contract Core is ERC20Core {
12 |
13 | constructor(string memory name, string memory symbol, string memory contractURI, address owner)
14 | payable
15 | ERC20Core(name, symbol, contractURI, owner, new address[](0), new bytes[](0))
16 | {}
17 |
18 | // disable mint callbacks for this script
19 | function _beforeMint(address to, uint256 amount, bytes calldata data) internal override {}
20 |
21 | }
22 |
23 | contract SuperChainInteropScript is Script {
24 |
25 | Core public core;
26 | SuperChainInterop public superchainInterop;
27 | SuperChainBridge public superchainBridge;
28 | address internal constant L2_TO_L2_CROSS_DOMAIN_MESSENGER = 0x4200000000000000000000000000000000000023;
29 |
30 | function run() external {
31 | uint256 testPrivateKey = vm.envUint("TEST_PRIVATE_KEY");
32 | address testAddress = vm.addr(testPrivateKey);
33 | vm.startBroadcast(testPrivateKey);
34 |
35 | // setup bridge and ERC-20 core
36 | superchainBridge = SuperChainBridge(0x6BF0d7B06930e016A9c03627c5BdE157cEfA1a47);
37 | core = Core(payable(0x6f7116d27F6fAE3986bdD05652EC22232B1DDAd7));
38 | console.log("SuperChainERC20 and Bridge setup");
39 |
40 | // mint tokens
41 | core.mint(testAddress, 10 ether, "");
42 | console.log("Tokens minted to test address: ", core.balanceOf(testAddress));
43 |
44 | // sendERC20
45 | superchainBridge.sendERC20(address(core), testAddress, 10 ether, 902);
46 |
47 | vm.stopBroadcast();
48 | }
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/src/Module.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | pragma solidity ^0.8.20;
3 |
4 | import {IModule} from "./interface/IModule.sol";
5 |
6 | abstract contract Module is IModule {
7 |
8 | function getModuleConfig() external pure virtual returns (ModuleConfig memory);
9 |
10 | }
11 |
--------------------------------------------------------------------------------
/src/TWCloneFactory.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | pragma solidity ^0.8.20;
3 |
4 | import {LibClone} from "@solady/utils/LibClone.sol";
5 |
6 | contract TWCloneFactory {
7 |
8 | /// @dev Emitted when a proxy is deployed.
9 | event ProxyDeployed(address indexed implementation, address proxy, address indexed deployer, bytes data);
10 |
11 | /// @dev Deploys a proxy that points to the given implementation.
12 | function deployProxyByImplementation(address implementation, bytes memory data, bytes32 salt)
13 | public
14 | returns (address deployedProxy)
15 | {
16 | bytes32 saltHash = _guard(salt, data);
17 | deployedProxy = LibClone.cloneDeterministic(implementation, saltHash);
18 |
19 | emit ProxyDeployed(implementation, deployedProxy, msg.sender, data);
20 |
21 | if (data.length > 0) {
22 | // slither-disable-next-line unused-return
23 | (bool success,) = deployedProxy.call(data);
24 | require(success, "TWCloneFactory: proxy deployment failed");
25 | }
26 | }
27 |
28 | function _guard(bytes32 salt, bytes memory data) internal view returns (bytes32) {
29 | // 01 if cross chain deployment is allowed
30 | // 00 if cross chain deployment is not allowed
31 | bool allowCrossChainDeployment = bytes1(salt[0]) == hex"01";
32 | bool encodeDataIntoSalt = bytes1(salt[1]) == hex"01";
33 |
34 | if (allowCrossChainDeployment && encodeDataIntoSalt) {
35 | return keccak256(abi.encode(salt, data));
36 | } else if (allowCrossChainDeployment && !encodeDataIntoSalt) {
37 | return salt;
38 | } else if (!allowCrossChainDeployment && encodeDataIntoSalt) {
39 | return keccak256(abi.encode(salt, block.chainid, data));
40 | } else {
41 | return keccak256(abi.encode(salt, block.chainid));
42 | }
43 | }
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/src/callback/AfterWithdrawCallback.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | pragma solidity ^0.8.20;
3 |
4 | import {Split} from "../libraries/Split.sol";
5 |
6 | contract AfterWithdrawCallback {
7 |
8 | /*//////////////////////////////////////////////////////////////
9 | ERRORS
10 | //////////////////////////////////////////////////////////////*/
11 |
12 | error AfterWithdrawNotImplemented();
13 |
14 | /*//////////////////////////////////////////////////////////////
15 | EXTERNAL FUNCTIONS
16 | //////////////////////////////////////////////////////////////*/
17 |
18 | /**
19 | * @notice The afterWithdraw hook that is called by a core split fees contract after withdrawing tokens.
20 | * @dev Meant to be called by the core split fees contract.
21 | * @param amountToWithdraw The amount of tokens to withdraw.
22 | * @param account The address of the account to withdraw tokens to.
23 | * @param _token The address of the token to withdraw.
24 | */
25 | function afterWithdraw(uint256 amountToWithdraw, address account, address _token) external virtual {
26 | revert AfterWithdrawNotImplemented();
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/src/callback/BeforeApproveCallbackERC20.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | pragma solidity ^0.8.20;
3 |
4 | contract BeforeApproveCallbackERC20 {
5 |
6 | /*//////////////////////////////////////////////////////////////
7 | ERRORS
8 | //////////////////////////////////////////////////////////////*/
9 |
10 | error BeforeApproveCallbackERC20NotImplemented();
11 |
12 | /*//////////////////////////////////////////////////////////////
13 | EXTERNAL FUNCTIONS
14 | //////////////////////////////////////////////////////////////*/
15 |
16 | /**
17 | * @notice The beforeApproveERC20 hook that is called by a core token before approving tokens.
18 | *
19 | * @param _from The address that is approving tokens.
20 | * @param _to The address that is being approved.
21 | * @param _amount The amount of tokens being approved.
22 | * @return result Abi encoded bytes result of the hook.
23 | */
24 | function beforeApproveERC20(address _from, address _to, uint256 _amount)
25 | external
26 | virtual
27 | returns (bytes memory result)
28 | {
29 | revert BeforeApproveCallbackERC20NotImplemented();
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/src/callback/BeforeApproveCallbackERC721.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | pragma solidity ^0.8.20;
3 |
4 | contract BeforeApproveCallbackERC721 {
5 |
6 | /*//////////////////////////////////////////////////////////////
7 | ERRORS
8 | //////////////////////////////////////////////////////////////*/
9 |
10 | error BeforeApproveCallbackERC721NotImplemented();
11 |
12 | /*//////////////////////////////////////////////////////////////
13 | EXTERNAL FUNCTIONS
14 | //////////////////////////////////////////////////////////////*/
15 |
16 | /**
17 | * @notice The beforeApproveERC721 hook that is called by a core token before approving a token.
18 | *
19 | * @param _from The address that is approving tokens.
20 | * @param _to The address that is being approved.
21 | * @param _tokenId The token ID being approved.
22 | * @param _approve The approval status to set.
23 | * @return result Abi encoded bytes result of the hook.
24 | */
25 | function beforeApproveERC721(address _from, address _to, uint256 _tokenId, bool _approve)
26 | external
27 | virtual
28 | returns (bytes memory result)
29 | {
30 | revert BeforeApproveCallbackERC721NotImplemented();
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/src/callback/BeforeApproveForAllCallback.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | pragma solidity ^0.8.20;
3 |
4 | contract BeforeApproveForAllCallback {
5 |
6 | /*//////////////////////////////////////////////////////////////
7 | ERRORS
8 | //////////////////////////////////////////////////////////////*/
9 |
10 | error BeforeApproveForAllCallbackNotImplemented();
11 |
12 | /*//////////////////////////////////////////////////////////////
13 | EXTERNAL FUNCTIONS
14 | //////////////////////////////////////////////////////////////*/
15 |
16 | /**
17 | * @notice The beforeApproveForAll hook that is called by a core token before approving an operator to transfer all tokens.
18 | *
19 | * @param _from The address that is approving tokens.
20 | * @param _to The address that is being approved.
21 | * @param _approved Whether to grant or revoke approval.
22 | */
23 | function beforeApproveForAll(address _from, address _to, bool _approved)
24 | external
25 | virtual
26 | returns (bytes memory result)
27 | {
28 | revert BeforeApproveForAllCallbackNotImplemented();
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/src/callback/BeforeBatchMintCallbackERC1155.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | pragma solidity ^0.8.20;
3 |
4 | contract BeforeBatchMintCallbackERC1155 {
5 |
6 | /*//////////////////////////////////////////////////////////////
7 | ERRORS
8 | //////////////////////////////////////////////////////////////*/
9 |
10 | error BeforeBatchMintCallbackERC1155NotImplemented();
11 |
12 | /*//////////////////////////////////////////////////////////////
13 | EXTERNAL FUNCTIONS
14 | //////////////////////////////////////////////////////////////*/
15 |
16 | /**
17 | * @notice The beforeBatchMintERC1155 hook that is called by a core token before minting tokens.
18 | * @param to The address to mint the token to.
19 | * @param ids The tokenIds to mint.
20 | * @param amounts The amounts of tokens to mint.
21 | * @param data ABI encoded data to pass to the beforeBatchMint hook.
22 | * @return result Abi encoded bytes result of the hook.
23 | */
24 | function beforeBatchMintERC1155(address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data)
25 | external
26 | payable
27 | virtual
28 | returns (bytes memory result)
29 | {
30 | revert BeforeBatchMintCallbackERC1155NotImplemented();
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/src/callback/BeforeBatchTransferCallbackERC1155.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | pragma solidity ^0.8.20;
3 |
4 | contract BeforeBatchTransferCallbackERC1155 {
5 |
6 | /*//////////////////////////////////////////////////////////////
7 | ERRORS
8 | //////////////////////////////////////////////////////////////*/
9 |
10 | error BeforeBatchTransferCallbackERC1155NotImplemented();
11 |
12 | /*//////////////////////////////////////////////////////////////
13 | EXTERNAL FUNCTIONS
14 | //////////////////////////////////////////////////////////////*/
15 |
16 | /**
17 | * @notice The beforeBatchTransferERC1155 hook that is called by a core token before batch transferring tokens.
18 | *
19 | * @param from The address that is transferring tokens.
20 | * @param to The address that is receiving tokens.
21 | * @param ids The token IDs being transferred.
22 | * @param values The quantities of tokens being transferred.
23 | * @return result Abi encoded bytes result of the hook.
24 | */
25 | function beforeBatchTransferERC1155(address from, address to, uint256[] calldata ids, uint256[] calldata values)
26 | external
27 | virtual
28 | returns (bytes memory result)
29 | {
30 | revert BeforeBatchTransferCallbackERC1155NotImplemented();
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/src/callback/BeforeBurnCallbackERC1155.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | pragma solidity ^0.8.20;
3 |
4 | contract BeforeBurnCallbackERC1155 {
5 |
6 | /*//////////////////////////////////////////////////////////////
7 | ERRORS
8 | //////////////////////////////////////////////////////////////*/
9 |
10 | error BeforeBurnCallbackERC1155NotImplemented();
11 |
12 | /*//////////////////////////////////////////////////////////////
13 | EXTERNAL FUNCTIONS
14 | //////////////////////////////////////////////////////////////*/
15 |
16 | /**
17 | * @notice The beforeBurnERC1155 hook that is called by a core token before burning a token.
18 | *
19 | * @param _from The address whose tokens are being burned.
20 | * @param _id The token ID being burned.
21 | * @param _value The quantity of tokens being burned.
22 | * @param _data The encoded arguments for the beforeBurn hook.
23 | * @return result Abi encoded bytes result of the hook.
24 | */
25 | function beforeBurnERC1155(address _from, uint256 _id, uint256 _value, bytes memory _data)
26 | external
27 | payable
28 | virtual
29 | returns (bytes memory result)
30 | {
31 | revert BeforeBurnCallbackERC1155NotImplemented();
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/src/callback/BeforeBurnCallbackERC20.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | pragma solidity ^0.8.20;
3 |
4 | contract BeforeBurnCallbackERC20 {
5 |
6 | /*//////////////////////////////////////////////////////////////
7 | ERRORS
8 | //////////////////////////////////////////////////////////////*/
9 |
10 | error BeforeBurnCallbackERC20NotImplemented();
11 |
12 | /*//////////////////////////////////////////////////////////////
13 | EXTERNAL FUNCTIONS
14 | //////////////////////////////////////////////////////////////*/
15 |
16 | /**
17 | * @notice The beforeBurnERC20 hook that is called by a core token before burning tokens.
18 | *
19 | * @param _from The address whose tokens are being burned.
20 | * @param _amount The amount of tokens being burned.
21 | * @param _data The encoded arguments for the beforeBurn hook.
22 | * @return result Abi encoded bytes result of the hook.
23 | */
24 | function beforeBurnERC20(address _from, uint256 _amount, bytes memory _data)
25 | external
26 | payable
27 | virtual
28 | returns (bytes memory result)
29 | {
30 | revert BeforeBurnCallbackERC20NotImplemented();
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/src/callback/BeforeBurnCallbackERC721.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | pragma solidity ^0.8.20;
3 |
4 | contract BeforeBurnCallbackERC721 {
5 |
6 | /*//////////////////////////////////////////////////////////////
7 | ERRORS
8 | //////////////////////////////////////////////////////////////*/
9 |
10 | error BeforeBurnCallbackERC721NotImplemented();
11 |
12 | /*//////////////////////////////////////////////////////////////
13 | EXTERNAL FUNCTIONS
14 | //////////////////////////////////////////////////////////////*/
15 |
16 | /**
17 | * @notice The beforeBurnERC721 hook that is called by a core token before burning a token.
18 | *
19 | * @param _tokenId The token ID being burned.
20 | * @param _data The encoded arguments for the beforeBurn hook.
21 | * @return result Abi encoded bytes result of the hook.
22 | */
23 | function beforeBurnERC721(uint256 _tokenId, bytes memory _data)
24 | external
25 | payable
26 | virtual
27 | returns (bytes memory result)
28 | {
29 | revert BeforeBurnCallbackERC721NotImplemented();
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/src/callback/BeforeDistributeCallback.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | pragma solidity ^0.8.20;
3 |
4 | import {Split} from "../libraries/Split.sol";
5 |
6 | contract BeforeDistributeCallback {
7 |
8 | /*//////////////////////////////////////////////////////////////
9 | ERRORS
10 | //////////////////////////////////////////////////////////////*/
11 |
12 | error BeforeDistributeNotImplemented();
13 |
14 | /*//////////////////////////////////////////////////////////////
15 | EXTERNAL FUNCTIONS
16 | //////////////////////////////////////////////////////////////*/
17 |
18 | /**
19 | * @notice The beforeDistribute hook that is called by a core split fees contract before distributing tokens.
20 | * @dev Meant to be called by the core split fees contract.
21 | * @param _splitWallet The address of the split wallet contract.
22 | * @param _token The address of the token to distribute.
23 | * @return amountToSplit The amount of tokens to distribute.
24 | */
25 | function beforeDistribute(address _splitWallet, address _token) external virtual returns (uint256, Split memory) {
26 | revert BeforeDistributeNotImplemented();
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/src/callback/BeforeMintCallbackERC1155.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | pragma solidity ^0.8.20;
3 |
4 | contract BeforeMintCallbackERC1155 {
5 |
6 | /*//////////////////////////////////////////////////////////////
7 | ERRORS
8 | //////////////////////////////////////////////////////////////*/
9 |
10 | error BeforeMintCallbackERC1155NotImplemented();
11 |
12 | /*//////////////////////////////////////////////////////////////
13 | EXTERNAL FUNCTIONS
14 | //////////////////////////////////////////////////////////////*/
15 |
16 | /**
17 | * @notice The beforeMintERC1155 hook that is called by a core token before minting tokens.
18 | *
19 | * @param _to The address that is minting tokens.
20 | * @param _id The token ID being minted.
21 | * @param _amount The amount of tokens to mint.
22 | * @param _data Optional extra data passed to the hook.
23 | * @return result Abi encoded bytes result of the hook.
24 | */
25 | function beforeMintERC1155(address _to, uint256 _id, uint256 _amount, bytes memory _data)
26 | external
27 | payable
28 | virtual
29 | returns (bytes memory result)
30 | {
31 | revert BeforeMintCallbackERC1155NotImplemented();
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/src/callback/BeforeMintCallbackERC20.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | pragma solidity ^0.8.20;
3 |
4 | contract BeforeMintCallbackERC20 {
5 |
6 | /*//////////////////////////////////////////////////////////////
7 | ERRORS
8 | //////////////////////////////////////////////////////////////*/
9 |
10 | error BeforeMintCallbackERC20NotImplemented();
11 |
12 | /*//////////////////////////////////////////////////////////////
13 | EXTERNAL FUNCTIONS
14 | //////////////////////////////////////////////////////////////*/
15 |
16 | /**
17 | * @notice The beforeMintERC20 hook that is called by a core token before minting tokens.
18 | *
19 | * @param _to The address to mint tokens to.
20 | * @param _amount The amount of tokens to mint.
21 | * @param _data Optional extra data passed to the hook.
22 | * @return result Abi encoded bytes result of the hook.
23 | */
24 | function beforeMintERC20(address _to, uint256 _amount, bytes memory _data)
25 | external
26 | payable
27 | virtual
28 | returns (bytes memory result)
29 | {
30 | revert BeforeMintCallbackERC20NotImplemented();
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/src/callback/BeforeMintCallbackERC721.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | pragma solidity ^0.8.20;
3 |
4 | contract BeforeMintCallbackERC721 {
5 |
6 | /*//////////////////////////////////////////////////////////////
7 | ERRORS
8 | //////////////////////////////////////////////////////////////*/
9 |
10 | error BeforeMintCallbackERC721NotImplemented();
11 |
12 | /*//////////////////////////////////////////////////////////////
13 | EXTERNAL FUNCTIONS
14 | //////////////////////////////////////////////////////////////*/
15 |
16 | /**
17 | * @notice The beforeMintERC721 hook that is called by a core token before minting tokens.
18 | *
19 | * @param _to The address that is minting tokens.
20 | * @param _startTokenId The token ID being minted.
21 | * @param _amount The amount of tokens to mint.
22 | * @param _data Optional extra data passed to the hook.
23 | * @return result Abi encoded bytes result of the hook.
24 | */
25 | function beforeMintERC721(address _to, uint256 _startTokenId, uint256 _amount, bytes memory _data)
26 | external
27 | payable
28 | virtual
29 | returns (bytes memory result)
30 | {
31 | revert BeforeMintCallbackERC721NotImplemented();
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/src/callback/BeforeMintWithSignatureCallbackERC1155.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | pragma solidity ^0.8.20;
3 |
4 | contract BeforeMintWithSignatureCallbackERC1155 {
5 |
6 | /*//////////////////////////////////////////////////////////////
7 | ERRORS
8 | //////////////////////////////////////////////////////////////*/
9 |
10 | error BeforeMintWithSignatureCallbackERC1155NotImplemented();
11 |
12 | /*//////////////////////////////////////////////////////////////
13 | EXTERNAL FUNCTIONS
14 | //////////////////////////////////////////////////////////////*/
15 |
16 | /**
17 | * @notice The beforeMintWithSignatureERC1155 hook that is called by a core token before minting tokens.
18 | *
19 | * @param _to The address that is minting tokens.
20 | * @param _id The token ID being minted.
21 | * @param _amount The quantity of tokens to mint.
22 | * @param _data Optional extra data passed to the hook.
23 | * @param _signer The address that signed the minting request.
24 | * @return result Abi encoded bytes result of the hook.
25 | */
26 | function beforeMintWithSignatureERC1155(
27 | address _to,
28 | uint256 _id,
29 | uint256 _amount,
30 | bytes memory _data,
31 | address _signer
32 | ) external payable virtual returns (bytes memory result) {
33 | revert BeforeMintWithSignatureCallbackERC1155NotImplemented();
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/src/callback/BeforeMintWithSignatureCallbackERC20.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | pragma solidity ^0.8.20;
3 |
4 | contract BeforeMintWithSignatureCallbackERC20 {
5 |
6 | /*//////////////////////////////////////////////////////////////
7 | ERRORS
8 | //////////////////////////////////////////////////////////////*/
9 |
10 | error BeforeMintWithSignatureCallbackERC20NotImplemented();
11 |
12 | /*//////////////////////////////////////////////////////////////
13 | EXTERNAL FUNCTIONS
14 | //////////////////////////////////////////////////////////////*/
15 |
16 | /**
17 | * @notice The beforeMintWithSignatureERC20 hook that is called by a core token before minting tokens.
18 | *
19 | * @param _to The address that is minting tokens.
20 | * @param _amount The amount of tokens to mint.
21 | * @param _data Optional extra data passed to the hook.
22 | * @param _signer The address that signed the minting request.
23 | * @return result Abi encoded bytes result of the hook.
24 | */
25 | function beforeMintWithSignatureERC20(address _to, uint256 _amount, bytes memory _data, address _signer)
26 | external
27 | payable
28 | virtual
29 | returns (bytes memory result)
30 | {
31 | revert BeforeMintWithSignatureCallbackERC20NotImplemented();
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/src/callback/BeforeMintWithSignatureCallbackERC721.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | pragma solidity ^0.8.20;
3 |
4 | contract BeforeMintWithSignatureCallbackERC721 {
5 |
6 | /*//////////////////////////////////////////////////////////////
7 | ERRORS
8 | //////////////////////////////////////////////////////////////*/
9 |
10 | error BeforeMintWithSignatureCallbackERC721NotImplemented();
11 |
12 | /*//////////////////////////////////////////////////////////////
13 | EXTERNAL FUNCTIONS
14 | //////////////////////////////////////////////////////////////*/
15 |
16 | /**
17 | * @notice The beforeMintWithSignatureERC721 hook that is called by a core token before minting tokens.
18 | *
19 | * @param _to The address that is minting tokens.
20 | * @param _startTokenId The token ID being minted.
21 | * @param _amount The amount of tokens to mint.
22 | * @param _data Optional extra data passed to the hook.
23 | * @param _signer The address that signed the minting request.
24 | * @return result Abi encoded bytes result of the hook.
25 | */
26 | function beforeMintWithSignatureERC721(
27 | address _to,
28 | uint256 _startTokenId,
29 | uint256 _amount,
30 | bytes memory _data,
31 | address _signer
32 | ) external payable virtual returns (bytes memory result) {
33 | revert BeforeMintWithSignatureCallbackERC721NotImplemented();
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/src/callback/BeforeTransferCallbackERC1155.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | pragma solidity ^0.8.20;
3 |
4 | contract BeforeTransferCallbackERC1155 {
5 |
6 | /*//////////////////////////////////////////////////////////////
7 | ERRORS
8 | //////////////////////////////////////////////////////////////*/
9 |
10 | error BeforeTransferCallbackERC1155NotImplemented();
11 |
12 | /*//////////////////////////////////////////////////////////////
13 | EXTERNAL FUNCTIONS
14 | //////////////////////////////////////////////////////////////*/
15 |
16 | /**
17 | * @notice The beforeTransferERC1155 hook that is called by a core token before transferring a token.
18 | * @param _from The address that is transferring tokens.
19 | * @param _to The address that is receiving tokens.
20 | * @param _id The token ID being transferred.
21 | * @param _amount The amount of tokens being transferred.
22 | * @return result Abi encoded bytes result of the hook.
23 | */
24 | function beforeTransferERC1155(address _from, address _to, uint256 _id, uint256 _amount)
25 | external
26 | virtual
27 | returns (bytes memory result)
28 | {
29 | revert BeforeTransferCallbackERC1155NotImplemented();
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/src/callback/BeforeTransferCallbackERC20.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | pragma solidity ^0.8.20;
3 |
4 | contract BeforeTransferCallbackERC20 {
5 |
6 | /*//////////////////////////////////////////////////////////////
7 | ERRORS
8 | //////////////////////////////////////////////////////////////*/
9 |
10 | error BeforeTransferCallbackERC20NotImplemented();
11 |
12 | /*//////////////////////////////////////////////////////////////
13 | EXTERNAL FUNCTIONS
14 | //////////////////////////////////////////////////////////////*/
15 |
16 | /**
17 | * @notice The beforeTransferERC20 hook that is called by a core token before transferring tokens.
18 | *
19 | * @param _from The address that is transferring tokens.
20 | * @param _to The address that is receiving tokens.
21 | * @param _amount The amount of tokens being transferred.
22 | * @return result Abi encoded bytes result of the hook.
23 | */
24 | function beforeTransferERC20(address _from, address _to, uint256 _amount)
25 | external
26 | virtual
27 | returns (bytes memory result)
28 | {
29 | revert BeforeTransferCallbackERC20NotImplemented();
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/src/callback/BeforeTransferCallbackERC721.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | pragma solidity ^0.8.20;
3 |
4 | contract BeforeTransferCallbackERC721 {
5 |
6 | /*//////////////////////////////////////////////////////////////
7 | ERRORS
8 | //////////////////////////////////////////////////////////////*/
9 |
10 | error BeforeTransferCallbackERC721NotImplemented();
11 |
12 | /*//////////////////////////////////////////////////////////////
13 | EXTERNAL FUNCTIONS
14 | //////////////////////////////////////////////////////////////*/
15 |
16 | /**
17 | * @notice The beforeTransferERC721 hook that is called by a core token before transferring a token.
18 | *
19 | * @param _from The address that is transferring tokens.
20 | * @param _to The address that is receiving tokens.
21 | * @param _tokenId The token ID being transferred.
22 | * @return result Abi encoded bytes result of the hook.
23 | */
24 | function beforeTransferERC721(address _from, address _to, uint256 _tokenId)
25 | external
26 | virtual
27 | returns (bytes memory result)
28 | {
29 | revert BeforeTransferCallbackERC721NotImplemented();
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/src/callback/OnTokenURICallback.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | pragma solidity ^0.8.20;
3 |
4 | contract OnTokenURICallback {
5 |
6 | /*//////////////////////////////////////////////////////////////
7 | ERRORS
8 | //////////////////////////////////////////////////////////////*/
9 |
10 | error OnTokenURICallbackNotImplemented();
11 |
12 | /*//////////////////////////////////////////////////////////////
13 | EXTERNAL FUNCTIONS
14 | //////////////////////////////////////////////////////////////*/
15 |
16 | /**
17 | * @notice Returns the URI to fetch token metadata from.
18 | * @dev Meant to be called by the core token contract.
19 | * @param _tokenId The token ID of the NFT.
20 | * @return metadata The URI to fetch token metadata from.
21 | */
22 | function onTokenURI(uint256 _tokenId) external view virtual returns (string memory metadata) {
23 | revert OnTokenURICallbackNotImplemented();
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/src/callback/UpdateMetadataCallbackERC1155.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | pragma solidity ^0.8.20;
3 |
4 | contract UpdateMetadataCallbackERC1155 {
5 |
6 | /*//////////////////////////////////////////////////////////////
7 | ERRORS
8 | //////////////////////////////////////////////////////////////*/
9 |
10 | error UpdateMetadataCallbackERC1155NotImplemented();
11 |
12 | /*//////////////////////////////////////////////////////////////
13 | EXTERNAL FUNCTIONS
14 | //////////////////////////////////////////////////////////////*/
15 |
16 | /**
17 | * @notice The beforeMintERC1155 hook that is called by a core token before minting tokens.
18 | *
19 | * @param _to The address that is minting tokens.
20 | * @param _quantity The quantity of tokens to mint.
21 | * @param _baseURI The URI to fetch token metadata from.
22 | * @return result Abi encoded bytes result of the hook.
23 | */
24 | function updateMetadataERC1155(address _to, uint256 _startTokenId, uint256 _quantity, string calldata _baseURI)
25 | external
26 | payable
27 | virtual
28 | returns (bytes memory result)
29 | {
30 | revert UpdateMetadataCallbackERC1155NotImplemented();
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/src/callback/UpdateMetadataCallbackERC721.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | pragma solidity ^0.8.20;
3 |
4 | contract UpdateMetadataCallbackERC721 {
5 |
6 | /*//////////////////////////////////////////////////////////////
7 | ERRORS
8 | //////////////////////////////////////////////////////////////*/
9 |
10 | error UpdateMetadataCallbackERC721NotImplemented();
11 |
12 | /*//////////////////////////////////////////////////////////////
13 | EXTERNAL FUNCTIONS
14 | //////////////////////////////////////////////////////////////*/
15 |
16 | /**
17 | * @notice The beforeMintERC721 hook that is called by a core token before minting tokens.
18 | *
19 | * @param _to The address that is minting tokens.
20 | * @param _amount The amount of tokens to mint.
21 | * @param _baseURI The URI to fetch token metadata from.
22 | * @return result Abi encoded bytes result of the hook.
23 | */
24 | function updateMetadataERC721(address _to, uint256 _startTokenId, uint256 _amount, string calldata _baseURI)
25 | external
26 | payable
27 | virtual
28 | returns (bytes memory result)
29 | {
30 | revert UpdateMetadataCallbackERC721NotImplemented();
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/src/callback/UpdateTokenIdERC1155.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | pragma solidity ^0.8.20;
3 |
4 | contract UpdateTokenIdCallbackERC1155 {
5 |
6 | /*//////////////////////////////////////////////////////////////
7 | ERRORS
8 | //////////////////////////////////////////////////////////////*/
9 |
10 | error UpdateTokenIdCallbackERC1155NotImplemented();
11 |
12 | /*//////////////////////////////////////////////////////////////
13 | EXTERNAL FUNCTIONS
14 | //////////////////////////////////////////////////////////////*/
15 |
16 | /**
17 | * @notice The updateTokenIdERC1155 hook that is called by a core token before minting tokens.
18 | *
19 | * @dev If the tokenId is type(uint256).max, the next tokenId will be set to the current next tokenId + amount.
20 | *
21 | * @param _tokenId The tokenId to mint.
22 | * @return result tokenId to mint.
23 | */
24 | function updateTokenIdERC1155(uint256 _tokenId) external payable virtual returns (uint256) {
25 | revert UpdateTokenIdCallbackERC1155NotImplemented();
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/src/core/MintFeeManagerCore.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | pragma solidity ^0.8.20;
3 |
4 | import {Core} from "../Core.sol";
5 |
6 | contract MintFeeManagerCore is Core {
7 |
8 | constructor(address _owner, address[] memory _modules, bytes[] memory _moduleInstallData) {
9 | _initializeOwner(_owner);
10 |
11 | // Install and initialize modules
12 | require(_modules.length == _moduleInstallData.length);
13 | for (uint256 i = 0; i < _modules.length; i++) {
14 | _installModule(_modules[i], _moduleInstallData[i]);
15 | }
16 | }
17 |
18 | function getSupportedCallbackFunctions()
19 | public
20 | pure
21 | override
22 | returns (SupportedCallbackFunction[] memory supportedCallbackFunctions)
23 | {
24 | supportedCallbackFunctions = new SupportedCallbackFunction[](0);
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/src/core/SplitFeesCore.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | pragma solidity ^0.8.20;
3 |
4 | import {SplitWallet} from "../core/SplitWallet.sol";
5 |
6 | import {IERC20Metadata} from "../interface/IERC20Metadata.sol";
7 | import {Cast} from "../libraries/Cast.sol";
8 | import {ShortString, ShortStrings} from "../libraries/ShortString.sol";
9 | import {Split} from "../libraries/Split.sol";
10 |
11 | import {AfterWithdrawCallback} from "../callback/AfterWithdrawCallback.sol";
12 | import {BeforeDistributeCallback} from "../callback/BeforeDistributeCallback.sol";
13 |
14 | import {ERC6909} from "@solady/tokens/ERC6909.sol";
15 | import {LibClone} from "@solady/utils/LibClone.sol";
16 | import {Multicallable} from "@solady/utils/Multicallable.sol";
17 |
18 | import {Core} from "../Core.sol";
19 |
20 | contract SplitFeesCore is Core, Multicallable, ERC6909 {
21 |
22 | using ShortStrings for string;
23 | using ShortStrings for ShortString;
24 | using Cast for uint256;
25 | using Cast for address;
26 |
27 | /*//////////////////////////////////////////////////////////////
28 | CONSTANTS
29 | //////////////////////////////////////////////////////////////*/
30 |
31 | /// @notice prefix for metadata name.
32 | string private constant METADATA_PREFIX_NAME = "Splits Wrapped ";
33 |
34 | /// @notice prefix for metadata symbol.
35 | string private constant METADATA_PREFIX_SYMBOL = "splits";
36 |
37 | /// @notice address of the native token, inline with ERC 7528.
38 | address public constant NATIVE_TOKEN_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
39 |
40 | /// @notice uint256 representation of the native token.
41 | uint256 public constant NATIVE_TOKEN_ID = uint256(uint160(NATIVE_TOKEN_ADDRESS));
42 |
43 | /// @notice metadata name of the native token.
44 | ShortString private immutable NATIVE_TOKEN_NAME;
45 |
46 | /// @notice metadata symbol of the native token.
47 | ShortString private immutable NATIVE_TOKEN_SYMBOL;
48 |
49 | address public splitWalletImplementation;
50 |
51 | /*//////////////////////////////////////////////////////////////
52 | CONSTRUCTOR
53 | //////////////////////////////////////////////////////////////*/
54 |
55 | receive() external payable {}
56 |
57 | constructor(address _owner, address[] memory _modules, bytes[] memory _moduleInstallData) {
58 | _initializeOwner(_owner);
59 |
60 | splitWalletImplementation = address(new SplitWallet(_owner));
61 |
62 | // Install and initialize modules
63 | require(_modules.length == _moduleInstallData.length);
64 | for (uint256 i = 0; i < _modules.length; i++) {
65 | _installModule(_modules[i], _moduleInstallData[i]);
66 | }
67 | }
68 |
69 | function getSupportedCallbackFunctions()
70 | public
71 | pure
72 | override
73 | returns (SupportedCallbackFunction[] memory supportedCallbackFunctions)
74 | {
75 | supportedCallbackFunctions = new SupportedCallbackFunction[](2);
76 |
77 | supportedCallbackFunctions[0] = SupportedCallbackFunction({
78 | selector: BeforeDistributeCallback.beforeDistribute.selector,
79 | mode: CallbackMode.REQUIRED
80 | });
81 | supportedCallbackFunctions[1] = SupportedCallbackFunction({
82 | selector: AfterWithdrawCallback.afterWithdraw.selector,
83 | mode: CallbackMode.REQUIRED
84 | });
85 | }
86 |
87 | function supportsInterface(bytes4 interfaceId) public view virtual override(ERC6909, Core) returns (bool) {
88 | return interfaceId == 0x0f632fb3 || super.supportsInterface(interfaceId);
89 | }
90 |
91 | function distribute(address _splitWallet, address _token) external {
92 | (uint256 amountToSplit, Split memory _split) = _beforeDistribute(_splitWallet, _token);
93 |
94 | uint256 length = _split.recipients.length;
95 | for (uint256 i = 0; i < length; i++) {
96 | uint256 amountToSend = (amountToSplit * _split.allocations[i]) / _split.totalAllocation;
97 |
98 | _mint(_split.recipients[i], _token.toUint256(), amountToSend);
99 | }
100 | }
101 |
102 | function withdraw(address account, address _token) external {
103 | uint256 amountToWithdraw = balanceOf(account, _token.toUint256());
104 | _burn(account, _token.toUint256(), amountToWithdraw);
105 | _afterWithdraw(amountToWithdraw, account, _token);
106 | }
107 |
108 | /**
109 | * @notice Name of a given token.
110 | * @param id The id of the token.
111 | * @return The name of the token.
112 | */
113 | function name(uint256 id) public view override returns (string memory) {
114 | if (id == NATIVE_TOKEN_ID) {
115 | return NATIVE_TOKEN_NAME.toString();
116 | }
117 | return string.concat(METADATA_PREFIX_NAME, IERC20Metadata(id.toAddress()).name());
118 | }
119 |
120 | /**
121 | * @notice Symbol of a given token.
122 | * @param id The id of the token.
123 | * @return The symbol of the token.
124 | */
125 | function symbol(uint256 id) public view override returns (string memory) {
126 | if (id == NATIVE_TOKEN_ID) {
127 | return NATIVE_TOKEN_SYMBOL.toString();
128 | }
129 | return string.concat(METADATA_PREFIX_SYMBOL, IERC20Metadata(id.toAddress()).symbol());
130 | }
131 |
132 | /// @dev Returns the Uniform Resource Identifier (URI) for token `id`.
133 | function tokenURI(uint256 id) public view override returns (string memory) {
134 | return "";
135 | }
136 |
137 | /// @dev Fetches token URI from the token metadata hook.
138 | function _beforeDistribute(address _splitWallet, address _token)
139 | internal
140 | returns (uint256 amountToSplit, Split memory _split)
141 | {
142 | (, bytes memory returndata) = _executeCallbackFunction(
143 | BeforeDistributeCallback.beforeDistribute.selector,
144 | abi.encodeCall(BeforeDistributeCallback.beforeDistribute, (_splitWallet, _token))
145 | );
146 | (amountToSplit, _split) = abi.decode(returndata, (uint256, Split));
147 | }
148 |
149 | /// @dev Fetches token URI from the token metadata hook.
150 | function _afterWithdraw(uint256 amountToWithdraw, address account, address _token) internal {
151 | _executeCallbackFunction(
152 | AfterWithdrawCallback.afterWithdraw.selector,
153 | abi.encodeCall(AfterWithdrawCallback.afterWithdraw, (amountToWithdraw, account, _token))
154 | );
155 | }
156 |
157 | }
158 |
--------------------------------------------------------------------------------
/src/core/SplitWallet.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | pragma solidity ^0.8.20;
3 |
4 | import {Core} from "../Core.sol";
5 |
6 | import {IERC20} from "../interface/IERC20.sol";
7 | import {Ownable} from "@solady/auth/Ownable.sol";
8 |
9 | contract SplitWallet is Ownable {
10 |
11 | address public immutable splitFees;
12 |
13 | constructor(address _owner) {
14 | splitFees = msg.sender;
15 | _initializeOwner(_owner);
16 | }
17 |
18 | error OnlySplitFees();
19 |
20 | modifier onlySplitFees() {
21 | if (msg.sender != splitFees) {
22 | revert OnlySplitFees();
23 | }
24 | _;
25 | }
26 |
27 | function transferETH(uint256 amount) external payable onlySplitFees {
28 | (bool success,) = splitFees.call{value: amount}("");
29 | require(success, "Failed to send Ether");
30 | }
31 |
32 | function transferERC20(address token, uint256 amount) external onlySplitFees {
33 | IERC20(token).transfer(splitFees, amount);
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/src/core/token/ERC1155Core.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | pragma solidity ^0.8.20;
3 |
4 | import {ERC1155Base} from "./ERC1155Base.sol";
5 |
6 | contract ERC1155Core is ERC1155Base {
7 |
8 | constructor(
9 | string memory _name,
10 | string memory _symbol,
11 | string memory _contractURI,
12 | address _owner,
13 | address[] memory _modules,
14 | bytes[] memory _moduleInstallData
15 | ) {
16 | _initialize(_name, _symbol, _contractURI, _owner, _modules, _moduleInstallData);
17 | }
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/src/core/token/ERC1155CoreInitializable.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | pragma solidity ^0.8.20;
3 |
4 | import {ERC1155Base} from "./ERC1155Base.sol";
5 | import {Initializable} from "@solady/utils/Initializable.sol";
6 |
7 | contract ERC1155CoreInitializable is ERC1155Base, Initializable {
8 |
9 | constructor() {
10 | _disableInitializers();
11 | }
12 |
13 | function initialize(
14 | string memory _name,
15 | string memory _symbol,
16 | string memory _contractURI,
17 | address _owner,
18 | address[] memory _modules,
19 | bytes[] memory _moduleInstallData
20 | ) external payable initializer {
21 | _initialize(_name, _symbol, _contractURI, _owner, _modules, _moduleInstallData);
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/src/core/token/ERC20Core.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | pragma solidity ^0.8.20;
3 |
4 | import {ERC20Base} from "./ERC20Base.sol";
5 |
6 | contract ERC20Core is ERC20Base {
7 |
8 | constructor(
9 | string memory _name,
10 | string memory _symbol,
11 | string memory _contractURI,
12 | address _owner,
13 | address[] memory _modules,
14 | bytes[] memory _moduleInstallData
15 | ) {
16 | _initialize(_name, _symbol, _contractURI, _owner, _modules, _moduleInstallData);
17 | }
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/src/core/token/ERC20CoreInitializable.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | pragma solidity ^0.8.20;
3 |
4 | import {ERC20Base} from "./ERC20Base.sol";
5 | import {Initializable} from "@solady/utils/Initializable.sol";
6 |
7 | contract ERC20CoreInitializable is ERC20Base, Initializable {
8 |
9 | constructor() {
10 | _disableInitializers();
11 | }
12 |
13 | function initialize(
14 | string memory _name,
15 | string memory _symbol,
16 | string memory _contractURI,
17 | address _owner,
18 | address[] memory _modules,
19 | bytes[] memory _moduleInstallData
20 | ) external payable initializer {
21 | _initialize(_name, _symbol, _contractURI, _owner, _modules, _moduleInstallData);
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/src/core/token/ERC721Core.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | pragma solidity ^0.8.20;
3 |
4 | import {ERC721Base} from "./ERC721Base.sol";
5 |
6 | contract ERC721Core is ERC721Base {
7 |
8 | constructor(
9 | string memory _name,
10 | string memory _symbol,
11 | string memory _contractURI,
12 | address _owner,
13 | address[] memory _modules,
14 | bytes[] memory _moduleInstallData
15 | ) {
16 | _initialize(_name, _symbol, _contractURI, _owner, _modules, _moduleInstallData);
17 | }
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/src/core/token/ERC721CoreInitializable.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | pragma solidity ^0.8.20;
3 |
4 | import {ERC721Base} from "./ERC721Base.sol";
5 | import {Initializable} from "@solady/utils/Initializable.sol";
6 |
7 | contract ERC721CoreInitializable is ERC721Base, Initializable {
8 |
9 | constructor() {
10 | _disableInitializers();
11 | }
12 |
13 | function initialize(
14 | string memory _name,
15 | string memory _symbol,
16 | string memory _contractURI,
17 | address _owner,
18 | address[] memory _modules,
19 | bytes[] memory _moduleInstallData
20 | ) external payable initializer {
21 | _initialize(_name, _symbol, _contractURI, _owner, _modules, _moduleInstallData);
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/src/interface/ICore.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | pragma solidity ^0.8.20;
3 |
4 | import {IERC165} from "./IERC165.sol";
5 | import {IModuleConfig} from "./IModuleConfig.sol";
6 |
7 | interface ICore is IModuleConfig, IERC165 {
8 |
9 | /*//////////////////////////////////////////////////////////////
10 | STRUCTS & ENUMS
11 | //////////////////////////////////////////////////////////////*/
12 |
13 | /**
14 | * @dev Whether execution reverts when the callback function is not implemented by any installed Module.
15 | * @param OPTIONAL Execution does not revert when the callback function is not implemented.
16 | * @param REQUIRED Execution reverts when the callback function is not implemented.
17 | */
18 | enum CallbackMode {
19 | OPTIONAL,
20 | REQUIRED
21 | }
22 |
23 | /**
24 | * @dev Struct representing a callback function called on an Module during some fixed function's execution.
25 | * @param selector The 4-byte function selector of the callback function.
26 | * @param mode Whether execution reverts when the callback function is not implemented by any installed Module.
27 | */
28 | struct SupportedCallbackFunction {
29 | bytes4 selector;
30 | CallbackMode mode;
31 | }
32 |
33 | /**
34 | * @dev Struct representing an installed Module.
35 | * @param implementation The address of the Module contract.
36 | * @param config The Module Config of the Module contract.
37 | */
38 | struct InstalledModule {
39 | address implementation;
40 | ModuleConfig config;
41 | }
42 |
43 | /*//////////////////////////////////////////////////////////////
44 | VIEW FUNCTIONS
45 | //////////////////////////////////////////////////////////////*/
46 |
47 | /// @dev Returns all callback function calls made to Modules at some point during a fixed function's execution.
48 | function getSupportedCallbackFunctions() external pure returns (SupportedCallbackFunction[] memory);
49 |
50 | /// @dev Returns all installed modules and their respective module configs.
51 | function getInstalledModules() external view returns (InstalledModule[] memory);
52 |
53 | /*//////////////////////////////////////////////////////////////
54 | EXTERNAL FUNCTIONS
55 | //////////////////////////////////////////////////////////////*/
56 |
57 | /**
58 | * @dev Installs an Module in the Core.
59 | *
60 | * @param moduleContract The address of the Module contract to be installed.
61 | * @param data The data to be passed to the Module's onInstall callback function.
62 | *
63 | * MUST implement authorization control.
64 | * MUST call `onInstall` callback function if Module Config has registerd for installation callbacks.
65 | * MUST revert if Core does not implement the interface required by the Module, specified in the Module Config.
66 | * MUST revert if any callback or fallback function in the Module's ModuleConfig is already registered in the Core with another Module.
67 | *
68 | * MAY interpret the provided address as the implementation address of the Module contract to install as a proxy.
69 | */
70 | function installModule(address moduleContract, bytes calldata data) external payable;
71 |
72 | /**
73 | * @dev Uninstalls an Module from the Core.
74 | *
75 | * @param moduleContract The address of the Module contract to be uninstalled.
76 | * @param data The data to be passed to the Module's onUninstall callback function.
77 | *
78 | * MUST implement authorization control.
79 | * MUST call `onUninstall` callback function if Module Config has registerd for installation callbacks.
80 | *
81 | * MAY interpret the provided address as the implementation address of the Module contract which is installed as a proxy.
82 | */
83 | function uninstallModule(address moduleContract, bytes calldata data) external payable;
84 |
85 | }
86 |
--------------------------------------------------------------------------------
/src/interface/IERC165.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | pragma solidity ^0.8.20;
3 |
4 | interface IERC165 {
5 |
6 | /// @notice Query if a contract implements an interface
7 | /// @param interfaceID The interface identifier, as specified in ERC-165
8 | /// @dev Interface identification is specified in ERC-165. This function
9 | /// uses less than 30,000 gas.
10 | /// @return `true` if the contract implements `interfaceID` and
11 | /// `interfaceID` is not 0xffffffff, `false` otherwise
12 | function supportsInterface(bytes4 interfaceID) external view returns (bool);
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/src/interface/IERC20.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | pragma solidity ^0.8.20;
3 |
4 | interface IERC20 {
5 |
6 | event Transfer(address indexed from, address indexed to, uint256 value);
7 | event Approval(address indexed owner, address indexed spender, uint256 value);
8 |
9 | function totalSupply() external view returns (uint256);
10 |
11 | function balanceOf(address account) external view returns (uint256);
12 |
13 | function transfer(address to, uint256 value) external returns (bool);
14 |
15 | function allowance(address owner, address spender) external view returns (uint256);
16 |
17 | function approve(address spender, uint256 value) external returns (bool);
18 |
19 | function transferFrom(address from, address to, uint256 value) external returns (bool);
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/src/interface/IERC20Metadata.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: APACHE-2.0
2 | pragma solidity ^0.8.20;
3 |
4 | import "./IERC20.sol";
5 |
6 | interface IERC20Metadata is IERC20 {
7 |
8 | function name() external view returns (string memory);
9 |
10 | function symbol() external view returns (string memory);
11 |
12 | function decimals() external view returns (uint8);
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/src/interface/IERC7572.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | pragma solidity ^0.8.20;
3 |
4 | interface IERC7572 {
5 |
6 | function contractURI() external view returns (string memory);
7 |
8 | event ContractURIUpdated();
9 |
10 | }
11 |
--------------------------------------------------------------------------------
/src/interface/IInstallationCallback.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | pragma solidity ^0.8.20;
3 |
4 | interface IInstallationCallback {
5 |
6 | /*//////////////////////////////////////////////////////////////
7 | EXTERNAL FUNCTIONS
8 | //////////////////////////////////////////////////////////////*/
9 |
10 | /**
11 | * @dev Called by a Core into an Module during the installation of the Module.
12 | *
13 | * @param data The data passed to the Core's installModule function.
14 | */
15 | function onInstall(bytes calldata data) external;
16 |
17 | /**
18 | * @dev Called by a Core into an Module during the uninstallation of the Module.
19 | *
20 | * @param data The data passed to the Core's uninstallModule function.
21 | */
22 | function onUninstall(bytes calldata data) external;
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/src/interface/IMintFeeManager.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | pragma solidity ^0.8.20;
3 |
4 | interface IMintFeeManager {
5 |
6 | function calculatePlatformFeeAndRecipient(uint256 _price)
7 | external
8 | view
9 | returns (uint256 platformFee, address feeRecipient);
10 |
11 | }
12 |
--------------------------------------------------------------------------------
/src/interface/IModule.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | pragma solidity ^0.8.20;
3 |
4 | import {IModuleConfig} from "./IModuleConfig.sol";
5 |
6 | interface IModule is IModuleConfig {
7 |
8 | /*//////////////////////////////////////////////////////////////
9 | VIEW FUNCTIONS
10 | //////////////////////////////////////////////////////////////*/
11 |
12 | /**
13 | * @dev Returns the ModuleConfig of the Module contract.
14 | */
15 | function getModuleConfig() external pure returns (ModuleConfig memory);
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/src/interface/IModuleConfig.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | pragma solidity ^0.8.20;
3 |
4 | interface IModuleConfig {
5 |
6 | /*//////////////////////////////////////////////////////////////
7 | STRUCTS & ENUMS
8 | //////////////////////////////////////////////////////////////*/
9 |
10 | /**
11 | * @dev Struct for a callback function. Called by a Core into an Module during the execution of some fixed function.
12 | *
13 | * @param selector The 4-byte selector of the function.
14 | * @param callType The type of call to be made to the function.
15 | */
16 | struct CallbackFunction {
17 | bytes4 selector;
18 | }
19 |
20 | /**
21 | * @dev Struct for a fallback function. Called by a Core into an Module via the Core's fallback.
22 | *
23 | * @param selector The 4-byte selector of the function.
24 | * @param callType The type of call to be made to the function.
25 | * @param permissionBits Core’s fallback function MUST check that msg.sender has these permissions before
26 | * performing a call on the Module. (OPTIONAL field)
27 | */
28 | struct FallbackFunction {
29 | bytes4 selector;
30 | uint256 permissionBits;
31 | }
32 |
33 | /**
34 | * @dev Struct containing all information that a Core uses to check whether an Module is compatible for installation.
35 | *
36 | * @param registerInstallationCallback Whether the Module expects onInstall and onUninstall callback function calls at
37 | * installation and uninstallation time, respectively
38 | * @param requiredInterfaces The ERC-165 interface that a Core MUST support to be compatible for installation. OPTIONAL -- can be bytes4(0)
39 | * if there is no required interface id.
40 | * @param supportedInterfaces The ERC-165 interfaces that a Core supports upon installing the Module.
41 | * @param callbackFunctions List of callback functions that the Core MUST call at some point in the execution of its fixed functions.
42 | * @param fallbackFunctions List of functions that the Core MUST call via its fallback function with the Module as the call destination.
43 | */
44 | struct ModuleConfig {
45 | bool registerInstallationCallback;
46 | bytes4[] requiredInterfaces;
47 | bytes4[] supportedInterfaces;
48 | CallbackFunction[] callbackFunctions;
49 | FallbackFunction[] fallbackFunctions;
50 | }
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/src/interface/ISplitWallet.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | pragma solidity ^0.8.20;
3 |
4 | interface ISplitWallet {
5 |
6 | function transferETH(uint256 amount) external payable;
7 | function transferERC20(address token, uint256 amount) external;
8 |
9 | }
10 |
--------------------------------------------------------------------------------
/src/libraries/Cast.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.8.20;
2 |
3 | library Cast {
4 |
5 | function toAddress(uint256 _value) internal pure returns (address) {
6 | return address(uint160(_value));
7 | }
8 |
9 | function toUint256(address _value) internal pure returns (uint256) {
10 | return uint256(uint160(_value));
11 | }
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/src/libraries/ShortString.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.8.20;
2 |
3 | type ShortString is bytes32;
4 |
5 | library ShortStrings {
6 |
7 | error StringTooLong(string str);
8 | error InvalidShortString();
9 |
10 | /**
11 | * @dev Encode a string of at most 31 chars into a `ShortString`.
12 | *
13 | * This will trigger a `StringTooLong` error is the input string is too long.
14 | */
15 | function toShortString(string memory str) internal pure returns (ShortString) {
16 | bytes memory bstr = bytes(str);
17 | if (bstr.length > 31) {
18 | revert StringTooLong(str);
19 | }
20 | return ShortString.wrap(bytes32(uint256(bytes32(bstr)) | bstr.length));
21 | }
22 |
23 | /**
24 | * @dev Decode a `ShortString` back to a "normal" string.
25 | */
26 | function toString(ShortString sstr) internal pure returns (string memory) {
27 | uint256 len = byteLength(sstr);
28 | // using `new string(len)` would work locally but is not memory safe.
29 | string memory str = new string(32);
30 | /// @solidity memory-safe-assembly
31 | assembly {
32 | mstore(str, len)
33 | mstore(add(str, 0x20), sstr)
34 | }
35 | return str;
36 | }
37 |
38 | /**
39 | * @dev Return the length of a `ShortString`.
40 | */
41 | function byteLength(ShortString sstr) internal pure returns (uint256) {
42 | uint256 result = uint256(ShortString.unwrap(sstr)) & 0xFF;
43 | if (result > 31) {
44 | revert InvalidShortString();
45 | }
46 | return result;
47 | }
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/src/libraries/Split.sol:
--------------------------------------------------------------------------------
1 | struct Split {
2 | address controller;
3 | address[] recipients;
4 | uint256[] allocations;
5 | uint256 totalAllocation;
6 | }
7 |
--------------------------------------------------------------------------------
/src/module/MintFeeManagerModule.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | pragma solidity ^0.8.20;
3 |
4 | import {Module} from "../Module.sol";
5 | import {Role} from "../Role.sol";
6 |
7 | library MintFeeManagerStorage {
8 |
9 | /// @custom:storage-location erc7201:mint.fee.manager
10 | bytes32 public constant MINT_FEE_MANAGER_STORAGE_POSITION =
11 | keccak256(abi.encode(uint256(keccak256("mint.fee.manager")) - 1)) & ~bytes32(uint256(0xff));
12 |
13 | struct Data {
14 | address feeRecipient;
15 | uint256 defaultMintFee;
16 | mapping(address _contract => uint256 mintFee) mintFees;
17 | }
18 |
19 | function data() internal pure returns (Data storage data_) {
20 | bytes32 position = MINT_FEE_MANAGER_STORAGE_POSITION;
21 | assembly {
22 | data_.slot := position
23 | }
24 | }
25 |
26 | }
27 |
28 | contract MintFeeManagerModule is Module {
29 |
30 | uint256 private constant MINT_FEE_MAX_BPS = 10_000;
31 |
32 | event MintFeeUpdated(address indexed contractAddress, uint256 mintFee);
33 | event DefaultMintFeeUpdated(uint256 mintFee);
34 | event feeRecipientUpdated(address feeRecipient);
35 |
36 | error MintFeeExceedsMaxBps();
37 |
38 | function getModuleConfig() external pure override returns (ModuleConfig memory config) {
39 | config.fallbackFunctions = new FallbackFunction[](7);
40 |
41 | config.fallbackFunctions[0] = FallbackFunction({selector: this.getfeeRecipient.selector, permissionBits: 0});
42 | config.fallbackFunctions[1] =
43 | FallbackFunction({selector: this.setfeeRecipient.selector, permissionBits: Role._MANAGER_ROLE});
44 | config.fallbackFunctions[2] = FallbackFunction({selector: this.getDefaultMintFee.selector, permissionBits: 0});
45 | config.fallbackFunctions[3] =
46 | FallbackFunction({selector: this.setDefaultMintFee.selector, permissionBits: Role._MANAGER_ROLE});
47 | config.fallbackFunctions[4] = FallbackFunction({selector: this.getMintFees.selector, permissionBits: 0});
48 | config.fallbackFunctions[5] =
49 | FallbackFunction({selector: this.updateMintFee.selector, permissionBits: Role._MANAGER_ROLE});
50 | config.fallbackFunctions[6] =
51 | FallbackFunction({selector: this.calculatePlatformFeeAndRecipient.selector, permissionBits: 0});
52 |
53 | config.registerInstallationCallback = true;
54 | }
55 |
56 | /// @dev Called by a Core into an Module during the installation of the Module.
57 | function onInstall(bytes calldata data) external {
58 | (address _feeRecipient, uint256 _defaultMintFee) = abi.decode(data, (address, uint256));
59 | setfeeRecipient(_feeRecipient);
60 | setDefaultMintFee(_defaultMintFee);
61 | }
62 |
63 | /// @dev Called by a Core into an Module during the uninstallation of the Module.
64 | function onUninstall(bytes calldata data) external {}
65 |
66 | /*//////////////////////////////////////////////////////////////
67 | Encode install / uninstall data
68 | //////////////////////////////////////////////////////////////*/
69 |
70 | /// @dev Returns bytes encoded install params, to be sent to `onInstall` function
71 | function encodeBytesOnInstall(address _feeRecipient, uint256 _defaultMintFee)
72 | external
73 | pure
74 | returns (bytes memory)
75 | {
76 | return abi.encode(_feeRecipient, _defaultMintFee);
77 | }
78 |
79 | /// @dev Returns bytes encoded uninstall params, to be sent to `onUninstall` function
80 | function encodeBytesOnUninstall() external pure returns (bytes memory) {
81 | return "";
82 | }
83 |
84 | /*//////////////////////////////////////////////////////////////
85 | FALLBACK FUNCTIONS
86 | //////////////////////////////////////////////////////////////*/
87 |
88 | function getfeeRecipient() external view returns (address) {
89 | return _mintFeeManagerStorage().feeRecipient;
90 | }
91 |
92 | function setfeeRecipient(address _feeRecipient) public {
93 | _mintFeeManagerStorage().feeRecipient = _feeRecipient;
94 |
95 | emit feeRecipientUpdated(_feeRecipient);
96 | }
97 |
98 | function getDefaultMintFee() external view returns (uint256) {
99 | return _mintFeeManagerStorage().defaultMintFee;
100 | }
101 |
102 | function setDefaultMintFee(uint256 _defaultMintFee) public {
103 | if (_defaultMintFee > MINT_FEE_MAX_BPS) {
104 | revert MintFeeExceedsMaxBps();
105 | }
106 | _mintFeeManagerStorage().defaultMintFee = _defaultMintFee;
107 |
108 | emit DefaultMintFeeUpdated(_defaultMintFee);
109 | }
110 |
111 | function getMintFees(address _contract) external view returns (uint256) {
112 | return _mintFeeManagerStorage().mintFees[_contract];
113 | }
114 |
115 | /**
116 | * @notice updates the mint fee for the specified contract
117 | * @param _contract the address of the token to be updated
118 | * @param _mintFee the new mint fee for the contract
119 | * @dev a mint fee of 0 means they are registered under the default mint fee
120 | */
121 | function updateMintFee(address _contract, uint256 _mintFee) external {
122 | if (_mintFee > MINT_FEE_MAX_BPS && _mintFee != type(uint256).max) {
123 | revert MintFeeExceedsMaxBps();
124 | }
125 | _mintFeeManagerStorage().mintFees[_contract] = _mintFee;
126 |
127 | emit MintFeeUpdated(_contract, _mintFee);
128 | }
129 |
130 | /**
131 | * @notice returns the mint fee for the specified contract and the platform fee recipient
132 | * @param _price the price of the token to be minted
133 | * @return the mint fee for the specified contract and the platform fee recipient
134 | * @dev a mint fee of uint256 max means they are subject to zero mint fees
135 | */
136 | function calculatePlatformFeeAndRecipient(uint256 _price) external view returns (uint256, address) {
137 | uint256 mintFee;
138 |
139 | if (_mintFeeManagerStorage().mintFees[msg.sender] == 0) {
140 | mintFee = (_price * _mintFeeManagerStorage().defaultMintFee) / MINT_FEE_MAX_BPS;
141 | } else if (_mintFeeManagerStorage().mintFees[msg.sender] != type(uint256).max) {
142 | mintFee = (_price * _mintFeeManagerStorage().mintFees[msg.sender]) / MINT_FEE_MAX_BPS;
143 | }
144 |
145 | return (mintFee, _mintFeeManagerStorage().feeRecipient);
146 | }
147 |
148 | function _mintFeeManagerStorage() internal pure returns (MintFeeManagerStorage.Data storage) {
149 | return MintFeeManagerStorage.data();
150 | }
151 |
152 | }
153 |
--------------------------------------------------------------------------------
/src/module/token/crosschain/CrossChain.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | pragma solidity ^0.8.20;
3 |
4 | abstract contract CrossChain {
5 |
6 | /*//////////////////////////////////////////////////////////////
7 | ERRORS
8 | //////////////////////////////////////////////////////////////*/
9 |
10 | error OnCrossChainTransactionSentNotImplemented();
11 | error OnCrossChainTransactionReceivedNotImplemented();
12 |
13 | /*//////////////////////////////////////////////////////////////
14 | EXTERNAL FUNCTIONS
15 | //////////////////////////////////////////////////////////////*/
16 |
17 | /**
18 | * @notice Sends a cross-chain transaction.
19 | * @param _destinationChain The destination chain ID.
20 | * @param _callAddress The address of the contract on the destination chain.
21 | * @param _payload The payload to send to the destination chain.
22 | * @param _extraArgs The extra arguments to pass
23 | * @dev extraArgs may contain items such as token, amount, feeTokenAddress, receipient, gasLimit, etc
24 | */
25 | function sendCrossChainTransaction(
26 | uint64 _destinationChain,
27 | address _callAddress,
28 | bytes calldata _payload,
29 | bytes calldata _extraArgs
30 | ) external payable virtual;
31 |
32 | /**
33 | * @notice callback function for when a cross-chain transaction is sent.
34 | * @param _destinationChain The destination chain ID.
35 | * @param _callAddress The address of the contract on the destination chain.
36 | * @param _payload The payload sent to the destination chain.
37 | * @param _extraArgs The extra arguments sent to the callAddress on the destination chain.
38 | */
39 | function onCrossChainTransactionSent(
40 | uint64 _destinationChain,
41 | address _callAddress,
42 | bytes calldata _payload,
43 | bytes calldata _extraArgs
44 | ) internal virtual {
45 | revert OnCrossChainTransactionSentNotImplemented();
46 | }
47 |
48 | /**
49 | * @notice callback function for when a cross-chain transaction is received.
50 | * @param _sourceChain The source chain ID.
51 | * @param _sourceAddress The address of the contract on the source chain.
52 | * @param _payload The payload sent to the destination chain.
53 | * @param _extraArgs The extra arguments sent to the callAddress on the destination chain.
54 | */
55 | function onCrossChainTransactionReceived(
56 | uint64 _sourceChain,
57 | address _sourceAddress,
58 | bytes memory _payload,
59 | bytes memory _extraArgs
60 | ) internal virtual {
61 | revert OnCrossChainTransactionReceivedNotImplemented();
62 | }
63 |
64 | function setRouter(address _router) external virtual;
65 | function getRouter() external view virtual returns (address);
66 |
67 | }
68 |
--------------------------------------------------------------------------------
/src/module/token/crosschain/SuperChainInterop.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | pragma solidity ^0.8.20;
3 |
4 | import {Module} from "../../../Module.sol";
5 |
6 | import {Role} from "../../../Role.sol";
7 | import {IInstallationCallback} from "../../../interface/IInstallationCallback.sol";
8 | import {IMintFeeManager} from "../../../interface/IMintFeeManager.sol";
9 | import {OwnableRoles} from "@solady/auth/OwnableRoles.sol";
10 | import {ERC20} from "@solady/tokens/ERC20.sol";
11 | import {SafeTransferLib} from "@solady/utils/SafeTransferLib.sol";
12 |
13 | import {BeforeMintCallbackERC20} from "../../../callback/BeforeMintCallbackERC20.sol";
14 | import {BeforeMintWithSignatureCallbackERC20} from "../../../callback/BeforeMintWithSignatureCallbackERC20.sol";
15 |
16 | // Inherit from OP repo once implemented
17 | interface ICrossChainERC20 {
18 |
19 | function crosschainMint(address _account, uint256 _amount) external;
20 | function crosschainBurn(address _account, uint256 _amount) external;
21 |
22 | event CrosschainMinted(address indexed _to, uint256 _amount);
23 | event CrosschainBurnt(address indexed _from, uint256 _amount);
24 |
25 | }
26 |
27 | library SuperChainInteropStorage {
28 |
29 | /// @custom:storage-location erc7201:crosschain.superchain.erc20
30 | bytes32 public constant SUPERCHAIN_ERC20_POSITION =
31 | keccak256(abi.encode(uint256(keccak256("crosschain.superchain.erc20")) - 1)) & ~bytes32(uint256(0xff));
32 |
33 | struct Data {
34 | address superchainBridge;
35 | }
36 |
37 | function data() internal pure returns (Data storage data_) {
38 | bytes32 position = SUPERCHAIN_ERC20_POSITION;
39 | assembly {
40 | data_.slot := position
41 | }
42 | }
43 |
44 | }
45 |
46 | contract SuperChainInterop is ERC20, Module, IInstallationCallback, ICrossChainERC20 {
47 |
48 | /*//////////////////////////////////////////////////////////////
49 | ERRORS
50 | //////////////////////////////////////////////////////////////*/
51 |
52 | /// @dev Emitted when the minting request signature is unauthorized.
53 | error SuperChainInteropNotSuperChainBridge();
54 |
55 | /*//////////////////////////////////////////////////////////////
56 | MODULE CONFIG
57 | //////////////////////////////////////////////////////////////*/
58 |
59 | function getModuleConfig() external pure override returns (ModuleConfig memory config) {
60 | config.fallbackFunctions = new FallbackFunction[](4);
61 |
62 | config.fallbackFunctions[0] = FallbackFunction({selector: this.crosschainMint.selector, permissionBits: 0});
63 | config.fallbackFunctions[1] = FallbackFunction({selector: this.crosschainBurn.selector, permissionBits: 0});
64 | config.fallbackFunctions[2] = FallbackFunction({selector: this.getSuperChainBridge.selector, permissionBits: 0});
65 | config.fallbackFunctions[3] =
66 | FallbackFunction({selector: this.setSuperChainBridge.selector, permissionBits: Role._MANAGER_ROLE});
67 |
68 | config.requiredInterfaces = new bytes4[](1);
69 | config.requiredInterfaces[0] = 0x36372b07; // ERC20
70 |
71 | config.registerInstallationCallback = true;
72 | }
73 |
74 | /*//////////////////////////////////////////////////////////////
75 | ERC20 FUNCTIONS
76 | //////////////////////////////////////////////////////////////*/
77 |
78 | /// @dev Returns the name of the token.
79 | function name() public pure override returns (string memory) {
80 | return "SuperChainInterop";
81 | }
82 |
83 | /// @dev Returns the symbol of the token.
84 | function symbol() public pure override returns (string memory) {
85 | return "SC20";
86 | }
87 |
88 | /*//////////////////////////////////////////////////////////////
89 | MODIFIERS
90 | //////////////////////////////////////////////////////////////*/
91 |
92 | modifier onlySuperchainERC20Bridge() {
93 | if (msg.sender != _superchainInteropStorage().superchainBridge) {
94 | revert SuperChainInteropNotSuperChainBridge();
95 | }
96 | _;
97 | }
98 |
99 | /*//////////////////////////////////////////////////////////////
100 | onInstall / onUninstall
101 | //////////////////////////////////////////////////////////////*/
102 |
103 | /// @dev Called by a Core into an Module during the installation of the Module.
104 | function onInstall(bytes calldata data) external {
105 | address superchainBridge = abi.decode(data, (address));
106 | _superchainInteropStorage().superchainBridge = superchainBridge;
107 | }
108 |
109 | /// @dev Called by a Core into an Module during the uninstallation of the Module.
110 | function onUninstall(bytes calldata data) external {}
111 |
112 | /*//////////////////////////////////////////////////////////////
113 | Encode install / uninstall data
114 | //////////////////////////////////////////////////////////////*/
115 |
116 | /// @dev Returns bytes encoded install params, to be sent to `onInstall` function
117 | function encodeBytesOnInstall(address superchainBridge) external pure returns (bytes memory) {
118 | return abi.encode(superchainBridge);
119 | }
120 |
121 | /// @dev Returns bytes encoded uninstall params, to be sent to `onUninstall` function
122 | function encodeBytesOnUninstall() external pure returns (bytes memory) {
123 | return "";
124 | }
125 |
126 | /*//////////////////////////////////////////////////////////////
127 | FALLBACK FUNCTIONS
128 | //////////////////////////////////////////////////////////////*/
129 |
130 | /// @notice performs a crosschain mint
131 | function crosschainMint(address _account, uint256 _amount) external onlySuperchainERC20Bridge {
132 | _mint(_account, _amount);
133 |
134 | emit CrosschainMinted(_account, _amount);
135 | }
136 |
137 | /// @notice performs a crosschain burn
138 | function crosschainBurn(address _account, uint256 _amount) external onlySuperchainERC20Bridge {
139 | _burn(_account, _amount);
140 |
141 | emit CrosschainBurnt(_account, _amount);
142 | }
143 |
144 | /// @notice returns the superchain bridge address
145 | function getSuperChainBridge() external view returns (address) {
146 | return _superchainInteropStorage().superchainBridge;
147 | }
148 |
149 | /// @notice sets the superchain bridge address
150 | function setSuperChainBridge(address _superchainBridge) external {
151 | _superchainInteropStorage().superchainBridge = _superchainBridge;
152 | }
153 |
154 | /*//////////////////////////////////////////////////////////////
155 | INTERNAL FUNCTIONS
156 | //////////////////////////////////////////////////////////////*/
157 |
158 | function _superchainInteropStorage() internal pure returns (SuperChainInteropStorage.Data storage) {
159 | return SuperChainInteropStorage.data();
160 | }
161 |
162 | }
163 |
--------------------------------------------------------------------------------
/src/module/token/metadata/BatchMetadataERC1155.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | pragma solidity ^0.8.20;
3 |
4 | import {Role} from "../../../Role.sol";
5 |
6 | import {UpdateMetadataCallbackERC1155} from "../../../callback/UpdateMetadataCallbackERC1155.sol";
7 | import {BatchMetadataERC721} from "./BatchMetadataERC721.sol";
8 |
9 | contract BatchMetadataERC1155 is BatchMetadataERC721, UpdateMetadataCallbackERC1155 {
10 |
11 | /// @notice Returns all implemented callback and module functions.
12 | function getModuleConfig() external pure virtual override returns (ModuleConfig memory config) {
13 | config.callbackFunctions = new CallbackFunction[](2);
14 | config.fallbackFunctions = new FallbackFunction[](6);
15 |
16 | config.callbackFunctions[0] = CallbackFunction(this.onTokenURI.selector);
17 | config.callbackFunctions[1] = CallbackFunction(this.updateMetadataERC1155.selector);
18 |
19 | config.fallbackFunctions[0] =
20 | FallbackFunction({selector: this.uploadMetadata.selector, permissionBits: Role._MINTER_ROLE});
21 | config.fallbackFunctions[1] =
22 | FallbackFunction({selector: this.setBaseURI.selector, permissionBits: Role._MANAGER_ROLE});
23 | config.fallbackFunctions[2] =
24 | FallbackFunction({selector: this.getAllMetadataBatches.selector, permissionBits: 0});
25 | config.fallbackFunctions[3] = FallbackFunction({selector: this.getMetadataBatch.selector, permissionBits: 0});
26 | config.fallbackFunctions[4] = FallbackFunction({selector: this.nextTokenIdToMint.selector, permissionBits: 0});
27 | config.fallbackFunctions[5] = FallbackFunction({selector: this.getBatchIndex.selector, permissionBits: 0});
28 |
29 | config.requiredInterfaces = new bytes4[](1);
30 | config.requiredInterfaces[0] = 0xd9b67a26; // ERC1155
31 |
32 | config.supportedInterfaces = new bytes4[](1);
33 | config.supportedInterfaces[0] = 0x49064906; // ERC4906.
34 | }
35 |
36 | /// @notice Callback function for updating metadata
37 | function updateMetadataERC1155(address _to, uint256 _startTokenId, uint256 _quantity, string calldata _baseURI)
38 | external
39 | payable
40 | virtual
41 | override
42 | returns (bytes memory)
43 | {
44 | if (_startTokenId < _batchMetadataStorage().nextTokenIdRangeStart) {
45 | revert BatchMetadataMetadataAlreadySet();
46 | }
47 | _setMetadata(_startTokenId, _quantity, _baseURI);
48 | }
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/src/module/token/metadata/OpenEditionMetadataERC1155.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | pragma solidity ^0.8.20;
3 |
4 | import {Role} from "../../../Role.sol";
5 | import {OpenEditionMetadataERC721} from "./OpenEditionMetadataERC721.sol";
6 |
7 | contract OpenEditionMetadataERC1155 is OpenEditionMetadataERC721 {
8 |
9 | /// @notice Returns all implemented callback and module functions.
10 | function getModuleConfig() external pure virtual override returns (ModuleConfig memory config) {
11 | config.callbackFunctions = new CallbackFunction[](1);
12 | config.fallbackFunctions = new FallbackFunction[](1);
13 |
14 | config.callbackFunctions[0] = CallbackFunction(this.onTokenURI.selector);
15 | config.fallbackFunctions[0] =
16 | FallbackFunction({selector: this.setSharedMetadata.selector, permissionBits: Role._MINTER_ROLE});
17 |
18 | config.requiredInterfaces = new bytes4[](1);
19 | config.requiredInterfaces[0] = 0xd9b67a26; // ERC1155
20 |
21 | config.supportedInterfaces = new bytes4[](1);
22 | config.supportedInterfaces[0] = 0x49064906; // ERC4906.
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/src/module/token/metadata/OpenEditionMetadataERC721.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | pragma solidity ^0.8.20;
3 |
4 | import {Module} from "../../../Module.sol";
5 | import {Role} from "../../../Role.sol";
6 |
7 | import {Base64} from "@solady/utils/Base64.sol";
8 | import {LibString} from "@solady/utils/LibString.sol";
9 |
10 | library OpenEditionMetadataStorage {
11 |
12 | /// @custom:storage-location erc7201:token.metadata.openedition
13 | bytes32 public constant OPEN_EDITION_METADATA_STORAGE_POSITION =
14 | keccak256(abi.encode(uint256(keccak256("token.metadata.openedition")) - 1)) & ~bytes32(uint256(0xff));
15 |
16 | struct Data {
17 | /// @notice shared token metadata
18 | OpenEditionMetadataERC721.SharedMetadata sharedMetadata;
19 | }
20 |
21 | function data() internal pure returns (Data storage data_) {
22 | bytes32 position = OPEN_EDITION_METADATA_STORAGE_POSITION;
23 | assembly {
24 | data_.slot := position
25 | }
26 | }
27 |
28 | }
29 |
30 | contract OpenEditionMetadataERC721 is Module {
31 |
32 | /*//////////////////////////////////////////////////////////////
33 | STRUCTS
34 | //////////////////////////////////////////////////////////////*/
35 |
36 | /**
37 | * @notice Structure for metadata shared across all tokens
38 | *
39 | * @param name Shared name of NFT in metadata
40 | * @param description Shared description of NFT in metadata
41 | * @param imageURI Shared URI of image to render for NFTs
42 | * @param animationURI Shared URI of animation to render for NFTs
43 | */
44 | struct SharedMetadata {
45 | string name;
46 | string description;
47 | string imageURI;
48 | string animationURI;
49 | }
50 |
51 | /*//////////////////////////////////////////////////////////////
52 | EVENTS
53 | //////////////////////////////////////////////////////////////*/
54 |
55 | /// @dev EIP-4906: Emitted when shared metadata is updated
56 | event BatchMetadataUpdate(uint256 _fromTokenId, uint256 _toTokenId);
57 |
58 | /// @notice Emitted when the metadata URI is queried for non-existent token.
59 | error BatchMetadataNoMetadataForTokenId();
60 |
61 | /*//////////////////////////////////////////////////////////////
62 | MODULE CONFIG
63 | //////////////////////////////////////////////////////////////*/
64 |
65 | /// @notice Returns all implemented callback and module functions.
66 | function getModuleConfig() external pure virtual override returns (ModuleConfig memory config) {
67 | config.callbackFunctions = new CallbackFunction[](1);
68 | config.fallbackFunctions = new FallbackFunction[](1);
69 |
70 | config.callbackFunctions[0] = CallbackFunction(this.onTokenURI.selector);
71 | config.fallbackFunctions[0] =
72 | FallbackFunction({selector: this.setSharedMetadata.selector, permissionBits: Role._MINTER_ROLE});
73 |
74 | config.requiredInterfaces = new bytes4[](1);
75 | config.requiredInterfaces[0] = 0x80ac58cd; // ERC721.
76 |
77 | config.supportedInterfaces = new bytes4[](1);
78 | config.supportedInterfaces[0] = 0x49064906; // ERC4906.
79 | }
80 |
81 | /*//////////////////////////////////////////////////////////////
82 | CALLBACK FUNCTIONS
83 | //////////////////////////////////////////////////////////////*/
84 |
85 | /// @notice Callback function for ERC721Metadata.tokenURI
86 | function onTokenURI(uint256 _id) external view returns (string memory) {
87 | SharedMetadata memory info = OpenEditionMetadataStorage.data().sharedMetadata;
88 |
89 | if (bytes(info.name).length == 0 && bytes(info.description).length == 0 && bytes(info.imageURI).length == 0) {
90 | revert BatchMetadataNoMetadataForTokenId();
91 | }
92 |
93 | return _createMetadataEdition({
94 | name: info.name,
95 | description: info.description,
96 | imageURI: info.imageURI,
97 | animationURI: info.animationURI,
98 | tokenOfEdition: _id
99 | });
100 | }
101 |
102 | /*//////////////////////////////////////////////////////////////
103 | FALLBACK FUNCTIONS
104 | //////////////////////////////////////////////////////////////*/
105 |
106 | /// @notice Set shared metadata for NFTs
107 | function setSharedMetadata(SharedMetadata calldata _metadata) external {
108 | OpenEditionMetadataStorage.data().sharedMetadata = _metadata;
109 |
110 | emit BatchMetadataUpdate(0, type(uint256).max);
111 | }
112 |
113 | /*//////////////////////////////////////////////////////////////
114 | INTERNAL FUNCTIONS
115 | //////////////////////////////////////////////////////////////*/
116 |
117 | /// @dev Generates open edition metadata as base64-json blob
118 | function _createMetadataEdition(
119 | string memory name,
120 | string memory description,
121 | string memory imageURI,
122 | string memory animationURI,
123 | uint256 tokenOfEdition
124 | ) internal pure returns (string memory) {
125 | string memory tokenMediaData = _tokenMediaData(imageURI, animationURI);
126 | bytes memory json = _createMetadataJSON(name, description, tokenMediaData, tokenOfEdition);
127 | return _encodeMetadataJSON(json);
128 | }
129 |
130 | /**
131 | * @param name Name of NFT in metadata
132 | * @param description Description of NFT in metadata
133 | * @param mediaData Data for media to include in json object
134 | * @param tokenOfEdition Token ID for specific token
135 | */
136 | function _createMetadataJSON(
137 | string memory name,
138 | string memory description,
139 | string memory mediaData,
140 | uint256 tokenOfEdition
141 | ) internal pure returns (bytes memory) {
142 | return abi.encodePacked(
143 | '{"name": "',
144 | name,
145 | " ",
146 | LibString.toString(tokenOfEdition),
147 | '", "',
148 | 'description": "',
149 | description,
150 | '", "',
151 | mediaData,
152 | 'properties": {"number": ',
153 | LibString.toString(tokenOfEdition),
154 | ', "name": "',
155 | name,
156 | '"}}'
157 | );
158 | }
159 |
160 | /// Encodes the argument json bytes into base64-data uri format
161 | /// @param json Raw json to base64 and turn into a data-uri
162 | function _encodeMetadataJSON(bytes memory json) internal pure returns (string memory) {
163 | return string(abi.encodePacked("data:application/json;base64,", Base64.encode(json)));
164 | }
165 |
166 | /// Generates edition metadata from storage information as base64-json blob
167 | /// Combines the media data and metadata
168 | /// @param imageUrl URL of image to render for edition
169 | /// @param animationUrl URL of animation to render for edition
170 | function _tokenMediaData(string memory imageUrl, string memory animationUrl)
171 | internal
172 | pure
173 | returns (string memory)
174 | {
175 | bool hasImage = bytes(imageUrl).length > 0;
176 | bool hasAnimation = bytes(animationUrl).length > 0;
177 | if (hasImage && hasAnimation) {
178 | return string(abi.encodePacked('image": "', imageUrl, '", "animation_url": "', animationUrl, '", "'));
179 | }
180 | if (hasImage) {
181 | return string(abi.encodePacked('image": "', imageUrl, '", "'));
182 | }
183 | if (hasAnimation) {
184 | return string(abi.encodePacked('animation_url": "', animationUrl, '", "'));
185 | }
186 |
187 | return "";
188 | }
189 |
190 | }
191 |
--------------------------------------------------------------------------------
/src/module/token/tokenId/SequentialTokenIdERC1155.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | pragma solidity ^0.8.20;
3 |
4 | import {Module} from "../../../Module.sol";
5 |
6 | import {Role} from "../../../Role.sol";
7 |
8 | import {UpdateTokenIdCallbackERC1155} from "../../../callback/UpdateTokenIdERC1155.sol";
9 | import {IInstallationCallback} from "../../../interface/IInstallationCallback.sol";
10 |
11 | library SequentialTokenIdStorage {
12 |
13 | /// @custom:storage-location erc7201:token.minting.tokenId
14 | bytes32 public constant SEQUENTIAL_TOKEN_ID_STORAGE_POSITION =
15 | keccak256(abi.encode(uint256(keccak256("token.tokenId.erc1155")) - 1)) & ~bytes32(uint256(0xff));
16 |
17 | struct Data {
18 | uint256 nextTokenId;
19 | }
20 |
21 | function data() internal pure returns (Data storage data_) {
22 | bytes32 position = SEQUENTIAL_TOKEN_ID_STORAGE_POSITION;
23 | assembly {
24 | data_.slot := position
25 | }
26 | }
27 |
28 | }
29 |
30 | contract SequentialTokenIdERC1155 is Module, UpdateTokenIdCallbackERC1155 {
31 |
32 | /*//////////////////////////////////////////////////////////////
33 | ERRORS
34 | //////////////////////////////////////////////////////////////*/
35 |
36 | /// @dev Emitted when the tokenId is invalid.
37 | error SequentialTokenIdInvalidTokenId();
38 |
39 | /*//////////////////////////////////////////////////////////////
40 | MODULE CONFIG
41 | //////////////////////////////////////////////////////////////*/
42 |
43 | /// @notice Returns all implemented callback and fallback functions.
44 | function getModuleConfig() external pure override returns (ModuleConfig memory config) {
45 | config.callbackFunctions = new CallbackFunction[](1);
46 | config.fallbackFunctions = new FallbackFunction[](1);
47 |
48 | config.callbackFunctions[0] = CallbackFunction(this.updateTokenIdERC1155.selector);
49 |
50 | config.fallbackFunctions[0] = FallbackFunction({selector: this.getNextTokenId.selector, permissionBits: 0});
51 |
52 | config.requiredInterfaces = new bytes4[](1);
53 | config.requiredInterfaces[0] = 0xd9b67a26; // ERC1155
54 |
55 | config.registerInstallationCallback = true;
56 | }
57 |
58 | /*//////////////////////////////////////////////////////////////
59 | CALLBACK FUNCTION
60 | //////////////////////////////////////////////////////////////*/
61 |
62 | function updateTokenIdERC1155(uint256 _tokenId) external payable override returns (uint256) {
63 | uint256 _nextTokenId = _tokenIdStorage().nextTokenId;
64 |
65 | if (_tokenId == type(uint256).max) {
66 | _tokenIdStorage().nextTokenId = _nextTokenId + 1;
67 |
68 | return _nextTokenId;
69 | }
70 |
71 | if (_tokenId > _nextTokenId) {
72 | revert SequentialTokenIdInvalidTokenId();
73 | }
74 |
75 | return _tokenId;
76 | }
77 |
78 | /// @dev Called by a Core into an Module during the installation of the Module.
79 | function onInstall(bytes calldata data) external {
80 | uint256 nextTokenId = abi.decode(data, (uint256));
81 | _tokenIdStorage().nextTokenId = nextTokenId;
82 | }
83 |
84 | /// @dev Called by a Core into an Module during the uninstallation of the Module.
85 | function onUninstall(bytes calldata data) external {}
86 |
87 | /*//////////////////////////////////////////////////////////////
88 | Encode install / uninstall data
89 | //////////////////////////////////////////////////////////////*/
90 |
91 | /// @dev Returns bytes encoded install params, to be sent to `onInstall` function
92 | function encodeBytesOnInstall(uint256 startTokenId) external pure returns (bytes memory) {
93 | return abi.encode(startTokenId);
94 | }
95 |
96 | /// @dev Returns bytes encoded uninstall params, to be sent to `onUninstall` function
97 | function encodeBytesOnUninstall() external pure returns (bytes memory) {
98 | return "";
99 | }
100 |
101 | /*//////////////////////////////////////////////////////////////
102 | FALLBACK FUNCTIONS
103 | //////////////////////////////////////////////////////////////*/
104 |
105 | /// @notice Returns the sale configuration for a token.
106 | function getNextTokenId() external view returns (uint256) {
107 | return _tokenIdStorage().nextTokenId;
108 | }
109 |
110 | function _tokenIdStorage() internal pure returns (SequentialTokenIdStorage.Data storage) {
111 | return SequentialTokenIdStorage.data();
112 | }
113 |
114 | }
115 |
--------------------------------------------------------------------------------
/src/module/token/transferable/CreatorTokenERC20.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache 2.0
2 | pragma solidity ^0.8.0;
3 |
4 | import {Module} from "../../../Module.sol";
5 | import {Role} from "../../../Role.sol";
6 |
7 | import {BeforeTransferCallbackERC20} from "../../../callback/BeforeTransferCallbackERC20.sol";
8 | import {OwnableRoles} from "@solady/auth/OwnableRoles.sol";
9 |
10 | import {ICreatorToken} from "@limitbreak/creator-token-standards/interfaces/ICreatorToken.sol";
11 |
12 | import {ITransferValidator} from "@limitbreak/creator-token-standards/interfaces/ITransferValidator.sol";
13 | import {ITransferValidatorSetTokenType} from
14 | "@limitbreak/creator-token-standards/interfaces/ITransferValidatorSetTokenType.sol";
15 | import {TOKEN_TYPE_ERC20} from "@limitbreak/permit-c/Constants.sol";
16 |
17 | library CreatorTokenStorage {
18 |
19 | /// @custom:storage-location erc7201:creator.token.erc20
20 | bytes32 public constant CREATORTOKEN_STORAGE_POSITION =
21 | keccak256(abi.encode(uint256(keccak256("creator.token.erc20")) - 1)) & ~bytes32(uint256(0xff));
22 |
23 | struct Data {
24 | // the addresss of the transfer validator
25 | address transferValidator;
26 | }
27 |
28 | function data() internal pure returns (Data storage data_) {
29 | bytes32 position = CREATORTOKEN_STORAGE_POSITION;
30 | assembly {
31 | data_.slot := position
32 | }
33 | }
34 |
35 | }
36 |
37 | contract CreatorTokenERC20 is Module, BeforeTransferCallbackERC20, ICreatorToken {
38 |
39 | /*//////////////////////////////////////////////////////////////
40 | CONSTANTS
41 | //////////////////////////////////////////////////////////////*/
42 |
43 | bytes32 private constant DEFAULT_ACCESS_CONTROL_ADMIN_ROLE = 0x00;
44 |
45 | /*//////////////////////////////////////////////////////////////
46 | ERRORS
47 | //////////////////////////////////////////////////////////////*/
48 |
49 | /// @notice Revert with an error if the transfer validator is not valid
50 | error InvalidTransferValidatorContract();
51 |
52 | /// @notice Revert with an error if the transfer validator is not valid
53 | error NotTransferValidator();
54 |
55 | /*//////////////////////////////////////////////////////////////
56 | EXTENSION CONFIG
57 | //////////////////////////////////////////////////////////////*/
58 |
59 | /// @notice Returns all implemented callback and extension functions.
60 | function getModuleConfig() external pure override returns (ModuleConfig memory config) {
61 | config.callbackFunctions = new CallbackFunction[](1);
62 | config.fallbackFunctions = new FallbackFunction[](4);
63 |
64 | config.callbackFunctions[0] = CallbackFunction(this.beforeTransferERC20.selector);
65 |
66 | config.fallbackFunctions[0] =
67 | FallbackFunction({selector: this.getTransferValidator.selector, permissionBits: 0});
68 | config.fallbackFunctions[1] =
69 | FallbackFunction({selector: this.getTransferValidationFunction.selector, permissionBits: 0});
70 | config.fallbackFunctions[2] =
71 | FallbackFunction({selector: this.setTransferValidator.selector, permissionBits: Role._MANAGER_ROLE});
72 | config.fallbackFunctions[3] = FallbackFunction({selector: this.hasRole.selector, permissionBits: 0});
73 | }
74 |
75 | /*//////////////////////////////////////////////////////////////
76 | CALLBACK FUNCTIONS
77 | //////////////////////////////////////////////////////////////*/
78 |
79 | /// @notice Callback function for ERC20.transferFrom/safeTransferFrom
80 | function beforeTransferERC20(address from, address to, uint256 amount)
81 | external
82 | virtual
83 | override
84 | returns (bytes memory)
85 | {
86 | address transferValidator = getTransferValidator();
87 | if (transferValidator != address(0)) {
88 | ITransferValidator(transferValidator).validateTransfer(msg.sender, from, to, 0, amount);
89 | }
90 | }
91 |
92 | /*//////////////////////////////////////////////////////////////
93 | FALLBACK FUNCTIONS
94 | //////////////////////////////////////////////////////////////*/
95 |
96 | /// @notice Returns the transfer validator contract address for this token contract.
97 | function getTransferValidator() public view returns (address validator) {
98 | validator = _creatorTokenStorage().transferValidator;
99 | }
100 |
101 | /**
102 | * @notice Returns the function selector for the transfer validator's validation function to be called
103 | * @notice for transaction simulation.
104 | */
105 | function getTransferValidationFunction() external pure returns (bytes4 functionSignature, bool isViewFunction) {
106 | functionSignature = bytes4(keccak256("validateTransfer(address,address,address,uint256,uint256)"));
107 | isViewFunction = true;
108 | }
109 |
110 | function setTransferValidator(address validator) external {
111 | _setTransferValidator(validator);
112 | }
113 |
114 | function hasRole(bytes32 role, address account) external view returns (bool) {
115 | if (msg.sender != _creatorTokenStorage().transferValidator) {
116 | revert NotTransferValidator();
117 | }
118 | if (role == DEFAULT_ACCESS_CONTROL_ADMIN_ROLE) {
119 | return OwnableRoles(address(this)).hasAllRoles(account, Role._MANAGER_ROLE);
120 | }
121 | return OwnableRoles(address(this)).hasAllRoles(account, uint256(role));
122 | }
123 |
124 | /*//////////////////////////////////////////////////////////////
125 | INTERNAL FUNCTIONS
126 | //////////////////////////////////////////////////////////////*/
127 |
128 | function _setTransferValidator(address validator) internal {
129 | bool isValidTransferValidator = validator.code.length > 0;
130 |
131 | if (validator != address(0) && !isValidTransferValidator) {
132 | revert InvalidTransferValidatorContract();
133 | }
134 |
135 | emit TransferValidatorUpdated(address(getTransferValidator()), validator);
136 |
137 | _creatorTokenStorage().transferValidator = validator;
138 | _registerTokenType(validator);
139 | }
140 |
141 | function _registerTokenType(address validator) internal {
142 | if (validator != address(0)) {
143 | uint256 validatorCodeSize;
144 | assembly {
145 | validatorCodeSize := extcodesize(validator)
146 | }
147 | if (validatorCodeSize > 0) {
148 | try ITransferValidatorSetTokenType(validator).setTokenTypeOfCollection(address(this), _tokenType()) {}
149 | catch {}
150 | }
151 | }
152 | }
153 |
154 | function _tokenType() internal pure virtual returns (uint16 tokenType) {
155 | return uint16(TOKEN_TYPE_ERC20);
156 | }
157 |
158 | function _creatorTokenStorage() internal pure returns (CreatorTokenStorage.Data storage) {
159 | return CreatorTokenStorage.data();
160 | }
161 |
162 | }
163 |
--------------------------------------------------------------------------------
/src/module/token/transferable/TransferableERC1155.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | pragma solidity ^0.8.20;
3 |
4 | import {Module} from "../../../Module.sol";
5 | import {Role} from "../../../Role.sol";
6 |
7 | import {BeforeBatchTransferCallbackERC1155} from "../../../callback/BeforeBatchTransferCallbackERC1155.sol";
8 | import {BeforeTransferCallbackERC1155} from "../../../callback/BeforeTransferCallbackERC1155.sol";
9 |
10 | library TransferableStorage {
11 |
12 | /// @custom:storage-location erc7201:token.transferable
13 | bytes32 public constant TRANSFERABLE_STORAGE_POSITION =
14 | keccak256(abi.encode(uint256(keccak256("token.transferable")) - 1)) & ~bytes32(uint256(0xff));
15 |
16 | struct Data {
17 | // whether transfers are enabled
18 | bool transferEnabled;
19 | // from/to/operator address => bool, whether transfer is enabled
20 | mapping(address => bool) transferEnabledFor;
21 | }
22 |
23 | function data() internal pure returns (Data storage data_) {
24 | bytes32 position = TRANSFERABLE_STORAGE_POSITION;
25 | assembly {
26 | data_.slot := position
27 | }
28 | }
29 |
30 | }
31 |
32 | contract TransferableERC1155 is Module, BeforeTransferCallbackERC1155, BeforeBatchTransferCallbackERC1155 {
33 |
34 | /*//////////////////////////////////////////////////////////////
35 | ERRORS
36 | //////////////////////////////////////////////////////////////*/
37 |
38 | /// @notice Emitted on attempt to transfer a token when transfers are disabled.
39 | error TransferDisabled();
40 |
41 | /*//////////////////////////////////////////////////////////////
42 | MODULE CONFIG
43 | //////////////////////////////////////////////////////////////*/
44 |
45 | /// @notice Returns all implemented callback and module functions.
46 | function getModuleConfig() external pure override returns (ModuleConfig memory config) {
47 | config.callbackFunctions = new CallbackFunction[](2);
48 | config.fallbackFunctions = new FallbackFunction[](4);
49 |
50 | config.callbackFunctions[0] = CallbackFunction(this.beforeTransferERC1155.selector);
51 | config.callbackFunctions[1] = CallbackFunction(this.beforeBatchTransferERC1155.selector);
52 |
53 | config.fallbackFunctions[0] = FallbackFunction({selector: this.isTransferEnabled.selector, permissionBits: 0});
54 | config.fallbackFunctions[1] =
55 | FallbackFunction({selector: this.isTransferEnabledFor.selector, permissionBits: 0});
56 | config.fallbackFunctions[2] =
57 | FallbackFunction({selector: this.setTransferable.selector, permissionBits: Role._MANAGER_ROLE});
58 | config.fallbackFunctions[3] =
59 | FallbackFunction({selector: this.setTransferableFor.selector, permissionBits: Role._MANAGER_ROLE});
60 |
61 | config.requiredInterfaces = new bytes4[](1);
62 | config.requiredInterfaces[0] = 0xd9b67a26; // ERC1155
63 | }
64 |
65 | /*//////////////////////////////////////////////////////////////
66 | CALLBACK FUNCTIONS
67 | //////////////////////////////////////////////////////////////*/
68 |
69 | /// @notice Callback function for ERC1155.safeTransferFrom
70 | function beforeTransferERC1155(address from, address to, uint256, uint256)
71 | external
72 | virtual
73 | override
74 | returns (bytes memory)
75 | {
76 | TransferableStorage.Data storage data = _transferableStorage();
77 | bool isOperatorAllowed =
78 | data.transferEnabledFor[msg.sender] || data.transferEnabledFor[from] || data.transferEnabledFor[to];
79 |
80 | if (!isOperatorAllowed && !data.transferEnabled) {
81 | revert TransferDisabled();
82 | }
83 | }
84 |
85 | /// @notice Callback function for ERC1155.safeBatchTransferFrom
86 | function beforeBatchTransferERC1155(address from, address to, uint256[] calldata, uint256[] calldata)
87 | external
88 | virtual
89 | override
90 | returns (bytes memory)
91 | {
92 | TransferableStorage.Data storage data = _transferableStorage();
93 | bool isOperatorAllowed =
94 | data.transferEnabledFor[msg.sender] || data.transferEnabledFor[from] || data.transferEnabledFor[to];
95 |
96 | if (!isOperatorAllowed && !data.transferEnabled) {
97 | revert TransferDisabled();
98 | }
99 | }
100 |
101 | /*//////////////////////////////////////////////////////////////
102 | FALLBACK FUNCTIONS
103 | //////////////////////////////////////////////////////////////*/
104 |
105 | /// @notice Returns whether transfers is enabled for the token.
106 | function isTransferEnabled() external view returns (bool) {
107 | return _transferableStorage().transferEnabled;
108 | }
109 |
110 | /// @notice Returns whether transfers is enabled for the target for the token.
111 | function isTransferEnabledFor(address target) external view returns (bool) {
112 | return _transferableStorage().transferEnabledFor[target];
113 | }
114 |
115 | /// @notice Set transferability for a token.
116 | function setTransferable(bool enableTransfer) external {
117 | _transferableStorage().transferEnabled = enableTransfer;
118 | }
119 |
120 | /// @notice Set transferability for an operator for a token.
121 | function setTransferableFor(address target, bool enableTransfer) external {
122 | _transferableStorage().transferEnabledFor[target] = enableTransfer;
123 | }
124 |
125 | /*//////////////////////////////////////////////////////////////
126 | INTERNAL FUNCTIONS
127 | //////////////////////////////////////////////////////////////*/
128 |
129 | function _transferableStorage() internal pure returns (TransferableStorage.Data storage) {
130 | return TransferableStorage.data();
131 | }
132 |
133 | }
134 |
--------------------------------------------------------------------------------
/src/module/token/transferable/TransferableERC20.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | pragma solidity ^0.8.20;
3 |
4 | import {Module} from "../../../Module.sol";
5 | import {Role} from "../../../Role.sol";
6 | import {BeforeTransferCallbackERC20} from "../../../callback/BeforeTransferCallbackERC20.sol";
7 |
8 | library TransferableStorage {
9 |
10 | /// @custom:storage-location erc7201:token.transferable
11 | bytes32 public constant TRANSFERABLE_STORAGE_POSITION =
12 | keccak256(abi.encode(uint256(keccak256("token.transferable")) - 1)) & ~bytes32(uint256(0xff));
13 |
14 | struct Data {
15 | // whether transfers are enabled
16 | bool transferEnabled;
17 | // from/to/operator address => bool, whether transfer is enabled
18 | mapping(address => bool) transferEnabledFor;
19 | }
20 |
21 | function data() internal pure returns (Data storage data_) {
22 | bytes32 position = TRANSFERABLE_STORAGE_POSITION;
23 | assembly {
24 | data_.slot := position
25 | }
26 | }
27 |
28 | }
29 |
30 | contract TransferableERC20 is Module, BeforeTransferCallbackERC20 {
31 |
32 | /*//////////////////////////////////////////////////////////////
33 | ERRORS
34 | //////////////////////////////////////////////////////////////*/
35 |
36 | /// @notice Emitted on attempt to transfer a token when transfers are disabled.
37 | error TransferDisabled();
38 |
39 | /*//////////////////////////////////////////////////////////////
40 | MODULE CONFIG
41 | //////////////////////////////////////////////////////////////*/
42 |
43 | /// @notice Returns all implemented callback and module functions.
44 | function getModuleConfig() external pure override returns (ModuleConfig memory config) {
45 | config.callbackFunctions = new CallbackFunction[](1);
46 | config.fallbackFunctions = new FallbackFunction[](4);
47 |
48 | config.callbackFunctions[0] = CallbackFunction(this.beforeTransferERC20.selector);
49 |
50 | config.fallbackFunctions[0] = FallbackFunction({selector: this.isTransferEnabled.selector, permissionBits: 0});
51 | config.fallbackFunctions[1] =
52 | FallbackFunction({selector: this.isTransferEnabledFor.selector, permissionBits: 0});
53 | config.fallbackFunctions[2] =
54 | FallbackFunction({selector: this.setTransferable.selector, permissionBits: Role._MANAGER_ROLE});
55 | config.fallbackFunctions[3] =
56 | FallbackFunction({selector: this.setTransferableFor.selector, permissionBits: Role._MANAGER_ROLE});
57 | }
58 |
59 | /*//////////////////////////////////////////////////////////////
60 | CALLBACK FUNCTIONS
61 | //////////////////////////////////////////////////////////////*/
62 |
63 | /// @notice Callback function for ERC20.transfer
64 | function beforeTransferERC20(address from, address to, uint256) external virtual override returns (bytes memory) {
65 | TransferableStorage.Data storage data = _transferableStorage();
66 | bool isOperatorAllowed =
67 | data.transferEnabledFor[msg.sender] || data.transferEnabledFor[from] || data.transferEnabledFor[to];
68 |
69 | if (!isOperatorAllowed && !data.transferEnabled) {
70 | revert TransferDisabled();
71 | }
72 | }
73 |
74 | /*//////////////////////////////////////////////////////////////
75 | FALLBACK FUNCTIONS
76 | //////////////////////////////////////////////////////////////*/
77 |
78 | /// @notice Returns whether transfers is enabled for the token.
79 | function isTransferEnabled() external view returns (bool) {
80 | return _transferableStorage().transferEnabled;
81 | }
82 |
83 | /// @notice Returns whether transfers is enabled for the target address for the token.
84 | function isTransferEnabledFor(address target) external view returns (bool) {
85 | return _transferableStorage().transferEnabledFor[target];
86 | }
87 |
88 | /// @notice Set transferability for a token.
89 | function setTransferable(bool enableTransfer) external {
90 | _transferableStorage().transferEnabled = enableTransfer;
91 | }
92 |
93 | /// @notice Set transferability for an address for a token.
94 | function setTransferableFor(address target, bool enableTransfer) external {
95 | _transferableStorage().transferEnabledFor[target] = enableTransfer;
96 | }
97 |
98 | /*//////////////////////////////////////////////////////////////
99 | INTERNAL FUNCTIONS
100 | //////////////////////////////////////////////////////////////*/
101 |
102 | function _transferableStorage() internal pure returns (TransferableStorage.Data storage) {
103 | return TransferableStorage.data();
104 | }
105 |
106 | }
107 |
--------------------------------------------------------------------------------
/src/module/token/transferable/TransferableERC721.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | pragma solidity ^0.8.20;
3 |
4 | import {Module} from "../../../Module.sol";
5 | import {Role} from "../../../Role.sol";
6 | import {BeforeTransferCallbackERC721} from "../../../callback/BeforeTransferCallbackERC721.sol";
7 |
8 | library TransferableStorage {
9 |
10 | /// @custom:storage-location erc7201:token.transferable
11 | bytes32 public constant TRANSFERABLE_STORAGE_POSITION =
12 | keccak256(abi.encode(uint256(keccak256("token.transferable")) - 1)) & ~bytes32(uint256(0xff));
13 |
14 | struct Data {
15 | // whether transfer is enabled
16 | bool transferEnabled;
17 | // from/to/operator address => bool, whether transfer is enabled
18 | mapping(address => bool) transferEnabledFor;
19 | }
20 |
21 | function data() internal pure returns (Data storage data_) {
22 | bytes32 position = TRANSFERABLE_STORAGE_POSITION;
23 | assembly {
24 | data_.slot := position
25 | }
26 | }
27 |
28 | }
29 |
30 | contract TransferableERC721 is Module, BeforeTransferCallbackERC721 {
31 |
32 | /*//////////////////////////////////////////////////////////////
33 | ERRORS
34 | //////////////////////////////////////////////////////////////*/
35 |
36 | /// @notice Emitted on attempt to transfer a token when transfers are disabled.
37 | error TransferDisabled();
38 |
39 | /*//////////////////////////////////////////////////////////////
40 | MODULE CONFIG
41 | //////////////////////////////////////////////////////////////*/
42 |
43 | /// @notice Returns all implemented callback and module functions.
44 | function getModuleConfig() external pure override returns (ModuleConfig memory config) {
45 | config.callbackFunctions = new CallbackFunction[](1);
46 | config.fallbackFunctions = new FallbackFunction[](4);
47 |
48 | config.callbackFunctions[0] = CallbackFunction(this.beforeTransferERC721.selector);
49 |
50 | config.fallbackFunctions[0] = FallbackFunction({selector: this.isTransferEnabled.selector, permissionBits: 0});
51 | config.fallbackFunctions[1] =
52 | FallbackFunction({selector: this.isTransferEnabledFor.selector, permissionBits: 0});
53 | config.fallbackFunctions[2] =
54 | FallbackFunction({selector: this.setTransferable.selector, permissionBits: Role._MANAGER_ROLE});
55 | config.fallbackFunctions[3] =
56 | FallbackFunction({selector: this.setTransferableFor.selector, permissionBits: Role._MANAGER_ROLE});
57 |
58 | config.requiredInterfaces = new bytes4[](1);
59 | config.requiredInterfaces[0] = 0x80ac58cd; // ERC721.
60 | }
61 |
62 | /*//////////////////////////////////////////////////////////////
63 | CALLBACK FUNCTIONS
64 | //////////////////////////////////////////////////////////////*/
65 |
66 | /// @notice Callback function for ERC721.transferFrom/safeTransferFrom
67 | function beforeTransferERC721(address from, address to, uint256) external virtual override returns (bytes memory) {
68 | TransferableStorage.Data storage data = _transferableStorage();
69 | bool isOperatorAllowed =
70 | data.transferEnabledFor[msg.sender] || data.transferEnabledFor[from] || data.transferEnabledFor[to];
71 |
72 | if (!isOperatorAllowed && !data.transferEnabled) {
73 | revert TransferDisabled();
74 | }
75 | }
76 |
77 | /*//////////////////////////////////////////////////////////////
78 | FALLBACK FUNCTIONS
79 | //////////////////////////////////////////////////////////////*/
80 |
81 | /// @notice Returns whether transfers is enabled for the token.
82 | function isTransferEnabled() external view returns (bool) {
83 | return _transferableStorage().transferEnabled;
84 | }
85 |
86 | /// @notice Returns whether transfers is enabled for the target address for the token.
87 | function isTransferEnabledFor(address target) external view returns (bool) {
88 | return _transferableStorage().transferEnabledFor[target];
89 | }
90 |
91 | /// @notice Set transferability for a token.
92 | function setTransferable(bool enableTransfer) external {
93 | _transferableStorage().transferEnabled = enableTransfer;
94 | }
95 |
96 | /// @notice Set transferability for an address for a token.
97 | function setTransferableFor(address target, bool enableTransfer) external {
98 | _transferableStorage().transferEnabledFor[target] = enableTransfer;
99 | }
100 |
101 | /*//////////////////////////////////////////////////////////////
102 | INTERNAL FUNCTIONS
103 | //////////////////////////////////////////////////////////////*/
104 |
105 | function _transferableStorage() internal pure returns (TransferableStorage.Data storage) {
106 | return TransferableStorage.data();
107 | }
108 |
109 | }
110 |
--------------------------------------------------------------------------------
/test/Interface.t.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache 2.0
2 | pragma solidity ^0.8.0;
3 |
4 | // Test utils
5 | import {Test} from "forge-std/Test.sol";
6 |
7 | import {IERC7572} from "src/interface/IERC7572.sol";
8 |
9 | contract InterfaceTest is Test {
10 |
11 | event InterfaceId(bytes4 id);
12 |
13 | function testInterface() public {
14 | emit InterfaceId(type(IERC7572).interfaceId);
15 | }
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/test/TWCloneFactory.t.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | pragma solidity ^0.8.20;
3 |
4 | import "../src/TWCloneFactory.sol";
5 | import {LibClone} from "@solady/utils/LibClone.sol";
6 | import "forge-std/Test.sol";
7 |
8 | contract MockImplementation {
9 |
10 | uint256 public value;
11 |
12 | function initialize(uint256 _value) public {
13 | value = _value;
14 | }
15 |
16 | }
17 |
18 | contract TWCloneFactoryTest is Test {
19 |
20 | TWCloneFactory factory;
21 | MockImplementation implementation;
22 |
23 | uint256 chainId1 = 1;
24 | uint256 chainId2 = 2;
25 |
26 | function setUp() public {
27 | factory = new TWCloneFactory();
28 | implementation = new MockImplementation();
29 | }
30 |
31 | // Test case 1: Cross chain deployment on - same address on two chains
32 | function testCrossChainDeploymentOnSameAddress() public {
33 | // Set up the salt with allowCrossChainDeployment = true, encodeDataIntoSalt = false
34 | // salt[0] == hex"01", salt[1] == hex"00"
35 | bytes32 salt = bytes32(abi.encodePacked(hex"01", hex"00", bytes30(0)));
36 | bytes memory data = "";
37 |
38 | // Save the current state
39 | uint256 snapshotId = vm.snapshot();
40 |
41 | // Set chain ID to chainId1 and deploy
42 | vm.chainId(chainId1);
43 | address proxy1 = factory.deployProxyByImplementation(address(implementation), data, salt);
44 |
45 | // Revert to the snapshot to reset the state
46 | vm.revertTo(snapshotId);
47 |
48 | // Set chain ID to chainId2 and deploy
49 | vm.chainId(chainId2);
50 | address proxy2 = factory.deployProxyByImplementation(address(implementation), data, salt);
51 |
52 | // Check that the proxies are the same
53 | assertEq(proxy1, proxy2, "Proxies should be the same on different chains when cross chain deployment is on");
54 | }
55 |
56 | // Test case 2: Cross chain deployment off - different address on two chains
57 | function testCrossChainDeploymentOffDifferentAddress() public {
58 | // Set up the salt with allowCrossChainDeployment = false, encodeDataIntoSalt = false
59 | // salt[0] == hex"00", salt[1] == hex"00"
60 | bytes32 salt = bytes32(abi.encodePacked(hex"00", hex"00", bytes30(0)));
61 | bytes memory data = "";
62 |
63 | // Save the current state
64 | uint256 snapshotId = vm.snapshot();
65 |
66 | // Set chain ID to chainId1 and deploy
67 | vm.chainId(chainId1);
68 | address proxy1 = factory.deployProxyByImplementation(address(implementation), data, salt);
69 |
70 | // Revert to the snapshot to reset the state
71 | vm.revertTo(snapshotId);
72 |
73 | // Set chain ID to chainId2 and deploy
74 | vm.chainId(chainId2);
75 | address proxy2 = factory.deployProxyByImplementation(address(implementation), data, salt);
76 |
77 | // Check that the proxies are different
78 | assertNotEq(
79 | proxy1, proxy2, "Proxies should be different on different chains when cross chain deployment is off"
80 | );
81 | }
82 |
83 | // Test case 3: Not encodeDataIntoSalt - same address with different init data
84 | function testNotEncodeDataIntoSaltSameAddress() public {
85 | // Set up the salt with allowCrossChainDeployment = true, encodeDataIntoSalt = false
86 | // salt[0] == hex"01", salt[1] == hex"00"
87 | bytes32 salt = bytes32(abi.encodePacked(hex"01", hex"00", bytes30(0)));
88 |
89 | // Save the current state
90 | uint256 snapshotId = vm.snapshot();
91 |
92 | // Deploy with data1
93 | vm.chainId(chainId1);
94 | bytes memory data1 = abi.encodeWithSignature("initialize(uint256)", 42);
95 | address proxy1 = factory.deployProxyByImplementation(address(implementation), data1, salt);
96 |
97 | // Revert to the snapshot to reset the state
98 | vm.revertTo(snapshotId);
99 |
100 | // Deploy with data2
101 | vm.chainId(chainId2);
102 | bytes memory data2 = abi.encodeWithSignature("initialize(uint256)", 100);
103 | address proxy2 = factory.deployProxyByImplementation(address(implementation), data2, salt);
104 |
105 | // Check that the proxies are the same
106 | assertEq(proxy1, proxy2, "Proxies should be the same when encodeDataIntoSalt is off");
107 | }
108 |
109 | // Test case 4: Encode Data into salt - different address with different init data
110 | function testEncodeDataIntoSaltDifferentAddress() public {
111 | // Set up the salt with allowCrossChainDeployment = true, encodeDataIntoSalt = true
112 | // salt[0] == hex"01", salt[1] == hex"01"
113 | bytes32 salt = bytes32(abi.encodePacked(hex"01", hex"01", bytes30(0)));
114 |
115 | // Deploy with data1
116 | bytes memory data1 = abi.encodeWithSignature("initialize(uint256)", 42);
117 | address proxy1 = factory.deployProxyByImplementation(address(implementation), data1, salt);
118 |
119 | // Deploy with data2
120 | bytes memory data2 = abi.encodeWithSignature("initialize(uint256)", 100);
121 | address proxy2 = factory.deployProxyByImplementation(address(implementation), data2, salt);
122 |
123 | // Check that the proxies are different
124 | assertNotEq(proxy1, proxy2, "Proxies should be different when encodeDataIntoSalt is on");
125 |
126 | // Verify that both initializations took effect independently
127 | MockImplementation proxyImpl1 = MockImplementation(proxy1);
128 | uint256 value1 = proxyImpl1.value();
129 | assertEq(value1, 42, "Value should be initialized to 42");
130 |
131 | MockImplementation proxyImpl2 = MockImplementation(proxy2);
132 | uint256 value2 = proxyImpl2.value();
133 | assertEq(value2, 100, "Value should be initialized to 100");
134 | }
135 |
136 | }
137 |
--------------------------------------------------------------------------------
/test/mock/MockMintFeeManager.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | pragma solidity ^0.8.20;
3 |
4 | contract MockMintFeeManager {
5 |
6 | function calculatePlatformFeeAndRecipient(uint256 _price) external view returns (uint256, address) {
7 | return ((_price * 100) / 10_000, address(0x3));
8 | }
9 |
10 | function getPlatformFeeReceipient() external view returns (address) {
11 | return address(0x3);
12 | }
13 |
14 | function getPrimarySaleAndPlatformFeeAmount(uint256 _price) external view returns (uint256, uint256) {
15 | uint256 platformFeeAmount = (_price * 100) / 10_000;
16 | uint256 primarySaleAmount = _price - platformFeeAmount;
17 | return (primarySaleAmount, platformFeeAmount);
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/test/module/MintFeeManager.t.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | pragma solidity ^0.8.20;
3 |
4 | import "lib/forge-std/src/console.sol";
5 |
6 | import {Test} from "forge-std/Test.sol";
7 | import {LibClone} from "solady/utils/LibClone.sol";
8 | import {Role} from "src/Role.sol";
9 |
10 | import {MintFeeManagerCore} from "src/core/MintFeeManagerCore.sol";
11 | import {MintFeeManagerModule} from "src/module/MintFeeManagerModule.sol";
12 |
13 | contract MintFeeManagerTest is Test {
14 |
15 | address public mintFeeManagerCore;
16 | MintFeeManagerModule public mintFeeManagerModule;
17 |
18 | address public owner = address(0x123);
19 | address public feeRecipient = address(0x456);
20 | uint256 public defaultMintFee = 100;
21 | address public unauthorizedActor = address(0x3);
22 |
23 | address public contract1 = address(0x1);
24 | address public contract2 = address(0x2);
25 |
26 | event MintFeeUpdated(address indexed contractAddress, uint256 mintFee);
27 | event DefaultMintFeeUpdated(uint256 mintFee);
28 |
29 | function setUp() public {
30 | // Deploy the contract
31 | address[] memory modules;
32 | bytes[] memory moduleData;
33 | mintFeeManagerCore = address(new MintFeeManagerCore(owner, modules, moduleData));
34 | mintFeeManagerModule = new MintFeeManagerModule();
35 |
36 | bytes memory initializeData = mintFeeManagerModule.encodeBytesOnInstall(feeRecipient, defaultMintFee);
37 | vm.prank(owner);
38 | MintFeeManagerCore(payable(mintFeeManagerCore)).installModule(address(mintFeeManagerModule), initializeData);
39 |
40 | vm.prank(owner);
41 | MintFeeManagerCore(payable(mintFeeManagerCore)).grantRoles(owner, Role._MANAGER_ROLE);
42 | }
43 |
44 | /*///////////////////////////////////////////////////////////////
45 | Unit tests: `setfeeRecipient`
46 | //////////////////////////////////////////////////////////////*/
47 |
48 | function test_state_setfeeRecipient() public {
49 | vm.prank(owner);
50 | MintFeeManagerModule(mintFeeManagerCore).setfeeRecipient(address(0x456));
51 |
52 | assertEq(MintFeeManagerModule(mintFeeManagerCore).getfeeRecipient(), address(0x456));
53 | }
54 |
55 | function test_revert_setfeeRecipient_unauthorizedCaller() public {
56 | vm.prank(unauthorizedActor);
57 | vm.expectRevert(abi.encodeWithSelector(0x82b42900)); // Unauthorized()
58 | MintFeeManagerModule(mintFeeManagerCore).setfeeRecipient(address(0x456));
59 | }
60 |
61 | /*///////////////////////////////////////////////////////////////
62 | Unit tests: `updateMintFee`
63 | //////////////////////////////////////////////////////////////*/
64 |
65 | function test_state_updateMintFee() public {
66 | vm.startPrank(owner);
67 | vm.expectEmit(true, true, true, true);
68 | emit MintFeeUpdated(contract1, 500);
69 | MintFeeManagerModule(mintFeeManagerCore).updateMintFee(contract1, 500);
70 |
71 | vm.expectEmit(true, true, true, true);
72 | emit MintFeeUpdated(contract2, type(uint256).max);
73 | MintFeeManagerModule(mintFeeManagerCore).updateMintFee(contract2, type(uint256).max);
74 | vm.stopPrank();
75 |
76 | assertEq(MintFeeManagerModule(mintFeeManagerCore).getMintFees(contract1), 500);
77 | assertEq(MintFeeManagerModule(mintFeeManagerCore).getMintFees(contract2), type(uint256).max);
78 | }
79 |
80 | function test_revert_updateMintFee_unauthorizedCaller() public {
81 | vm.prank(unauthorizedActor);
82 | vm.expectRevert(abi.encodeWithSelector(0x82b42900)); // Unauthorized()
83 | MintFeeManagerModule(mintFeeManagerCore).updateMintFee(contract1, 100);
84 | }
85 |
86 | function test_revert_updateMintFee_invalidMintFee() public {
87 | vm.prank(owner);
88 | vm.expectRevert(abi.encodeWithSelector(MintFeeManagerModule.MintFeeExceedsMaxBps.selector));
89 | MintFeeManagerModule(mintFeeManagerCore).updateMintFee(contract1, 10_001);
90 | }
91 |
92 | /*///////////////////////////////////////////////////////////////
93 | Unit tests: `setDefaultMintFee`
94 | //////////////////////////////////////////////////////////////*/
95 |
96 | function test_state_setDefaultMintFee() public {
97 | vm.prank(owner);
98 | vm.expectEmit(true, true, true, true);
99 | emit DefaultMintFeeUpdated(500);
100 | MintFeeManagerModule(mintFeeManagerCore).setDefaultMintFee(500);
101 |
102 | assertEq(MintFeeManagerModule(mintFeeManagerCore).getDefaultMintFee(), 500);
103 | }
104 |
105 | function test_revert_setDefaultMintFee_unauthorizedCaller() public {
106 | vm.prank(unauthorizedActor);
107 | vm.expectRevert(abi.encodeWithSelector(0x82b42900)); // Unauthorized()
108 | MintFeeManagerModule(mintFeeManagerCore).setDefaultMintFee(100);
109 | }
110 |
111 | function test_revert_setDefaultMintFee_invalidMintFee() public {
112 | vm.prank(owner);
113 | vm.expectRevert(abi.encodeWithSelector(MintFeeManagerModule.MintFeeExceedsMaxBps.selector));
114 | MintFeeManagerModule(mintFeeManagerCore).setDefaultMintFee(10_001);
115 | }
116 |
117 | /*///////////////////////////////////////////////////////////////
118 | Unit tests: `calculatePlatformFeeAndRecipient`
119 | //////////////////////////////////////////////////////////////*/
120 |
121 | function test_state_calculatePlatformFeeAndRecipient() public {
122 | vm.prank(owner);
123 | MintFeeManagerModule(mintFeeManagerCore).updateMintFee(contract1, 500);
124 |
125 | vm.prank(contract1);
126 | (uint256 mintFee, address _feeRecipient) =
127 | MintFeeManagerModule(mintFeeManagerCore).calculatePlatformFeeAndRecipient(100);
128 |
129 | assertEq(mintFee, 5);
130 | assertEq(_feeRecipient, feeRecipient);
131 | }
132 |
133 | function test_state_calculatePlatformFeeAndRecipient_defaultMintFee() public {
134 | vm.prank(contract1);
135 | (uint256 mintFee, address _feeRecipient) =
136 | MintFeeManagerModule(mintFeeManagerCore).calculatePlatformFeeAndRecipient(100);
137 |
138 | assertEq(mintFee, 1);
139 | assertEq(_feeRecipient, feeRecipient);
140 | }
141 |
142 | function test_state_calculatePlatformFeeAndRecipient_zeroMintFee() public {
143 | vm.prank(owner);
144 | MintFeeManagerModule(mintFeeManagerCore).updateMintFee(contract1, type(uint256).max);
145 |
146 | vm.prank(contract1);
147 | (uint256 mintFee, address _feeRecipient) =
148 | MintFeeManagerModule(mintFeeManagerCore).calculatePlatformFeeAndRecipient(100);
149 |
150 | assertEq(mintFee, 0);
151 | assertEq(_feeRecipient, feeRecipient);
152 | }
153 |
154 | }
155 |
--------------------------------------------------------------------------------
/test/module/SplitWallet.t.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: UNLICENSED
2 | pragma solidity ^0.8.0;
3 |
4 | import "lib/forge-std/src/console.sol";
5 |
6 | import {Test, Vm} from "forge-std/Test.sol";
7 |
8 | // Target contracts
9 | import {Core} from "src/Core.sol";
10 | import {Module} from "src/Module.sol";
11 | import {Role} from "src/Role.sol";
12 | import {SplitFeesCore} from "src/core/SplitFeesCore.sol";
13 |
14 | import {SplitWallet} from "src/core/SplitWallet.sol";
15 | import {SplitFeesModule} from "src/module/SplitFeesModule.sol";
16 |
17 | import {ERC20} from "@solady/tokens/ERC20.sol";
18 | import {ISplitWallet} from "src/interface/ISplitWallet.sol";
19 | import {Split} from "src/libraries/Split.sol";
20 |
21 | contract MockCurrency is ERC20 {
22 |
23 | function mintTo(address _recipient, uint256 _amount) public {
24 | _mint(_recipient, _amount);
25 | }
26 |
27 | /// @dev Returns the name of the token.
28 | function name() public view virtual override returns (string memory) {
29 | return "MockCurrency";
30 | }
31 |
32 | /// @dev Returns the symbol of the token.
33 | function symbol() public view virtual override returns (string memory) {
34 | return "MOCK";
35 | }
36 |
37 | }
38 |
39 | contract SplitFeesModuleTest is Test {
40 |
41 | SplitFeesCore public splitFeesCore;
42 | SplitFeesModule public splitFeesModule;
43 |
44 | MockCurrency public token;
45 |
46 | address public splitWallet;
47 |
48 | address public owner = address(0x1);
49 | address public recipient1 = address(0x4);
50 | address public recipient2 = address(0x5);
51 |
52 | // Constants
53 | address private constant NATIVE_TOKEN_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
54 |
55 | function setUp() public {
56 | // Instantiate SplitFeesCore and install SplitFeesModule
57 | address[] memory modules;
58 | bytes[] memory moduleData;
59 | splitFeesCore = new SplitFeesCore(owner, modules, moduleData);
60 | splitFeesModule = new SplitFeesModule();
61 |
62 | vm.prank(owner);
63 | splitFeesCore.installModule(address(splitFeesModule), "");
64 |
65 | // Create a split using the module
66 | (address[] memory recipients, uint256[] memory allocations) = getRecipientsAndAllocations();
67 |
68 | vm.recordLogs();
69 | vm.prank(owner);
70 | SplitFeesModule(address(splitFeesCore)).createSplit(recipients, allocations, owner);
71 |
72 | // Retrieve the splitWallet address from the event logs
73 | Vm.Log[] memory entries = vm.getRecordedLogs();
74 |
75 | bytes32 SplitCreatedTopic = keccak256("SplitCreated(address,address[],uint256[],address)");
76 |
77 | for (uint256 i = 0; i < entries.length; i++) {
78 | Vm.Log memory log = entries[i];
79 | if (log.topics[0] == SplitCreatedTopic) {
80 | // This is the SplitCreated event
81 | splitWallet = address(uint160(uint256(log.topics[1])));
82 | break;
83 | }
84 | }
85 |
86 | token = new MockCurrency();
87 | }
88 |
89 | function getRecipientsAndAllocations() public view returns (address[] memory, uint256[] memory) {
90 | address[] memory recipients = new address[](2);
91 | uint256[] memory allocations = new uint256[](2);
92 | recipients[0] = recipient1;
93 | recipients[1] = recipient2;
94 | allocations[0] = 50;
95 | allocations[1] = 50;
96 |
97 | return (recipients, allocations);
98 | }
99 |
100 | /*//////////////////////////////////////////////////////////////
101 | Unit tests: distribute and withdraw
102 | //////////////////////////////////////////////////////////////*/
103 |
104 | function test_distribute_and_withdraw_ETH() public {
105 | // Deposit ETH to splitWallet
106 | vm.deal(splitWallet, 1 ether);
107 |
108 | // Check initial balances
109 | uint256 initialBalance1 = recipient1.balance;
110 | uint256 initialBalance2 = recipient2.balance;
111 |
112 | // Distribute ETH
113 | splitFeesCore.distribute(splitWallet, NATIVE_TOKEN_ADDRESS);
114 |
115 | // After distribute, balances should be updated in the ERC6909 contract
116 | uint256 tokenId = uint256(uint160(NATIVE_TOKEN_ADDRESS));
117 |
118 | // Check that recipients have the correct balances
119 | uint256 balance1 = splitFeesCore.balanceOf(recipient1, tokenId);
120 | uint256 balance2 = splitFeesCore.balanceOf(recipient2, tokenId);
121 |
122 | assertEq(balance1, 0.5 ether, "Recipient1 should have 0.5 ether balance");
123 | assertEq(balance2, 0.5 ether, "Recipient2 should have 0.5 ether balance");
124 |
125 | // Now recipients can withdraw their balances
126 | vm.prank(recipient1);
127 | splitFeesCore.withdraw(recipient1, NATIVE_TOKEN_ADDRESS);
128 |
129 | vm.prank(recipient2);
130 | splitFeesCore.withdraw(recipient2, NATIVE_TOKEN_ADDRESS);
131 |
132 | // Check that recipients received ETH
133 | assertEq(recipient1.balance, initialBalance1 + 0.5 ether, "Recipient1 should have received 0.5 ether");
134 | assertEq(recipient2.balance, initialBalance2 + 0.5 ether, "Recipient2 should have received 0.5 ether");
135 |
136 | // Check that their balances in the ERC6909 contract are zero
137 | balance1 = splitFeesCore.balanceOf(recipient1, tokenId);
138 | balance2 = splitFeesCore.balanceOf(recipient2, tokenId);
139 |
140 | assertEq(balance1, 0, "Recipient1 should have zero balance after withdrawal");
141 | assertEq(balance2, 0, "Recipient2 should have zero balance after withdrawal");
142 | }
143 |
144 | function test_distribute_and_withdraw_ERC20() public {
145 | // Mint tokens to splitWallet
146 | uint256 amount = 1000 ether;
147 | token.mintTo(splitWallet, amount);
148 |
149 | // Check initial balances
150 | uint256 initialBalance1 = token.balanceOf(recipient1);
151 | uint256 initialBalance2 = token.balanceOf(recipient2);
152 |
153 | // Distribute ERC20 tokens
154 | splitFeesCore.distribute(splitWallet, address(token));
155 |
156 | // After distribute, balances should be updated in the ERC6909 contract
157 | uint256 tokenId = uint256(uint160(address(token)));
158 |
159 | // Check that recipients have the correct balances
160 | uint256 balance1 = splitFeesCore.balanceOf(recipient1, tokenId);
161 | uint256 balance2 = splitFeesCore.balanceOf(recipient2, tokenId);
162 |
163 | assertEq(balance1, amount / 2, "Recipient1 should have half of the tokens");
164 | assertEq(balance2, amount / 2, "Recipient2 should have half of the tokens");
165 |
166 | // Now recipients can withdraw their balances
167 | vm.prank(recipient1);
168 | splitFeesCore.withdraw(recipient1, address(token));
169 |
170 | vm.prank(recipient2);
171 | splitFeesCore.withdraw(recipient2, address(token));
172 |
173 | // Check that recipients received tokens
174 | assertEq(token.balanceOf(recipient1), initialBalance1 + amount / 2, "Recipient1 should have received tokens");
175 | assertEq(token.balanceOf(recipient2), initialBalance2 + amount / 2, "Recipient2 should have received tokens");
176 |
177 | // Check that their balances in the ERC6909 contract are zero
178 | balance1 = splitFeesCore.balanceOf(recipient1, tokenId);
179 | balance2 = splitFeesCore.balanceOf(recipient2, tokenId);
180 |
181 | assertEq(balance1, 0, "Recipient1 should have zero balance after withdrawal");
182 | assertEq(balance2, 0, "Recipient2 should have zero balance after withdrawal");
183 | }
184 |
185 | function test_revert_notSplitFees() public {
186 | vm.expectRevert(abi.encodeWithSelector(SplitWallet.OnlySplitFees.selector));
187 | SplitWallet(splitWallet).transferETH(10 ether);
188 |
189 | vm.expectRevert(abi.encodeWithSelector(SplitWallet.OnlySplitFees.selector));
190 | SplitWallet(splitWallet).transferERC20(address(token), 10 ether);
191 | }
192 |
193 | }
194 |
--------------------------------------------------------------------------------
/test/module/crosschain/SuperChainInterop.t.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: UNLICENSED
2 | pragma solidity ^0.8.0;
3 |
4 | import "lib/forge-std/src/console.sol";
5 |
6 | import {Test} from "forge-std/Test.sol";
7 |
8 | import {OwnableRoles} from "@solady/auth/OwnableRoles.sol";
9 | import {ERC20} from "@solady/tokens/ERC20.sol";
10 |
11 | // Target contract
12 |
13 | import {Module} from "src/Module.sol";
14 |
15 | import {Role} from "src/Role.sol";
16 | import {ERC20Core} from "src/core/token/ERC20Core.sol";
17 |
18 | import {ICore} from "src/interface/ICore.sol";
19 | import {IModuleConfig} from "src/interface/IModuleConfig.sol";
20 | import {SuperChainInterop} from "src/module/token/crosschain/SuperChainInterop.sol";
21 |
22 | contract Core is ERC20Core {
23 |
24 | constructor(
25 | string memory name,
26 | string memory symbol,
27 | string memory contractURI,
28 | address owner,
29 | address[] memory modules,
30 | bytes[] memory moduleInstallData
31 | ) payable ERC20Core(name, symbol, contractURI, owner, modules, moduleInstallData) {}
32 |
33 | // disable mint callbacks for these tests
34 | function _beforeMint(address to, uint256 amount, bytes calldata data) internal override {}
35 |
36 | }
37 |
38 | contract MintableERC20Test is Test {
39 |
40 | Core public core;
41 |
42 | SuperChainInterop public superchainInterop;
43 |
44 | uint256 ownerPrivateKey = 1;
45 | address public owner;
46 | address public superchainBridge = address(0x123);
47 | address public actor1 = address(0x111);
48 | address public unpermissionedActor = address(0x222);
49 |
50 | event CrosschainMinted(address indexed _to, uint256 _amount);
51 | event CrosschainBurnt(address indexed _from, uint256 _amount);
52 |
53 | function setUp() public {
54 | owner = vm.addr(ownerPrivateKey);
55 |
56 | address[] memory modules;
57 | bytes[] memory moduleData;
58 |
59 | core = new Core("test", "TEST", "", owner, modules, moduleData);
60 | superchainInterop = new SuperChainInterop();
61 |
62 | // install module
63 | bytes memory encodedInstallParams = superchainInterop.encodeBytesOnInstall(superchainBridge);
64 | vm.prank(owner);
65 | core.installModule(address(superchainInterop), encodedInstallParams);
66 | }
67 |
68 | /*//////////////////////////////////////////////////////////////
69 | Tests: get / set SuperChainBridge
70 | //////////////////////////////////////////////////////////////*/
71 |
72 | function test_state_setSuperChainBridge() public {
73 | vm.prank(owner);
74 | SuperChainInterop(address(core)).setSuperChainBridge(address(0x123));
75 |
76 | assertEq(SuperChainInterop(address(core)).getSuperChainBridge(), address(0x123));
77 | }
78 |
79 | function test_revert_setSuperChainBridge_unauthorizedCaller() public {
80 | vm.prank(unpermissionedActor);
81 | vm.expectRevert(abi.encodeWithSelector(0x82b42900)); // Unauthorized()
82 | SuperChainInterop(address(core)).setSuperChainBridge(address(0x123));
83 | }
84 |
85 | function test_getSuperChainBridge_state() public {
86 | assertEq(SuperChainInterop(address(core)).getSuperChainBridge(), superchainBridge);
87 | }
88 |
89 | /*//////////////////////////////////////////////////////////////
90 | Tests: CrossChainMint
91 | //////////////////////////////////////////////////////////////*/
92 |
93 | function test_crosschainMint_state() public {
94 | uint256 balanceBefore = core.balanceOf(actor1);
95 | assertEq(balanceBefore, 0);
96 |
97 | vm.prank(superchainBridge);
98 | vm.expectEmit(true, true, true, true);
99 | emit CrosschainMinted(actor1, 10 ether);
100 | SuperChainInterop(address(core)).crosschainMint(actor1, 10 ether);
101 |
102 | assertEq(core.balanceOf(actor1), 10 ether);
103 | }
104 |
105 | function test_crosschainMint_revert_unauthorizedCaller() public {
106 | vm.prank(unpermissionedActor);
107 | vm.expectRevert(abi.encodeWithSelector(SuperChainInterop.SuperChainInteropNotSuperChainBridge.selector));
108 | SuperChainInterop(address(core)).crosschainMint(actor1, 10 ether);
109 | }
110 |
111 | /*//////////////////////////////////////////////////////////////
112 | Tests: CrossChainBurn
113 | //////////////////////////////////////////////////////////////*/
114 |
115 | function test_crosschainBurn_state() public {
116 | core.mint(actor1, 10 ether, "");
117 |
118 | uint256 balanceBefore = core.balanceOf(actor1);
119 | assertEq(balanceBefore, 10 ether);
120 |
121 | vm.prank(superchainBridge);
122 | vm.expectEmit(true, true, true, true);
123 | emit CrosschainBurnt(actor1, 10 ether);
124 | SuperChainInterop(address(core)).crosschainBurn(actor1, 10 ether);
125 |
126 | assertEq(core.balanceOf(actor1), 0);
127 | }
128 |
129 | function test_crosschainBurn_revert_unauthorizedCaller() public {
130 | vm.prank(unpermissionedActor);
131 | vm.expectRevert(abi.encodeWithSelector(SuperChainInterop.SuperChainInteropNotSuperChainBridge.selector));
132 | SuperChainInterop(address(core)).crosschainBurn(actor1, 10 ether);
133 | }
134 |
135 | }
136 |
--------------------------------------------------------------------------------
/test/module/metadata/BatchMetadataERC1155.t.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: UNLICENSED
2 | pragma solidity ^0.8.0;
3 |
4 | import "lib/forge-std/src/console.sol";
5 |
6 | import {Test} from "forge-std/Test.sol";
7 |
8 | // Target contract
9 |
10 | import {Core} from "src/Core.sol";
11 | import {Module} from "src/Module.sol";
12 | import {Role} from "src/Role.sol";
13 | import {ERC1155Core} from "src/core/token/ERC1155Core.sol";
14 |
15 | import {ICore} from "src/interface/ICore.sol";
16 | import {IModuleConfig} from "src/interface/IModuleConfig.sol";
17 |
18 | import {BatchMetadataERC1155} from "src/module/token/metadata/BatchMetadataERC1155.sol";
19 | import {BatchMetadataERC721} from "src/module/token/metadata/BatchMetadataERC721.sol";
20 | import {MintableERC1155} from "src/module/token/minting/MintableERC1155.sol";
21 |
22 | contract BatchMetadataExt is BatchMetadataERC1155 {}
23 |
24 | contract BatchMetadataERC1155Test is Test {
25 |
26 | ERC1155Core public core;
27 |
28 | BatchMetadataExt public moduleImplementation;
29 | MintableERC1155 public mintableModule;
30 |
31 | address public owner = address(0x1);
32 | address public permissionedActor = address(0x2);
33 | address public unpermissionedActor = address(0x3);
34 |
35 | function setUp() public {
36 | address[] memory modules;
37 | bytes[] memory moduleData;
38 |
39 | core = new ERC1155Core("test", "TEST", "", owner, modules, moduleData);
40 | moduleImplementation = new BatchMetadataExt();
41 | mintableModule = new MintableERC1155(address(0x0));
42 |
43 | // install module
44 | vm.prank(owner);
45 | core.installModule(address(moduleImplementation), "");
46 |
47 | bytes memory encodedInstallParams = abi.encode(owner);
48 | vm.prank(owner);
49 | core.installModule(address(mintableModule), encodedInstallParams);
50 |
51 | vm.prank(owner);
52 | core.grantRoles(owner, Role._MINTER_ROLE);
53 | }
54 |
55 | /*///////////////////////////////////////////////////////////////
56 | Unit tests: `getBatchIndex`
57 | //////////////////////////////////////////////////////////////*/
58 |
59 | function test_state_getBatchIndex() public {
60 | vm.prank(owner);
61 | BatchMetadataExt(address(core)).uploadMetadata(100, "ipfs://base/");
62 | uint256 batchIndex = BatchMetadataExt(address(core)).getBatchIndex(50);
63 |
64 | assertEq(batchIndex, 0);
65 | }
66 |
67 | function test_revert_getBatchIndex_noMetadata() public {
68 | vm.prank(owner);
69 | BatchMetadataExt(address(core)).uploadMetadata(100, "ipfs://base/");
70 |
71 | vm.expectRevert(BatchMetadataERC721.BatchMetadataNoMetadataForTokenId.selector);
72 | BatchMetadataExt(address(core)).getBatchIndex(101);
73 | }
74 |
75 | /*///////////////////////////////////////////////////////////////
76 | Unit tests: `getMetadataBatch`
77 | //////////////////////////////////////////////////////////////*/
78 |
79 | function test_state_getMetadataBatch() public {
80 | vm.startPrank(owner);
81 | BatchMetadataExt(address(core)).uploadMetadata(100, "ipfs://base/");
82 | BatchMetadataExt(address(core)).uploadMetadata(100, "ipfs://base2/");
83 | vm.stopPrank();
84 |
85 | BatchMetadataERC1155.MetadataBatch memory batch = BatchMetadataExt(address(core)).getMetadataBatch(0);
86 | BatchMetadataERC1155.MetadataBatch memory batch2 = BatchMetadataExt(address(core)).getMetadataBatch(1);
87 |
88 | assertEq(batch.startTokenIdInclusive, 0);
89 | assertEq(batch.endTokenIdInclusive, 99);
90 | assertEq(batch.baseURI, "ipfs://base/");
91 | assertEq(batch2.startTokenIdInclusive, 100);
92 | assertEq(batch2.endTokenIdInclusive, 199);
93 | assertEq(batch2.baseURI, "ipfs://base2/");
94 | }
95 |
96 | function test_revert_getMetadataBatch_invalidIndex() public {
97 | vm.prank(owner);
98 | BatchMetadataExt(address(core)).uploadMetadata(100, "ipfs://base/");
99 |
100 | vm.expectRevert();
101 | vm.prank(owner);
102 | BatchMetadataExt(address(core)).getMetadataBatch(1);
103 | }
104 |
105 | /*///////////////////////////////////////////////////////////////
106 | Unit tests: `setBaseURI`
107 | //////////////////////////////////////////////////////////////*/
108 |
109 | function test_state_setBaseURI() public {
110 | vm.prank(owner);
111 | BatchMetadataExt(address(core)).uploadMetadata(100, "ipfs://base/");
112 |
113 | // get metadata batches
114 | BatchMetadataExt.MetadataBatch[] memory batches = BatchMetadataExt(address(core)).getAllMetadataBatches();
115 |
116 | assertEq(batches.length, 1);
117 | assertEq(batches[0].baseURI, "ipfs://base/");
118 |
119 | vm.prank(owner);
120 | BatchMetadataExt(address(core)).setBaseURI(0, "ipfs://base2/");
121 |
122 | // get metadata batches
123 | BatchMetadataExt.MetadataBatch[] memory batches2 = BatchMetadataExt(address(core)).getAllMetadataBatches();
124 |
125 | assertEq(batches2.length, 1);
126 | assertEq(batches2[0].baseURI, "ipfs://base2/");
127 | }
128 |
129 | /*///////////////////////////////////////////////////////////////
130 | Unit tests: `uploadMetadata`
131 | //////////////////////////////////////////////////////////////*/
132 |
133 | function test_state_uploadMetadata() public {
134 | vm.prank(owner);
135 | BatchMetadataExt(address(core)).uploadMetadata(100, "ipfs://base/");
136 |
137 | // read state from core
138 | assertEq(core.uri(1), "ipfs://base/1");
139 | assertEq(core.uri(99), "ipfs://base/99");
140 |
141 | // upload another batch
142 | vm.prank(owner);
143 | BatchMetadataExt(address(core)).uploadMetadata(100, "ipfs://base2/");
144 |
145 | // read state from core
146 | assertEq(core.uri(1), "ipfs://base/1");
147 | assertEq(core.uri(99), "ipfs://base/99");
148 | assertEq(core.uri(100), "ipfs://base2/0");
149 | assertEq(core.uri(199), "ipfs://base2/99");
150 | }
151 |
152 | function test_revert_uploadMetadata() public {
153 | vm.expectRevert(0x82b42900); // `Unauthorized()`
154 | BatchMetadataExt(address(core)).uploadMetadata(100, "ipfs://base/");
155 | }
156 |
157 | function test_getAllMetadataBatches() public {
158 | vm.prank(owner);
159 | BatchMetadataExt(address(core)).uploadMetadata(100, "ipfs://base/");
160 |
161 | // get metadata batches
162 | BatchMetadataExt.MetadataBatch[] memory batches = BatchMetadataExt(address(core)).getAllMetadataBatches();
163 |
164 | assertEq(batches.length, 1);
165 | assertEq(batches[0].startTokenIdInclusive, 0);
166 | assertEq(batches[0].endTokenIdInclusive, 99);
167 | assertEq(batches[0].baseURI, "ipfs://base/");
168 | }
169 |
170 | function test_getNextTokenIdToMint() public {
171 | vm.prank(owner);
172 | BatchMetadataExt(address(core)).uploadMetadata(100, "ipfs://base/");
173 |
174 | // get metadata batches
175 | uint256 nextTokenIdToMint = BatchMetadataExt(address(core)).nextTokenIdToMint();
176 |
177 | assertEq(nextTokenIdToMint, 100);
178 | }
179 |
180 | }
181 |
--------------------------------------------------------------------------------
/test/module/metadata/OpenEditionMetadataERC1155.t.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: UNLICENSED
2 | pragma solidity ^0.8.0;
3 |
4 | import "lib/forge-std/src/console.sol";
5 |
6 | import {Test} from "forge-std/Test.sol";
7 |
8 | // Target contract
9 |
10 | import {Core} from "src/Core.sol";
11 | import {Module} from "src/Module.sol";
12 | import {ERC1155Core} from "src/core/token/ERC1155Core.sol";
13 |
14 | import {ICore} from "src/interface/ICore.sol";
15 | import {IModuleConfig} from "src/interface/IModuleConfig.sol";
16 | import {OpenEditionMetadataERC1155} from "src/module/token/metadata/OpenEditionMetadataERC1155.sol";
17 | import {
18 | OpenEditionMetadataERC721,
19 | OpenEditionMetadataStorage
20 | } from "src/module/token/metadata/OpenEditionMetadataERC721.sol";
21 |
22 | contract OpenEditionMetadataExt is OpenEditionMetadataERC1155 {
23 |
24 | function sharedMetadata() external view returns (SharedMetadata memory) {
25 | return OpenEditionMetadataStorage.data().sharedMetadata;
26 | }
27 |
28 | function createMetadataEdition(
29 | string memory name,
30 | string memory description,
31 | string memory imageURI,
32 | string memory animationURI,
33 | uint256 tokenOfEdition
34 | ) external pure returns (string memory) {
35 | return _createMetadataEdition(name, description, imageURI, animationURI, tokenOfEdition);
36 | }
37 |
38 | }
39 |
40 | contract OpenEditionMetadataERC1155Test is Test {
41 |
42 | ERC1155Core public core;
43 |
44 | OpenEditionMetadataExt public moduleImplementation;
45 | OpenEditionMetadataExt public installedModule;
46 |
47 | address public owner = address(0x1);
48 | address public permissionedActor = address(0x2);
49 | address public unpermissionedActor = address(0x3);
50 |
51 | function setUp() public {
52 | address[] memory modules;
53 | bytes[] memory moduleData;
54 |
55 | core = new ERC1155Core("test", "TEST", "", owner, modules, moduleData);
56 | moduleImplementation = new OpenEditionMetadataExt();
57 |
58 | // install module
59 | vm.prank(owner);
60 | core.installModule(address(moduleImplementation), "");
61 |
62 | ICore.InstalledModule[] memory installedModules = core.getInstalledModules();
63 | installedModule = OpenEditionMetadataExt(installedModules[0].implementation);
64 | }
65 |
66 | /*///////////////////////////////////////////////////////////////
67 | Unit tests: `setSharedMetadata`
68 | //////////////////////////////////////////////////////////////*/
69 |
70 | function test_state_setSharedMetadata() public {
71 | OpenEditionMetadataERC1155.SharedMetadata memory sharedMetadata = OpenEditionMetadataERC721.SharedMetadata({
72 | name: "Test",
73 | description: "Test",
74 | imageURI: "https://test.com",
75 | animationURI: "https://test.com"
76 | });
77 |
78 | vm.prank(owner);
79 | OpenEditionMetadataExt(address(core)).setSharedMetadata(sharedMetadata);
80 |
81 | // read state from core
82 | assertEq(
83 | core.uri(1),
84 | installedModule.createMetadataEdition(
85 | sharedMetadata.name, sharedMetadata.description, sharedMetadata.imageURI, sharedMetadata.animationURI, 1
86 | )
87 | );
88 | }
89 |
90 | function test_revert_setSharedMetadata() public {
91 | OpenEditionMetadataERC1155.SharedMetadata memory sharedMetadata;
92 |
93 | vm.expectRevert(0x82b42900); // `Unauthorized()`
94 | OpenEditionMetadataExt(address(core)).setSharedMetadata(sharedMetadata);
95 | }
96 |
97 | }
98 |
--------------------------------------------------------------------------------
/test/module/metadata/OpenEditionMetadataERC721.t.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: UNLICENSED
2 | pragma solidity ^0.8.0;
3 |
4 | import "lib/forge-std/src/console.sol";
5 |
6 | import {Test} from "forge-std/Test.sol";
7 |
8 | // Target contract
9 |
10 | import {Core} from "src/Core.sol";
11 | import {Module} from "src/Module.sol";
12 | import {ERC721Core} from "src/core/token/ERC721Core.sol";
13 |
14 | import {ICore} from "src/interface/ICore.sol";
15 | import {IModuleConfig} from "src/interface/IModuleConfig.sol";
16 | import {
17 | OpenEditionMetadataERC721,
18 | OpenEditionMetadataStorage
19 | } from "src/module/token/metadata/OpenEditionMetadataERC721.sol";
20 |
21 | contract OpenEditionMetadataExt is OpenEditionMetadataERC721 {
22 |
23 | function sharedMetadata() external view returns (SharedMetadata memory) {
24 | return OpenEditionMetadataStorage.data().sharedMetadata;
25 | }
26 |
27 | function createMetadataEdition(
28 | string memory name,
29 | string memory description,
30 | string memory imageURI,
31 | string memory animationURI,
32 | uint256 tokenOfEdition
33 | ) external pure returns (string memory) {
34 | return _createMetadataEdition(name, description, imageURI, animationURI, tokenOfEdition);
35 | }
36 |
37 | }
38 |
39 | contract OpenEditionMetadataERC721Test is Test {
40 |
41 | ERC721Core public core;
42 |
43 | OpenEditionMetadataExt public moduleImplementation;
44 | OpenEditionMetadataExt public installedModule;
45 |
46 | address public owner = address(0x1);
47 | address public permissionedActor = address(0x2);
48 | address public unpermissionedActor = address(0x3);
49 |
50 | function setUp() public {
51 | address[] memory modules;
52 | bytes[] memory moduleData;
53 |
54 | core = new ERC721Core("test", "TEST", "", owner, modules, moduleData);
55 | moduleImplementation = new OpenEditionMetadataExt();
56 |
57 | // install module
58 | vm.prank(owner);
59 | core.installModule(address(moduleImplementation), "");
60 |
61 | ICore.InstalledModule[] memory installedModules = core.getInstalledModules();
62 | installedModule = OpenEditionMetadataExt(installedModules[0].implementation);
63 | }
64 |
65 | /*///////////////////////////////////////////////////////////////
66 | Unit tests: `setSharedMetadata`
67 | //////////////////////////////////////////////////////////////*/
68 |
69 | function test_state_setSharedMetadata() public {
70 | OpenEditionMetadataERC721.SharedMetadata memory sharedMetadata = OpenEditionMetadataERC721.SharedMetadata({
71 | name: "Test",
72 | description: "Test",
73 | imageURI: "https://test.com",
74 | animationURI: "https://test.com"
75 | });
76 |
77 | vm.prank(owner);
78 | OpenEditionMetadataExt(address(core)).setSharedMetadata(sharedMetadata);
79 |
80 | // read state from core
81 | assertEq(
82 | core.tokenURI(1),
83 | installedModule.createMetadataEdition(
84 | sharedMetadata.name, sharedMetadata.description, sharedMetadata.imageURI, sharedMetadata.animationURI, 1
85 | )
86 | );
87 | }
88 |
89 | function test_revert_setSharedMetadata() public {
90 | OpenEditionMetadataERC721.SharedMetadata memory sharedMetadata;
91 |
92 | vm.expectRevert(0x82b42900); // `Unauthorized()`
93 | OpenEditionMetadataExt(address(core)).setSharedMetadata(sharedMetadata);
94 | }
95 |
96 | }
97 |
--------------------------------------------------------------------------------
/test/module/tokenId/SequentialTokenIdERC1155.t.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: UNLICENSED
2 | pragma solidity ^0.8.0;
3 |
4 | import "lib/forge-std/src/console.sol";
5 |
6 | import {OwnableRoles} from "@solady/auth/OwnableRoles.sol";
7 | import {ERC20} from "@solady/tokens/ERC20.sol";
8 | import {Test} from "forge-std/Test.sol";
9 |
10 | // Target contract
11 |
12 | import {Core} from "src/Core.sol";
13 | import {Module} from "src/Module.sol";
14 |
15 | import {Role} from "src/Role.sol";
16 | import {ERC1155Core} from "src/core/token/ERC1155Core.sol";
17 |
18 | import {ICore} from "src/interface/ICore.sol";
19 | import {IModuleConfig} from "src/interface/IModuleConfig.sol";
20 |
21 | import {BatchMetadataERC1155} from "src/module/token/metadata/BatchMetadataERC1155.sol";
22 | import {BatchMetadataERC721} from "src/module/token/metadata/BatchMetadataERC721.sol";
23 | import {MintableERC1155} from "src/module/token/minting/MintableERC1155.sol";
24 | import {SequentialTokenIdERC1155} from "src/module/token/tokenId/SequentialTokenIdERC1155.sol";
25 |
26 | contract MockCurrency is ERC20 {
27 |
28 | function mintTo(address _recipient, uint256 _amount) public {
29 | _mint(_recipient, _amount);
30 | }
31 |
32 | /// @dev Returns the name of the token.
33 | function name() public view virtual override returns (string memory) {
34 | return "MockCurrency";
35 | }
36 |
37 | /// @dev Returns the symbol of the token.
38 | function symbol() public view virtual override returns (string memory) {
39 | return "MOCK";
40 | }
41 |
42 | }
43 |
44 | contract MintableERC1155Test is Test {
45 |
46 | ERC1155Core public core;
47 |
48 | MintableERC1155 public mintableModule;
49 | BatchMetadataERC1155 public batchMetadataModule;
50 | SequentialTokenIdERC1155 public sequentialTokenIdModule;
51 |
52 | uint256 ownerPrivateKey = 1;
53 | address public owner;
54 |
55 | address tokenRecipient = address(0x123);
56 |
57 | MintableERC1155.MintSignatureParamsERC1155 public mintRequest;
58 |
59 | // Constants
60 | address private constant NATIVE_TOKEN_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
61 |
62 | function setUp() public {
63 | owner = vm.addr(ownerPrivateKey);
64 |
65 | address[] memory modules;
66 | bytes[] memory moduleData;
67 |
68 | core = new ERC1155Core("test", "TEST", "", owner, modules, moduleData);
69 | mintableModule = new MintableERC1155(address(0x0));
70 | batchMetadataModule = new BatchMetadataERC1155();
71 | sequentialTokenIdModule = new SequentialTokenIdERC1155();
72 |
73 | // install module
74 | bytes memory encodedInstallParams = abi.encode(owner);
75 | vm.prank(owner);
76 | core.installModule(address(mintableModule), encodedInstallParams);
77 |
78 | vm.prank(owner);
79 | core.installModule(address(batchMetadataModule), "");
80 |
81 | bytes memory encodedTokenIdInstallParams = sequentialTokenIdModule.encodeBytesOnInstall(0);
82 | vm.prank(owner);
83 | core.installModule(address(sequentialTokenIdModule), encodedTokenIdInstallParams);
84 |
85 | // Give permissioned actor minter role
86 | vm.prank(owner);
87 | core.grantRoles(owner, Role._MINTER_ROLE);
88 | }
89 |
90 | /*//////////////////////////////////////////////////////////////
91 | Tests: onInstall
92 | //////////////////////////////////////////////////////////////*/
93 |
94 | function test_onInstall() public {
95 | vm.startPrank(owner);
96 | core.uninstallModule(address(sequentialTokenIdModule), "");
97 | core.installModule(address(sequentialTokenIdModule), sequentialTokenIdModule.encodeBytesOnInstall(5));
98 | vm.stopPrank();
99 |
100 | assertEq(SequentialTokenIdERC1155(address(core)).getNextTokenId(), 5);
101 | }
102 |
103 | /*//////////////////////////////////////////////////////////////
104 | Tests: beforeMintERC1155
105 | //////////////////////////////////////////////////////////////*/
106 |
107 | function test_state_updateTokenId() public {
108 | assertEq(SequentialTokenIdERC1155(address(core)).getNextTokenId(), 0);
109 |
110 | // increments the tokenId
111 | vm.prank(owner);
112 | core.mint(owner, type(uint256).max, 10, "", "");
113 |
114 | assertEq(SequentialTokenIdERC1155(address(core)).getNextTokenId(), 1);
115 |
116 | // does not increment the tokenId
117 | vm.prank(owner);
118 | core.mint(owner, 1, 10, "", "");
119 |
120 | assertEq(SequentialTokenIdERC1155(address(core)).getNextTokenId(), 1);
121 | }
122 |
123 | function test_revert_updateTokenId() public {
124 | vm.expectRevert(SequentialTokenIdERC1155.SequentialTokenIdInvalidTokenId.selector);
125 | vm.prank(owner);
126 | core.mint(owner, 2, 1, "", "");
127 | }
128 |
129 | }
130 |
--------------------------------------------------------------------------------
/test/module/transferable/TransferableERC20.t.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: UNLICENSED
2 | pragma solidity ^0.8.0;
3 |
4 | import "lib/forge-std/src/console.sol";
5 |
6 | import {Test} from "forge-std/Test.sol";
7 |
8 | // Target contract
9 |
10 | import {Module} from "src/Module.sol";
11 | import {ERC20Core} from "src/core/token/ERC20Core.sol";
12 |
13 | import {ICore} from "src/interface/ICore.sol";
14 | import {IModuleConfig} from "src/interface/IModuleConfig.sol";
15 | import {TransferableERC20} from "src/module/token/transferable/TransferableERC20.sol";
16 |
17 | contract TransferableExt is TransferableERC20 {}
18 |
19 | contract Core is ERC20Core {
20 |
21 | constructor(
22 | string memory name,
23 | string memory symbol,
24 | string memory contractURI,
25 | address owner,
26 | address[] memory modules,
27 | bytes[] memory moduleInstallData
28 | ) payable ERC20Core(name, symbol, contractURI, owner, modules, moduleInstallData) {}
29 |
30 | // disable mint and approve callbacks for these tests
31 | function _beforeMint(address to, uint256 amount, bytes calldata data) internal override {}
32 | function _beforeApprove(address from, address to, uint256 amount) internal override {}
33 |
34 | }
35 |
36 | contract TransferableERC20Test is Test {
37 |
38 | Core public core;
39 |
40 | TransferableExt public moduleImplementation;
41 | TransferableExt public installedModule;
42 |
43 | address public owner = address(0x1);
44 | address public actorOne = address(0x2);
45 | address public actorTwo = address(0x3);
46 | address public actorThree = address(0x4);
47 |
48 | function setUp() public {
49 | address[] memory modules;
50 | bytes[] memory moduleData;
51 |
52 | core = new Core("test", "TEST", "", owner, modules, moduleData);
53 | moduleImplementation = new TransferableExt();
54 |
55 | // install module
56 | vm.prank(owner);
57 | core.installModule(address(moduleImplementation), "");
58 |
59 | ICore.InstalledModule[] memory installedModules = core.getInstalledModules();
60 | installedModule = TransferableExt(installedModules[0].implementation);
61 |
62 | // mint tokens
63 | core.mint(actorOne, 10 ether, "");
64 | core.mint(actorTwo, 10 ether, "");
65 | core.mint(actorThree, 10 ether, "");
66 | }
67 |
68 | /*///////////////////////////////////////////////////////////////
69 | Unit tests: `setTransferable`
70 | //////////////////////////////////////////////////////////////*/
71 |
72 | function test_state_setTransferable() public {
73 | // transfers enabled globally
74 | vm.prank(owner);
75 | TransferableExt(address(core)).setTransferable(true);
76 |
77 | // transfer tokens
78 | vm.prank(actorOne);
79 | core.transfer(actorTwo, 2 ether);
80 |
81 | // read state from core
82 | assertEq(core.balanceOf(actorOne), 8 ether);
83 | assertEq(core.balanceOf(actorTwo), 12 ether);
84 | assertEq(TransferableExt(address(core)).isTransferEnabled(), true);
85 |
86 | // transfers disabled globally
87 | vm.prank(owner);
88 | TransferableExt(address(core)).setTransferable(false);
89 |
90 | assertEq(TransferableExt(address(core)).isTransferEnabled(), false);
91 |
92 | // should revert on transfer tokens
93 | vm.prank(actorTwo);
94 | vm.expectRevert(TransferableERC20.TransferDisabled.selector);
95 | core.transfer(actorOne, 1);
96 | }
97 |
98 | function test_revert_setTransferable() public {
99 | vm.expectRevert(0x82b42900); // `Unauthorized()`
100 | TransferableExt(address(core)).setTransferable(true);
101 | }
102 |
103 | /*///////////////////////////////////////////////////////////////
104 | Unit tests: `setTransferableFor`
105 | //////////////////////////////////////////////////////////////*/
106 |
107 | function test_state_setTransferableFor_from() public {
108 | // transfers disabled globally
109 | vm.startPrank(owner);
110 | TransferableExt(address(core)).setTransferable(false);
111 | TransferableExt(address(core)).setTransferableFor(actorOne, true);
112 | vm.stopPrank();
113 |
114 | // transfer tokens
115 | vm.prank(actorOne);
116 | core.transfer(actorTwo, 2 ether);
117 |
118 | // read state from core
119 | assertEq(core.balanceOf(actorOne), 8 ether);
120 | assertEq(core.balanceOf(actorTwo), 12 ether);
121 | assertEq(TransferableExt(address(core)).isTransferEnabled(), false);
122 | assertEq(TransferableExt(address(core)).isTransferEnabledFor(actorOne), true);
123 | assertEq(TransferableExt(address(core)).isTransferEnabledFor(actorTwo), false);
124 |
125 | // should revert when transfer not enabled for
126 | vm.prank(actorTwo);
127 | vm.expectRevert(TransferableERC20.TransferDisabled.selector);
128 | core.transfer(actorThree, 1);
129 | }
130 |
131 | function test_state_setTransferableFor_to() public {
132 | // transfers disabled globally
133 | vm.startPrank(owner);
134 | TransferableExt(address(core)).setTransferable(false);
135 | TransferableExt(address(core)).setTransferableFor(actorTwo, true);
136 | vm.stopPrank();
137 |
138 | // transfer tokens
139 | vm.prank(actorOne);
140 | core.transfer(actorTwo, 2 ether);
141 |
142 | // read state from core
143 | assertEq(core.balanceOf(actorOne), 8 ether);
144 | assertEq(core.balanceOf(actorTwo), 12 ether);
145 | assertEq(TransferableExt(address(core)).isTransferEnabled(), false);
146 | assertEq(TransferableExt(address(core)).isTransferEnabledFor(actorOne), false);
147 | assertEq(TransferableExt(address(core)).isTransferEnabledFor(actorTwo), true);
148 |
149 | // revert when transfers not enabled for
150 | vm.prank(actorOne);
151 | vm.expectRevert(TransferableERC20.TransferDisabled.selector);
152 | core.transfer(actorThree, 1);
153 | }
154 |
155 | function test_state_setTransferableFor_operator() public {
156 | // transfers disabled globally
157 | vm.startPrank(owner);
158 | TransferableExt(address(core)).setTransferable(false);
159 | TransferableExt(address(core)).setTransferableFor(actorOne, true);
160 | vm.stopPrank();
161 |
162 | // approve tokens to operator actorOne
163 | vm.prank(actorTwo);
164 | core.approve(actorOne, type(uint256).max);
165 |
166 | // transfer tokens
167 | vm.prank(actorOne);
168 | core.transferFrom(actorTwo, actorThree, 2 ether);
169 |
170 | // read state from core
171 | assertEq(core.balanceOf(actorTwo), 8 ether);
172 | assertEq(core.balanceOf(actorThree), 12 ether);
173 | assertEq(TransferableExt(address(core)).isTransferEnabled(), false);
174 | assertEq(TransferableExt(address(core)).isTransferEnabledFor(actorOne), true);
175 | assertEq(TransferableExt(address(core)).isTransferEnabledFor(actorTwo), false);
176 | assertEq(TransferableExt(address(core)).isTransferEnabledFor(actorThree), false);
177 |
178 | // revert when transfers not enabled for
179 | vm.prank(actorTwo);
180 | vm.expectRevert(TransferableERC20.TransferDisabled.selector);
181 | core.transferFrom(actorTwo, actorThree, 0);
182 | }
183 |
184 | function test_revert_setTransferableFor() public {
185 | vm.expectRevert(0x82b42900); // `Unauthorized()`
186 | TransferableExt(address(core)).setTransferableFor(actorOne, true);
187 | }
188 |
189 | function test_burner_should_not_need_to_approve_to_themselves() public {
190 | vm.startPrank(actorOne);
191 | core.burn(actorOne, 1, "");
192 | }
193 |
194 | }
195 |
--------------------------------------------------------------------------------
/test/module/transferable/TransferableERC721.t.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: UNLICENSED
2 | pragma solidity ^0.8.0;
3 |
4 | import "lib/forge-std/src/console.sol";
5 |
6 | import {Test} from "forge-std/Test.sol";
7 |
8 | // Target contract
9 |
10 | import {Module} from "src/Module.sol";
11 | import {ERC721Core} from "src/core/token/ERC721Core.sol";
12 |
13 | import {ICore} from "src/interface/ICore.sol";
14 | import {IModuleConfig} from "src/interface/IModuleConfig.sol";
15 | import {TransferableERC721} from "src/module/token/transferable/TransferableERC721.sol";
16 |
17 | contract TransferableExt is TransferableERC721 {}
18 |
19 | contract Core is ERC721Core {
20 |
21 | constructor(
22 | string memory name,
23 | string memory symbol,
24 | string memory contractURI,
25 | address owner,
26 | address[] memory modules,
27 | bytes[] memory moduleInstallData
28 | ) ERC721Core(name, symbol, contractURI, owner, modules, moduleInstallData) {}
29 |
30 | // disable mint and approve callbacks for these tests
31 | function _beforeMint(address to, uint256 startTokenId, uint256 quantity, bytes calldata data) internal override {}
32 | function _beforeApproveForAll(address from, address to, bool approved) internal override {}
33 |
34 | }
35 |
36 | contract TransferableERC721Test is Test {
37 |
38 | Core public core;
39 |
40 | TransferableExt public moduleImplementation;
41 | TransferableExt public installedModule;
42 |
43 | address public owner = address(0x1);
44 | address public actorOne = address(0x2);
45 | address public actorTwo = address(0x3);
46 | address public actorThree = address(0x4);
47 |
48 | function setUp() public {
49 | address[] memory modules;
50 | bytes[] memory moduleData;
51 |
52 | core = new Core("test", "TEST", "", owner, modules, moduleData);
53 | moduleImplementation = new TransferableExt();
54 |
55 | // install module
56 | vm.prank(owner);
57 | core.installModule(address(moduleImplementation), "");
58 |
59 | ICore.InstalledModule[] memory installedModules = core.getInstalledModules();
60 | installedModule = TransferableExt(installedModules[0].implementation);
61 |
62 | // mint tokens
63 | core.mint(actorOne, 1, string(""), ""); // tokenId 0
64 | core.mint(actorTwo, 1, string(""), ""); // tokenId 1
65 | core.mint(actorThree, 1, string(""), ""); // tokenId 2
66 | }
67 |
68 | /*///////////////////////////////////////////////////////////////
69 | Unit tests: `setTransferable`
70 | //////////////////////////////////////////////////////////////*/
71 |
72 | function test_state_setTransferable() public {
73 | // transfers enabled globally
74 | vm.prank(owner);
75 | TransferableExt(address(core)).setTransferable(true);
76 |
77 | // transfer tokens
78 | vm.prank(actorOne);
79 | core.transferFrom(actorOne, actorTwo, 0);
80 |
81 | // read state from core
82 | assertEq(core.ownerOf(0), actorTwo);
83 | assertEq(core.ownerOf(1), actorTwo);
84 | assertEq(core.balanceOf(actorOne), 0);
85 | assertEq(core.balanceOf(actorTwo), 2);
86 | assertEq(TransferableExt(address(core)).isTransferEnabled(), true);
87 |
88 | // transfers disabled globally
89 | vm.prank(owner);
90 | TransferableExt(address(core)).setTransferable(false);
91 |
92 | assertEq(TransferableExt(address(core)).isTransferEnabled(), false);
93 |
94 | // should revert on transfer tokens
95 | vm.prank(actorTwo);
96 | vm.expectRevert(TransferableERC721.TransferDisabled.selector);
97 | core.transferFrom(actorTwo, actorOne, 0);
98 | }
99 |
100 | function test_revert_setTransferable() public {
101 | vm.expectRevert(0x82b42900); // `Unauthorized()`
102 | TransferableExt(address(core)).setTransferable(true);
103 | }
104 |
105 | /*///////////////////////////////////////////////////////////////
106 | Unit tests: `setTransferableFor`
107 | //////////////////////////////////////////////////////////////*/
108 |
109 | function test_state_setTransferableFor_from() public {
110 | // transfers disabled globally
111 | vm.startPrank(owner);
112 | TransferableExt(address(core)).setTransferable(false);
113 | TransferableExt(address(core)).setTransferableFor(actorOne, true);
114 | vm.stopPrank();
115 |
116 | // transfer tokens
117 | vm.prank(actorOne);
118 | core.transferFrom(actorOne, actorTwo, 0);
119 |
120 | // read state from core
121 | assertEq(core.ownerOf(0), actorTwo);
122 | assertEq(core.ownerOf(1), actorTwo);
123 | assertEq(core.balanceOf(actorOne), 0);
124 | assertEq(core.balanceOf(actorTwo), 2);
125 | assertEq(TransferableExt(address(core)).isTransferEnabled(), false);
126 | assertEq(TransferableExt(address(core)).isTransferEnabledFor(actorOne), true);
127 | assertEq(TransferableExt(address(core)).isTransferEnabledFor(actorTwo), false);
128 |
129 | // revert when transfers not enabled for
130 | vm.prank(actorTwo);
131 | vm.expectRevert(TransferableERC721.TransferDisabled.selector);
132 | core.transferFrom(actorTwo, actorThree, 0);
133 | }
134 |
135 | function test_state_setTransferableFor_to() public {
136 | // transfers disabled globally
137 | vm.startPrank(owner);
138 | TransferableExt(address(core)).setTransferable(false);
139 | TransferableExt(address(core)).setTransferableFor(actorTwo, true);
140 | vm.stopPrank();
141 |
142 | // transfer tokens
143 | vm.prank(actorOne);
144 | core.transferFrom(actorOne, actorTwo, 0);
145 |
146 | // read state from core
147 | assertEq(core.ownerOf(0), actorTwo);
148 | assertEq(core.ownerOf(1), actorTwo);
149 | assertEq(core.balanceOf(actorOne), 0);
150 | assertEq(core.balanceOf(actorTwo), 2);
151 | assertEq(TransferableExt(address(core)).isTransferEnabled(), false);
152 | assertEq(TransferableExt(address(core)).isTransferEnabledFor(actorOne), false);
153 | assertEq(TransferableExt(address(core)).isTransferEnabledFor(actorTwo), true);
154 |
155 | // revert when transfers not enabled for
156 | vm.prank(actorOne);
157 | vm.expectRevert(TransferableERC721.TransferDisabled.selector);
158 | core.transferFrom(actorOne, actorThree, 0);
159 | }
160 |
161 | function test_state_setTransferableFor_operator() public {
162 | // transfers disabled globally
163 | vm.startPrank(owner);
164 | TransferableExt(address(core)).setTransferable(false);
165 | TransferableExt(address(core)).setTransferableFor(actorOne, true);
166 | vm.stopPrank();
167 |
168 | // approve tokens to operator actorOne
169 | vm.prank(actorTwo);
170 | core.setApprovalForAll(actorOne, true);
171 |
172 | // transfer tokens
173 | vm.prank(actorOne);
174 | core.transferFrom(actorTwo, actorThree, 1);
175 |
176 | // read state from core
177 | assertEq(core.ownerOf(1), actorThree);
178 | assertEq(core.ownerOf(2), actorThree);
179 | assertEq(core.balanceOf(actorTwo), 0);
180 | assertEq(core.balanceOf(actorThree), 2);
181 | assertEq(TransferableExt(address(core)).isTransferEnabled(), false);
182 | assertEq(TransferableExt(address(core)).isTransferEnabledFor(actorOne), true);
183 | assertEq(TransferableExt(address(core)).isTransferEnabledFor(actorTwo), false);
184 | assertEq(TransferableExt(address(core)).isTransferEnabledFor(actorThree), false);
185 |
186 | // revert when transfers not enabled for
187 | vm.prank(actorTwo);
188 | vm.expectRevert(TransferableERC721.TransferDisabled.selector);
189 | core.transferFrom(actorTwo, actorThree, 0);
190 | }
191 |
192 | function test_revert_setTransferableFor() public {
193 | vm.expectRevert(0x82b42900); // `Unauthorized()`
194 | TransferableExt(address(core)).setTransferableFor(actorOne, true);
195 | }
196 |
197 | }
198 |
--------------------------------------------------------------------------------
/test/utils/ExtensionProxyFactory.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | pragma solidity ^0.8.0;
3 |
4 | import "@solady/utils/ERC1967Factory.sol";
5 |
6 | contract ModuleProxyFactory is ERC1967Factory {}
7 |
--------------------------------------------------------------------------------
/test/utils/TestPlus.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity ^0.8.4;
3 |
4 | // credit Solady (https://github.com/Vectorized/solady)
5 | contract TestPlus {
6 |
7 | event LogString(string name, string value);
8 | event LogBytes(string name, bytes value);
9 | event LogUint(string name, uint256 value);
10 | event LogInt(string name, int256 value);
11 |
12 | /// @dev `address(bytes20(uint160(uint256(keccak256("hevm cheat code")))))`.
13 | address private constant _VM_ADDRESS = 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D;
14 |
15 | /// @dev Returns a pseudorandom random number from [0 .. 2**256 - 1] (inclusive).
16 | /// For usage in fuzz tests, please ensure that the function has an unnamed uint256 argument.
17 | /// e.g. `testSomething(uint256) public`.
18 | function _random() internal returns (uint256 r) {
19 | /// @solidity memory-safe-assembly
20 | assembly {
21 | // This is the keccak256 of a very long string I randomly mashed on my keyboard.
22 | let sSlot := 0xd715531fe383f818c5f158c342925dcf01b954d24678ada4d07c36af0f20e1ee
23 | let sValue := sload(sSlot)
24 |
25 | mstore(0x20, sValue)
26 | r := keccak256(0x20, 0x40)
27 |
28 | // If the storage is uninitialized, initialize it to the keccak256 of the calldata.
29 | if iszero(sValue) {
30 | sValue := sSlot
31 | let m := mload(0x40)
32 | calldatacopy(m, 0, calldatasize())
33 | r := keccak256(m, calldatasize())
34 | }
35 | sstore(sSlot, add(r, 1))
36 |
37 | // Do some biased sampling for more robust tests.
38 | // prettier-ignore
39 | for {} 1 {} {
40 | let d := byte(0, r)
41 | // With a 1/256 chance, randomly set `r` to any of 0,1,2.
42 | if iszero(d) {
43 | r := and(r, 3)
44 | break
45 | }
46 | // With a 1/2 chance, set `r` to near a random power of 2.
47 | if iszero(and(2, d)) {
48 | // Set `t` either `not(0)` or `xor(sValue, r)`.
49 | let t := xor(not(0), mul(iszero(and(4, d)), not(xor(sValue, r))))
50 | // Set `r` to `t` shifted left or right by a random multiple of 8.
51 | switch and(8, d)
52 | case 0 {
53 | if iszero(and(16, d)) { t := 1 }
54 | r := add(shl(shl(3, and(byte(3, r), 0x1f)), t), sub(and(r, 7), 3))
55 | }
56 | default {
57 | if iszero(and(16, d)) { t := shl(255, 1) }
58 | r := add(shr(shl(3, and(byte(3, r), 0x1f)), t), sub(and(r, 7), 3))
59 | }
60 | // With a 1/2 chance, negate `r`.
61 | if iszero(and(0x20, d)) { r := not(r) }
62 | break
63 | }
64 | // Otherwise, just set `r` to `xor(sValue, r)`.
65 | r := xor(sValue, r)
66 | break
67 | }
68 | }
69 | }
70 |
71 | /// @dev Returns a random signer and its private key.
72 | function _randomSigner() internal returns (address signer, uint256 privateKey) {
73 | uint256 privateKeyMax = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140;
74 | privateKey = _hem(_random(), 1, privateKeyMax);
75 | /// @solidity memory-safe-assembly
76 | assembly {
77 | mstore(0x00, 0xffa18649) // `addr(uint256)`.
78 | mstore(0x20, privateKey)
79 | if iszero(call(gas(), _VM_ADDRESS, 0, 0x1c, 0x24, 0x00, 0x20)) { revert(0, 0) }
80 | signer := mload(0x00)
81 | }
82 | }
83 |
84 | /// @dev Returns a random address.
85 | function _randomAddress() internal returns (address result) {
86 | result = address(uint160(_random()));
87 | }
88 |
89 | /// @dev Returns a random non-zero address.
90 | function _randomNonZeroAddress() internal returns (address result) {
91 | do {
92 | result = address(uint160(_random()));
93 | } while (result == address(0));
94 | }
95 |
96 | /// @dev Adapted from `bound`:
97 | /// https://github.com/foundry-rs/forge-std/blob/ff4bf7db008d096ea5a657f2c20516182252a3ed/src/StdUtils.sol#L10
98 | /// Differentially fuzzed tested against the original implementation.
99 | function _hem(uint256 x, uint256 min, uint256 max) internal pure virtual returns (uint256 result) {
100 | require(min <= max, "Max is less than min.");
101 |
102 | /// @solidity memory-safe-assembly
103 | assembly {
104 | // prettier-ignore
105 | for {} 1 {} {
106 | // If `x` is between `min` and `max`, return `x` directly.
107 | // This is to ensure that dictionary values
108 | // do not get shifted if the min is nonzero.
109 | // More info: https://github.com/foundry-rs/forge-std/issues/188
110 | if iszero(or(lt(x, min), gt(x, max))) {
111 | result := x
112 | break
113 | }
114 |
115 | let size := add(sub(max, min), 1)
116 | if and(iszero(gt(x, 3)), gt(size, x)) {
117 | result := add(min, x)
118 | break
119 | }
120 |
121 | let w := not(0)
122 | if and(iszero(lt(x, sub(0, 4))), gt(size, sub(w, x))) {
123 | result := sub(max, sub(w, x))
124 | break
125 | }
126 |
127 | // Otherwise, wrap x into the range [min, max],
128 | // i.e. the range is inclusive.
129 | if iszero(lt(x, max)) {
130 | let d := sub(x, max)
131 | let r := mod(d, size)
132 | if iszero(r) {
133 | result := max
134 | break
135 | }
136 | result := add(add(min, r), w)
137 | break
138 | }
139 | let d := sub(min, x)
140 | let r := mod(d, size)
141 | if iszero(r) {
142 | result := min
143 | break
144 | }
145 | result := add(sub(max, r), 1)
146 | break
147 | }
148 | }
149 | }
150 |
151 | function min3(uint256 a, uint256 b, uint256 c) internal pure returns (uint256) {
152 | return a > b ? (b > c ? c : b) : (a > c ? c : a);
153 | }
154 |
155 | }
156 |
--------------------------------------------------------------------------------