├── .env.example ├── .eslintignore ├── .eslintrc.js ├── .gas-snapshot ├── .github ├── dependabot.yml └── workflows │ ├── publish.yaml │ └── test.yml ├── .gitignore ├── .gitmodules ├── .npmignore ├── .nvmrc ├── .prettierignore ├── .prettierrc ├── .solhint.json ├── .solhintignore ├── BUILD.md ├── CHANGELOG.md ├── CODEOWNERS ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── SECURITY.md ├── abi ├── README.md ├── generated.ts └── index.ts ├── audits ├── allowlist │ └── 202402-internal-audit-operator-allow-list.pdf ├── bridge │ ├── flow-rate │ │ └── 202310-external-audit-trail-of-bits.pdf │ ├── x │ │ └── 202402-internal-audit-immutablex-bridge-v4.pdf │ └── zkevm │ │ ├── 202312-external-audit-trail-of-bits-zkevmbridgecontracts.pdf │ │ └── 202409-external-perimeter-fuzzing.pdf ├── deployer │ └── 202405-internal-audit-deployer.pdf ├── games │ └── gems │ │ └── 202404-internal-audit-gm-game.pdf ├── multicall │ ├── 202309-external-audit-multicaller.pdf │ ├── 202309-threat-model-multicaller.md │ ├── 202309-threat-model-multicaller │ │ ├── architecture.png │ │ └── high-level.png │ └── 202408-threat-model-multicaller.md ├── staking │ ├── 202410-threat-model-stake-holder.md │ └── 202504-threat-model-stake-holder.md ├── token │ ├── 202309-threat-model-preset-erc721.md │ ├── 202309-threat-model-preset-erc721 │ │ └── immutableERC721.png │ ├── 202312-threat-model-preset-erc1155.md │ ├── 202312-threat-model-preset-erc1155 │ │ └── ImmutableERC1155.jpg │ ├── 202402-internal-audit-preset-erc721.pdf │ ├── 202403-internal-audit-immutable-erc20.pdf │ ├── 202404-internal-audit-immutable-erc20.pdf │ ├── 202404-threat-model-preset-immutable-erc20-minter-burner-permit │ │ └── ImmutableERC20MinterBurnerPermit.png │ ├── 202404-threat-model-preset-immutable-erc20.md │ └── 202502-internal-audit-preset-erc721v2.pdf └── trading │ ├── 202308-audit-information-seaport.md │ ├── 202404-threat-model-immutable-signed-zone-v2.md │ └── 202405-internal-audit-immutable-signed-zone-v2.pdf ├── cla.txt ├── clients ├── config │ └── overrides.ts ├── erc20.ts ├── erc721-mint-by-id.ts ├── erc721.ts └── index.ts ├── contract_address.json ├── contracts ├── access │ ├── IERC173.sol │ ├── IMintingAccessControl.sol │ └── MintingAccessControl.sol ├── allowlist │ ├── IOperatorAllowlist.sol │ ├── IWalletProxy.sol │ ├── OperatorAllowlistEnforced.sol │ ├── OperatorAllowlistUpgradeable.sol │ ├── README.md │ └── oal-architecture.png ├── bridge │ └── x │ │ ├── v3 │ │ ├── CoreV3.sol │ │ ├── README.md │ │ └── RegistrationV3.sol │ │ └── v4 │ │ ├── CoreV4.sol │ │ ├── README.md │ │ └── RegistrationV4.sol ├── deployer │ ├── AccessControlledDeployer.sol │ ├── README.md │ ├── create │ │ └── OwnableCreateDeploy.sol │ ├── create2 │ │ └── OwnableCreate2Deployer.sol │ └── create3 │ │ ├── OwnableCreate3.sol │ │ ├── OwnableCreate3Address.sol │ │ └── OwnableCreate3Deployer.sol ├── errors │ ├── Errors.sol │ └── PaymentSplitterErrors.sol ├── games │ └── gems │ │ ├── GemGame.sol │ │ └── README.md ├── mocks │ ├── MockDisguisedEOA.sol │ ├── MockEIP1271Wallet.sol │ ├── MockFactory.sol │ ├── MockFunctions.sol │ ├── MockMarketplace.sol │ ├── MockOnReceive.sol │ ├── MockWallet.sol │ └── MockWalletFactory.sol ├── multicall │ ├── GuardedMulticaller.sol │ ├── GuardedMulticaller2.sol │ └── README.md ├── payment-splitter │ └── PaymentSplitter.sol ├── random │ └── README.md ├── staking │ ├── IStakeHolder.sol │ ├── IWIMX.sol │ ├── README.md │ ├── StakeHolderBase.sol │ ├── StakeHolderERC20.sol │ ├── StakeHolderNative.sol │ ├── StakeHolderWIMX.sol │ ├── WIMX.sol │ └── staking-architecture.png ├── test │ └── allowlist │ │ └── OperatorAllowlist.sol ├── token │ ├── erc1155 │ │ ├── README.md │ │ ├── abstract │ │ │ ├── ERC1155Permit.sol │ │ │ ├── IERC1155Permit.sol │ │ │ └── ImmutableERC1155Base.sol │ │ └── preset │ │ │ └── ImmutableERC1155.sol │ ├── erc20 │ │ ├── README.md │ │ └── preset │ │ │ ├── Errors.sol │ │ │ ├── ImmutableERC20FixedSupplyNoBurn.sol │ │ │ └── ImmutableERC20MinterBurnerPermit.sol │ └── erc721 │ │ ├── MermaidDiagramSource.md │ │ ├── README.md │ │ ├── abstract │ │ ├── ERC721Hybrid.sol │ │ ├── ERC721HybridPermit.sol │ │ ├── ERC721HybridPermitV2.sol │ │ ├── ERC721HybridV2.sol │ │ ├── ERC721Permit.sol │ │ ├── IERC4494.sol │ │ ├── ImmutableERC721Base.sol │ │ ├── ImmutableERC721HybridBase.sol │ │ └── ImmutableERC721HybridBaseV2.sol │ │ ├── erc721psi │ │ ├── ERC721Psi.sol │ │ ├── ERC721PsiBurnable.sol │ │ ├── ERC721PsiBurnableV2.sol │ │ ├── ERC721PsiV2.sol │ │ └── README.md │ │ ├── interfaces │ │ ├── IImmutableERC721.sol │ │ ├── IImmutableERC721ByQuantity.sol │ │ ├── IImmutableERC721ByQuantityV2.sol │ │ ├── IImmutableERC721Errors.sol │ │ └── IImmutableERC721Structs.sol │ │ ├── preset │ │ ├── ImmutableERC721.sol │ │ ├── ImmutableERC721MintByID.sol │ │ └── ImmutableERC721V2.sol │ │ └── x │ │ ├── Asset.sol │ │ ├── IMintable.sol │ │ ├── Mintable.sol │ │ └── utils │ │ ├── Bytes.sol │ │ └── Minting.sol └── trading │ └── seaport │ ├── ImmutableSeaport.sol │ ├── conduit │ └── ConduitController.sol │ ├── interfaces │ └── ImmutableSeaportEvents.sol │ ├── test │ └── SeaportTestContracts.sol │ ├── validators │ ├── ReadOnlyOrderValidator.sol │ ├── SeaportValidator.sol │ └── SeaportValidatorHelper.sol │ └── zones │ └── immutable-signed-zone │ ├── v1 │ ├── ImmutableSignedZone.sol │ └── interfaces │ │ ├── SIP5Interface.sol │ │ ├── SIP6EventsAndErrors.sol │ │ ├── SIP7EventsAndErrors.sol │ │ └── SIP7Interface.sol │ └── v2 │ ├── ImmutableSignedZoneV2.sol │ ├── README.md │ ├── ZoneAccessControl.sol │ └── interfaces │ ├── SIP5EventsAndErrors.sol │ ├── SIP5Interface.sol │ ├── SIP6EventsAndErrors.sol │ ├── SIP6Interface.sol │ ├── SIP7EventsAndErrors.sol │ ├── SIP7Interface.sol │ └── ZoneAccessControlEventsAndErrors.sol ├── deploy ├── utils.ts └── x │ ├── README.md │ └── asset.ts ├── foundry.toml ├── hardhat.config.ts ├── index.ts ├── package.json ├── perfTest ├── README.md └── token │ └── erc721 │ ├── ERC721ByQuantityPerf.t.sol │ ├── ERC721Perf.t.sol │ ├── ImmutableERC721ByIdPerf.t.sol │ ├── ImmutableERC721ByIdPerfPrefill.t.sol │ ├── ImmutableERC721ByQuantityPerf.t.sol │ ├── ImmutableERC721ByQuantityPerfPrefill.t.sol │ ├── ImmutableERC721V2ByQuantityPerf.t.sol │ └── ImmutableERC721V2ByQuantityPerfPrefill.t.sol ├── readmecheck.sh ├── remappings.txt ├── script ├── bridge │ └── x │ │ └── v4 │ │ ├── DeployRegistrationV4.s.sol │ │ ├── DeployRegistrationV4Dev.s.sol │ │ └── DeployRegistrationV4Sandbox.s.sol ├── games │ └── gems │ │ └── DeployGemGame.sol ├── staking │ ├── README.md │ ├── StakeHolderScriptERC20.t.sol │ ├── StakeHolderScriptWIMX.t.sol │ ├── common.sh │ ├── deployComplex.sh │ ├── deployDeployer.sh │ ├── deploySimple.sh │ ├── stake.sh │ └── unstake.sh └── trading │ └── seaport │ ├── DeployImmutableSignedZoneV2.s.sol │ └── DeployImmutableSignedZoneV2Dev.s.sol ├── slither.config.json ├── test ├── allowlist │ ├── AllowlistImmutableERC721MintByIDTransferApprovals.t.sol │ ├── AllowlistImmutableERC721TransferApprovals.t.sol │ ├── MockOAL.sol │ └── OperatorAllowlistUpgradeable.t.sol ├── bridge │ └── x │ │ └── v4 │ │ ├── MockCoreV4.sol │ │ ├── README.md │ │ └── RegistrationV4.t.sol ├── deployer │ ├── AccessControlledDeployer.t.sol │ ├── create2 │ │ ├── Create2Utils.sol │ │ └── OwnableCreate2Deployer.t.sol │ └── create3 │ │ ├── Create3Utils.sol │ │ └── OwnableCreate3Deployer.t.sol ├── games │ └── gems │ │ └── GemGame.t.sol ├── multicall │ ├── GuardedMulticaller.test.ts │ ├── GuardedMulticaller2.t.sol │ └── SigUtils.t.sol ├── payment-splitter │ ├── MockERC20.sol │ └── PaymentSplitter.t.sol ├── royalty-enforcement │ └── RoyaltyMarketplace.test.ts ├── staking │ ├── README.md │ ├── StakeHolderAttackWallet.sol │ ├── StakeHolderBase.t.sol │ ├── StakeHolderConfigBase.t.sol │ ├── StakeHolderConfigERC20.t.sol │ ├── StakeHolderConfigNative.t.sol │ ├── StakeHolderConfigWIMX.t.sol │ ├── StakeHolderInitBase.t.sol │ ├── StakeHolderInitERC20.t.sol │ ├── StakeHolderInitNative.t.sol │ ├── StakeHolderInitWIMX.t.sol │ ├── StakeHolderOperationalBase.t.sol │ ├── StakeHolderOperationalERC20.t.sol │ ├── StakeHolderOperationalNative.t.sol │ ├── StakeHolderOperationalWIMX.t.sol │ ├── StakeHolderTimeDelayBase.t.sol │ ├── StakeHolderTimeDelayERC20.t.sol │ └── StakeHolderTimeDelayWIMX.t.sol ├── token │ ├── erc1155 │ │ ├── ImmutableERC1155.t.sol │ │ ├── ImmutableERC1155Costs.t.sol │ │ └── README.md │ ├── erc20 │ │ └── preset │ │ │ ├── ImmutableERC20FixedSupplyNoBurn.t.sol │ │ │ ├── ImmutableERC20MinterBurnerPermit.t.sol │ │ │ └── README.md │ ├── erc721 │ │ ├── ERC721Base.t.sol │ │ ├── ERC721ByQuantityBase.t.sol │ │ ├── ERC721ConfigBase.t.sol │ │ ├── ERC721ConfigByIdV1.t.sol │ │ ├── ERC721ConfigByQuantityBase.t.sol │ │ ├── ERC721ConfigByQuantityV1.t.sol │ │ ├── ERC721ConfigByQuantityV2.t.sol │ │ ├── ERC721OperationalBase.t.sol │ │ ├── ERC721OperationalByIdV1.t.sol │ │ ├── ERC721OperationalByQuantityBase.t.sol │ │ ├── ERC721OperationalByQuantityV1.t.sol │ │ ├── ERC721OperationalByQuantityV2.t.sol │ │ └── fuzz │ │ │ ├── ERC721PsiV2.Echidna.sol │ │ │ ├── Properties.md │ │ │ ├── README.md │ │ │ └── echidna.config.yaml │ └── x │ │ └── Asset.test.ts ├── trading │ └── seaport │ │ ├── ImmutableSeaportHarness.t.sol │ │ ├── ImmutableSeaportSignedZoneV2Integration.t.sol │ │ ├── immutableseaport.test.ts │ │ ├── utils │ │ ├── IImmutableERC1155.t.sol │ │ ├── IImmutableERC721.t.sol │ │ ├── IOperatorAllowlistUpgradeable.t.sol │ │ ├── SigningTestHelper.t.sol │ │ ├── criteria.ts │ │ ├── deploy-immutable-contracts.ts │ │ ├── eip712 │ │ │ ├── Eip712MerkleTree.ts │ │ │ ├── bulk-orders.ts │ │ │ ├── defaults.ts │ │ │ └── utils.ts │ │ ├── encoding.ts │ │ ├── erc721.ts │ │ ├── faucet.ts │ │ ├── order.ts │ │ ├── signedZone.ts │ │ └── types.ts │ │ └── zones │ │ └── immutable-signed-zone │ │ ├── v1 │ │ ├── README.md │ │ └── immutablesignedzone.test.ts │ │ └── v2 │ │ ├── IImmutableSignedZoneV2Harness.t.sol │ │ ├── ImmutableSignedZoneV2.t.sol │ │ ├── ImmutableSignedZoneV2Harness.t.sol │ │ └── README.md └── utils │ ├── DeployAllowlistProxy.sol │ ├── DeployHybridFixtures.ts │ ├── DeployMockMarketPlace.sol │ ├── DeployRegularFixtures.ts │ ├── DeploySCW.sol │ ├── Sign.sol │ └── proxyArtifact.json ├── tsconfig.json ├── wagmi.config.ts └── yarn.lock /.env.example: -------------------------------------------------------------------------------- 1 | IMMUTABLE_NETWORK=0 2 | BLOCKSCOUT_APIKEY= 3 | PRIVATE_KEY= 4 | HD_PATH="m/44'/60'/0'/0/0" 5 | DEPLOYER_ADDRESS= 6 | ROLE_ADMIN= 7 | UPGRADE_ADMIN= 8 | DISTRIBUTE_ADMIN= 9 | ERC20_STAKING_TOKEN= "$tmp" && mv "$tmp" ./package.json 51 | 52 | - name: Install dependencies 53 | run: | 54 | yarn install --frozen-lockfile --network-concurrency 1 55 | 56 | - name: Compile contracts 57 | run: | 58 | yarn compile 59 | 60 | - name: Build dist files 61 | run: | 62 | rm -rf dist && yarn build 63 | 64 | # ! Do NOT remove - this will cause a Sev 0 incident ! 65 | - name: Generate SDK attestation 66 | uses: actions/attest-build-provenance@v1 67 | with: 68 | subject-path: | 69 | dist 70 | contracts 71 | README.md 72 | LICENSE.md 73 | package.json 74 | 75 | - name: Publish package 76 | uses: JS-DevTools/npm-publish@v1 77 | with: 78 | token: ${{ secrets.CONTRACTS_NPM_TOKEN }} 79 | access: public 80 | tag: "latest" 81 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | coverage 4 | coverage.json 5 | typechain 6 | typechain-types 7 | node.json 8 | .idea/ 9 | .vscode/ 10 | package-lock.json 11 | 12 | # Hardhat files 13 | cache 14 | artifacts 15 | 16 | # Build files 17 | dist/ 18 | 19 | # Forge files 20 | foundry-out/ 21 | broadcast/ 22 | 23 | # Apple Mac files 24 | .DS_Store 25 | 26 | # Fuzz 27 | crytic-export 28 | echidna-corpus 29 | medusa-corpus 30 | med-logs 31 | slither.json 32 | slither.sarif 33 | solc-bin -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/forge-std"] 2 | path = lib/forge-std 3 | url = https://github.com/foundry-rs/forge-std 4 | [submodule "lib/solidity-bits"] 5 | path = lib/solidity-bits 6 | url = https://github.com/estarriolvetch/solidity-bits 7 | [submodule "lib/solidity-bytes-utils"] 8 | path = lib/solidity-bytes-utils 9 | url = https://github.com/GNSPS/solidity-bytes-utils 10 | [submodule "lib/openzeppelin-contracts-4.9.3"] 11 | path = lib/openzeppelin-contracts-4.9.3 12 | url = https://github.com/OpenZeppelin/openzeppelin-contracts 13 | [submodule "lib/openzeppelin-contracts-upgradeable-4.9.3"] 14 | path = lib/openzeppelin-contracts-upgradeable-4.9.3 15 | url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable 16 | [submodule "lib/immutable-seaport-1.5.0+im1.3"] 17 | path = lib/immutable-seaport-1.5.0+im1.3 18 | url = https://github.com/immutable/seaport 19 | [submodule "lib/immutable-seaport-core-1.5.0+im1"] 20 | path = lib/immutable-seaport-core-1.5.0+im1 21 | url = https://github.com/immutable/seaport-core 22 | [submodule "lib/openzeppelin-contracts-5.0.2"] 23 | path = lib/openzeppelin-contracts-5.0.2 24 | url = https://github.com/OpenZeppelin/openzeppelin-contracts 25 | [submodule "lib/axelar-gmp-sdk-solidity"] 26 | path = lib/axelar-gmp-sdk-solidity 27 | url = https://github.com/axelarnetwork/axelar-gmp-sdk-solidity 28 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | hardhat.config.ts 2 | scripts 3 | test 4 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v18.17.1 -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | artifacts 3 | cache 4 | coverage* 5 | gasReporterOutput.json 6 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120 3 | } 4 | -------------------------------------------------------------------------------- /.solhint.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["prettier"], 3 | 4 | "rules": { 5 | "code-complexity": ["warn", 7], 6 | "custom-errors": "error", 7 | "explicit-types": ["error"], 8 | "function-max-lines": ["off", 50], 9 | "max-line-length": ["off", 120], 10 | "max-states-count": ["off", 15], 11 | "no-console": "error", 12 | "no-empty-blocks": "warn", 13 | "no-global-import": "warn", 14 | "no-unused-import": "warn", 15 | "no-unused-vars": "warn", 16 | "one-contract-per-file": "warn", 17 | "payable-fallback": "warn", 18 | "reason-string": ["warn", {"maxLength": 32}], 19 | "constructor-syntax": "warn", 20 | 21 | "comprehensive-interface": "off", 22 | "quotes": ["error", "double"], 23 | 24 | "const-name-snakecase": "warn", 25 | "foundry-test-functions": ["off", ["setUp"]], 26 | "func-name-mixedcase": "warn", 27 | "modifier-name-mixedcase": "warn", 28 | "named-parameters-mapping": "warn", 29 | "named-return-values": "off", 30 | "private-vars-leading-underscore": ["off", {"strict": false}], 31 | "use-forbidden-name": "warn", 32 | "var-name-mixedcase": "warn", 33 | "imports-on-top": "warn", 34 | "visibility-modifier-order": "warn", 35 | 36 | "avoid-call-value": "error", 37 | "avoid-low-level-calls": "error", 38 | "avoid-sha3": "error", 39 | "avoid-suicide": "error", 40 | "avoid-throw": "error", 41 | "avoid-tx-origin": "error", 42 | "check-send-result": "error", 43 | "compiler-version": ["error", ">=0.8.19 <0.8.29"], 44 | "func-visibility": ["error", {"ignoreConstructors": true}], 45 | "multiple-sends": "warn", 46 | "no-complex-fallback": "warn", 47 | "no-inline-assembly": "warn", 48 | "not-rely-on-block-hash": "warn", 49 | "not-rely-on-time": "warn", 50 | "reentrancy": "warn", 51 | "state-visibility": "warn", 52 | 53 | "prettier/prettier": "error" 54 | } 55 | } 56 | 57 | -------------------------------------------------------------------------------- /.solhintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /BUILD.md: -------------------------------------------------------------------------------- 1 | # Build and Test Information 2 | 3 | ## Install 4 | 5 | Install dependencies: 6 | 7 | ``` 8 | yarn install 9 | sudo pip3 install slither-analyzer 10 | ``` 11 | 12 | ## Build and Test 13 | 14 | To build and test the contracts: 15 | 16 | ``` 17 | forge test -vvv 18 | yarn test 19 | ``` 20 | 21 | ## Solidity Linter 22 | 23 | To execute solhint: 24 | 25 | ``` 26 | yarn run solhint contracts/**/*.sol 27 | ``` 28 | 29 | To resolve formatting issues: 30 | 31 | ``` 32 | npx prettier --write --plugin=prettier-plugin-solidity 'contracts/**/*.sol' 33 | ``` 34 | 35 | 36 | ## Static Code Analysis 37 | 38 | To run slither: 39 | 40 | ``` 41 | slither --compile-force-framework forge --foundry-out-directory foundry-out . 42 | ``` 43 | 44 | ## Test Coverage 45 | 46 | To check the test coverage based on Foundry tests use: 47 | 48 | ``` 49 | forge coverage 50 | ``` 51 | 52 | ## Performance Tests 53 | 54 | To run tests that check the gas usage: 55 | 56 | ``` 57 | forge test -C perfTest --match-path "./perfTest/**" -vvv --block-gas-limit 1000000000000 58 | ``` 59 | 60 | ## Fuzz Tests 61 | 62 | For ERC721 tests see: [./test/token/erc721/fuzz/README.md](./test/token/erc721/fuzz/README.md) 63 | 64 | ## Deploy 65 | 66 | To deploy the contract with foundry use the following command: 67 | 68 | Additional Links: 69 | 70 | [Operator Allowlist Address](https://docs.immutable.com/docs/zkevm/products/minting/royalties/allowlist-spec/#operator-allowlist-values) 71 | ``` 72 | forge create --rpc-url --constructor-args --private-key --gas-price --priority-gas-price 73 | ``` 74 | 75 | An example for deploying an Immutable ERC721 preset on testnet: 76 | 77 | ``` 78 | forge create \ 79 | --rpc-url "https://rpc.testnet.immutable.com" \ 80 | --constructor-args "0xD509..." \ 81 | "Imaginary Immutable Iguanas" \ 82 | "III" \ 83 | "https://example-base-uri.com/" \ 84 | "https://example-contract-uri.com/" \ 85 | "0x6b969FD89dE634d8DE3271EbE97734FEFfcd58eE" \ 86 | "0xD509..." \ 87 | "2000" \ 88 | --private-key "7e03....." \ 89 | contracts/token/erc721/preset/ImmutableERC721.sol:ImmutableERC721 \ 90 | --gas-price 20000000000 \ 91 | --priority-gas-price 20000000000 92 | 93 | ``` 94 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](http://keepachangelog.com/) 6 | and this project adheres to [Semantic Versioning](http://semver.org/). 7 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | .github @immutable/assets @immutable/traders @immutable/prodsec 2 | 3 | /clients @immutable/assets 4 | /abi @immutable/assets 5 | 6 | /contracts/allowlist @immutable/assets 7 | /contracts/token @immutable/assets 8 | /contracts/trading @immutable/traders 9 | 10 | /test/allowlist @immutable/assets 11 | /test/royalty-enforcement @immutable/assets 12 | /test/token @immutable/assets 13 | /test/seaport @immutable/traders 14 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | Security vulnerabilities should be disclosed to the project maintainers through email to security@immutable.com. 4 | 5 | ## Security Patches 6 | 7 | Security vulnerabilities will be patched as soon as responsibly possible, and published as an advisory on this repository and on the affected npm packages. 8 | 9 | Projects that build on Immutable's contracts are encouraged to clearly state, in their source code and websites, how to be contacted about security issues in the event that a direct notification is considered necessary. We recommend including it in the NatSpec for the contract as `/// @custom:security-contact security@example.com`. 10 | 11 | Additionally, we recommend installing the library through npm and setting up vulnerability alerts such as [Dependabot]. 12 | 13 | [Dependabot]: https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-supply-chain-security#what-is-dependabot 14 | 15 | ### Supported Versions 16 | 17 | Security patches will be released for the latest minor of a given major release. For example, if an issue is found in versions >=4.6.0 and the latest is 4.8.0, the patch will be released only in version 4.8.1. 18 | 19 | Only critical severity bug fixes will be backported to past major releases. 20 | 21 | | Version | Critical security fixes | Other security fixes | 22 | | ------- | ----------------------- | -------------------- | 23 | | 4.x | :white_check_mark: | :white_check_mark: | 24 | | 3.4 | :white_check_mark: | :x: | 25 | | 2.5 | :white_check_mark: | :x: | 26 | | < 2.0 | :x: | :x: | 27 | 28 | Note as well that the Solidity language itself only guarantees security updates for the latest release. 29 | 30 | ## Legal 31 | 32 | Smart contracts are a nascent technology and carry a high level of technical risk and uncertainty. Immutable's zkEVM Contracts are made available under the Apache-2.0 License, which disclaims all warranties in relation to the project and which limits the liability of those that contribute and maintain the project, including Immutable. In your use of this project, you are solely responsible for any use of Immutable zkEVM Contracts and you assume all risks associated with any such use. This Security Policy in no way evidences or represents an on-going duty by any contributor, including Immutable, to correct any flaws or alert you to all or any of the potential risks of utilizing the project. 33 | -------------------------------------------------------------------------------- /abi/README.md: -------------------------------------------------------------------------------- 1 | # Typescript ABIs 2 | 3 | The `contracts` repo exports Typescript ABIs for customers to generate their own contract clients using modern libraries such as `abitype`, `viem` and `wagmi`, for interacting with deployed preset contracts. 4 | 5 | ## Adding a new Typescript ABI 6 | 7 | Typescript ABIs are generated using `@wagmi/cli` and `foundry` build files: 8 | 9 | - `@wagmi/cli` configuration can be found in file `wagmi.config.ts` 10 | - `foundry` build files are available in this repo once built at folder `foundry-out` 11 | 12 | To add a new Typescript ABI: 13 | 14 | - Ensure the JSON ABI is available in folder `foundry-out` 15 | - Update the configuration in `wagmi.config.ts` to add the ABI to the `contracts` array 16 | - Run command `yarn wagmi generate` from the root of the folder 17 | - Ensure the new Typescript ABI is available in `abi/generated.ts` 18 | - Update `abi/index.ts` to rename and export Typescript ABI 19 | 20 | The next published version will contain the new Typescript ABI. 21 | -------------------------------------------------------------------------------- /abi/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | guardedMulticallerAbi as GuardedMulticallerAbi, 3 | immutableErc721Abi as ImmutableERC721Abi, 4 | immutableErc721MintByIdAbi as ImmutableERC721MintByIdAbi, 5 | immutableErc1155Abi as ImmutableERC1155Abi, 6 | paymentSplitterAbi as PaymentSplitterAbi, 7 | guardedMulticaller2Abi as GuardedMulticaller2Abi, 8 | } from "./generated"; 9 | 10 | export { 11 | GuardedMulticallerAbi, 12 | ImmutableERC721Abi, 13 | ImmutableERC721MintByIdAbi, 14 | ImmutableERC1155Abi, 15 | PaymentSplitterAbi, 16 | GuardedMulticaller2Abi, 17 | }; 18 | -------------------------------------------------------------------------------- /audits/allowlist/202402-internal-audit-operator-allow-list.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/immutable/contracts/9bc03be17dc5c52ab541e0d3bf25ba1a32a62889/audits/allowlist/202402-internal-audit-operator-allow-list.pdf -------------------------------------------------------------------------------- /audits/bridge/flow-rate/202310-external-audit-trail-of-bits.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/immutable/contracts/9bc03be17dc5c52ab541e0d3bf25ba1a32a62889/audits/bridge/flow-rate/202310-external-audit-trail-of-bits.pdf -------------------------------------------------------------------------------- /audits/bridge/x/202402-internal-audit-immutablex-bridge-v4.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/immutable/contracts/9bc03be17dc5c52ab541e0d3bf25ba1a32a62889/audits/bridge/x/202402-internal-audit-immutablex-bridge-v4.pdf -------------------------------------------------------------------------------- /audits/bridge/zkevm/202312-external-audit-trail-of-bits-zkevmbridgecontracts.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/immutable/contracts/9bc03be17dc5c52ab541e0d3bf25ba1a32a62889/audits/bridge/zkevm/202312-external-audit-trail-of-bits-zkevmbridgecontracts.pdf -------------------------------------------------------------------------------- /audits/bridge/zkevm/202409-external-perimeter-fuzzing.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/immutable/contracts/9bc03be17dc5c52ab541e0d3bf25ba1a32a62889/audits/bridge/zkevm/202409-external-perimeter-fuzzing.pdf -------------------------------------------------------------------------------- /audits/deployer/202405-internal-audit-deployer.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/immutable/contracts/9bc03be17dc5c52ab541e0d3bf25ba1a32a62889/audits/deployer/202405-internal-audit-deployer.pdf -------------------------------------------------------------------------------- /audits/games/gems/202404-internal-audit-gm-game.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/immutable/contracts/9bc03be17dc5c52ab541e0d3bf25ba1a32a62889/audits/games/gems/202404-internal-audit-gm-game.pdf -------------------------------------------------------------------------------- /audits/multicall/202309-external-audit-multicaller.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/immutable/contracts/9bc03be17dc5c52ab541e0d3bf25ba1a32a62889/audits/multicall/202309-external-audit-multicaller.pdf -------------------------------------------------------------------------------- /audits/multicall/202309-threat-model-multicaller/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/immutable/contracts/9bc03be17dc5c52ab541e0d3bf25ba1a32a62889/audits/multicall/202309-threat-model-multicaller/architecture.png -------------------------------------------------------------------------------- /audits/multicall/202309-threat-model-multicaller/high-level.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/immutable/contracts/9bc03be17dc5c52ab541e0d3bf25ba1a32a62889/audits/multicall/202309-threat-model-multicaller/high-level.png -------------------------------------------------------------------------------- /audits/token/202309-threat-model-preset-erc721/immutableERC721.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/immutable/contracts/9bc03be17dc5c52ab541e0d3bf25ba1a32a62889/audits/token/202309-threat-model-preset-erc721/immutableERC721.png -------------------------------------------------------------------------------- /audits/token/202312-threat-model-preset-erc1155/ImmutableERC1155.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/immutable/contracts/9bc03be17dc5c52ab541e0d3bf25ba1a32a62889/audits/token/202312-threat-model-preset-erc1155/ImmutableERC1155.jpg -------------------------------------------------------------------------------- /audits/token/202402-internal-audit-preset-erc721.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/immutable/contracts/9bc03be17dc5c52ab541e0d3bf25ba1a32a62889/audits/token/202402-internal-audit-preset-erc721.pdf -------------------------------------------------------------------------------- /audits/token/202403-internal-audit-immutable-erc20.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/immutable/contracts/9bc03be17dc5c52ab541e0d3bf25ba1a32a62889/audits/token/202403-internal-audit-immutable-erc20.pdf -------------------------------------------------------------------------------- /audits/token/202404-internal-audit-immutable-erc20.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/immutable/contracts/9bc03be17dc5c52ab541e0d3bf25ba1a32a62889/audits/token/202404-internal-audit-immutable-erc20.pdf -------------------------------------------------------------------------------- /audits/token/202404-threat-model-preset-immutable-erc20-minter-burner-permit/ImmutableERC20MinterBurnerPermit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/immutable/contracts/9bc03be17dc5c52ab541e0d3bf25ba1a32a62889/audits/token/202404-threat-model-preset-immutable-erc20-minter-burner-permit/ImmutableERC20MinterBurnerPermit.png -------------------------------------------------------------------------------- /audits/token/202502-internal-audit-preset-erc721v2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/immutable/contracts/9bc03be17dc5c52ab541e0d3bf25ba1a32a62889/audits/token/202502-internal-audit-preset-erc721v2.pdf -------------------------------------------------------------------------------- /audits/trading/202405-internal-audit-immutable-signed-zone-v2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/immutable/contracts/9bc03be17dc5c52ab541e0d3bf25ba1a32a62889/audits/trading/202405-internal-audit-immutable-signed-zone-v2.pdf -------------------------------------------------------------------------------- /clients/config/overrides.ts: -------------------------------------------------------------------------------- 1 | import { CallOverrides } from "@ethersproject/contracts"; 2 | 3 | // https://docs.immutable.com/docs/zkEVM/architecture/gas-config 4 | export const defaultGasOverrides: CallOverrides = { 5 | maxPriorityFeePerGas: 10e9, // 10 Gwei 6 | maxFeePerGas: 15e9, 7 | gasLimit: 200000, // Expected when setting the above properties 8 | }; 9 | -------------------------------------------------------------------------------- /clients/index.ts: -------------------------------------------------------------------------------- 1 | export { ERC20Client } from "./erc20"; 2 | export { ERC721Client } from "./erc721"; 3 | export { ERC721MintByIDClient } from "./erc721-mint-by-id"; 4 | -------------------------------------------------------------------------------- /contracts/access/IERC173.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: Apache 2.0 2 | // solhint-disable compiler-version 3 | pragma solidity ^0.8.0; 4 | 5 | import {IERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; 6 | 7 | interface IERC173 is IERC165 { 8 | /// @dev This emits when ownership of a contract changes. 9 | event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); 10 | 11 | /// @notice Get the address of the owner 12 | /// @return The address of the owner. 13 | function owner() external view returns (address); 14 | 15 | /// @notice Set the address of the new owner of the contract 16 | /// @dev Set _newOwner to address(0) to renounce any ownership. 17 | /// @param _newOwner The address of the new owner of the contract 18 | function transferOwnership(address _newOwner) external; 19 | } 20 | -------------------------------------------------------------------------------- /contracts/access/IMintingAccessControl.sol: -------------------------------------------------------------------------------- 1 | // Copyright Immutable Pty Ltd 2018 - 2023 2 | // SPDX-License-Identifier: Apache 2.0 3 | pragma solidity >=0.8.19 <0.8.29; 4 | 5 | import {IAccessControlEnumerable} from "@openzeppelin/contracts/access/IAccessControlEnumerable.sol"; 6 | 7 | interface IMintingAccessControl is IAccessControlEnumerable { 8 | /** 9 | * @notice Role to mint tokens 10 | */ 11 | function MINTER_ROLE() external returns (bytes32); 12 | 13 | /** 14 | * @notice Allows admin grant `user` `MINTER` role 15 | * @param user The address to grant the `MINTER` role to 16 | */ 17 | function grantMinterRole(address user) external; 18 | 19 | /** 20 | * @notice Allows admin to revoke `MINTER_ROLE` role from `user` 21 | * @param user The address to revoke the `MINTER` role from 22 | */ 23 | function revokeMinterRole(address user) external; 24 | 25 | /** 26 | * @notice Returns the addresses which have DEFAULT_ADMIN_ROLE 27 | */ 28 | function getAdmins() external view returns (address[] memory); 29 | } 30 | -------------------------------------------------------------------------------- /contracts/access/MintingAccessControl.sol: -------------------------------------------------------------------------------- 1 | // Copyright Immutable Pty Ltd 2018 - 2023 2 | // SPDX-License-Identifier: Apache 2.0 3 | pragma solidity >=0.8.19 <0.8.29; 4 | 5 | import {AccessControlEnumerable} from "@openzeppelin/contracts/access/AccessControlEnumerable.sol"; 6 | 7 | abstract contract MintingAccessControl is AccessControlEnumerable { 8 | /// @notice Role to mint tokens 9 | bytes32 public constant MINTER_ROLE = bytes32("MINTER_ROLE"); 10 | 11 | /** 12 | * @notice Allows admin grant `user` `MINTER` role 13 | * @param user The address to grant the `MINTER` role to 14 | */ 15 | function grantMinterRole(address user) public onlyRole(DEFAULT_ADMIN_ROLE) { 16 | grantRole(MINTER_ROLE, user); 17 | } 18 | 19 | /** 20 | * @notice Allows admin to revoke `MINTER_ROLE` role from `user` 21 | * @param user The address to revoke the `MINTER` role from 22 | */ 23 | function revokeMinterRole(address user) public onlyRole(DEFAULT_ADMIN_ROLE) { 24 | revokeRole(MINTER_ROLE, user); 25 | } 26 | 27 | /** 28 | * @notice Returns the addresses which have DEFAULT_ADMIN_ROLE 29 | */ 30 | function getAdmins() public view returns (address[] memory) { 31 | uint256 adminCount = getRoleMemberCount(DEFAULT_ADMIN_ROLE); 32 | address[] memory admins = new address[](adminCount); 33 | for (uint256 i; i < adminCount; i++) { 34 | admins[i] = getRoleMember(DEFAULT_ADMIN_ROLE, i); 35 | } 36 | return admins; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /contracts/allowlist/IOperatorAllowlist.sol: -------------------------------------------------------------------------------- 1 | // Copyright Immutable Pty Ltd 2018 - 2023 2 | // SPDX-License-Identifier: Apache 2.0 3 | pragma solidity >=0.8.19 <0.8.29; 4 | 5 | /** 6 | * @notice Required interface of an OperatorAllowlist compliant contract 7 | */ 8 | interface IOperatorAllowlist { 9 | /** 10 | * @notice Returns true if an address is Allowlisted false otherwise 11 | * @param target the address to be checked against the Allowlist 12 | */ 13 | function isAllowlisted(address target) external view returns (bool); 14 | } 15 | -------------------------------------------------------------------------------- /contracts/allowlist/IWalletProxy.sol: -------------------------------------------------------------------------------- 1 | // Copyright Immutable Pty Ltd 2018 - 2023 2 | // SPDX-License-Identifier: Apache 2.0 3 | pragma solidity >=0.8.19 <0.8.29; 4 | 5 | // Interface to retrieve the implementation stored inside the Proxy contract 6 | /// Interface for Passport Wallet's proxy contract. 7 | interface IWalletProxy { 8 | // Returns the current implementation address used by the proxy contract 9 | // solhint-disable-next-line func-name-mixedcase 10 | function PROXY_getImplementation() external view returns (address); 11 | } 12 | -------------------------------------------------------------------------------- /contracts/allowlist/oal-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/immutable/contracts/9bc03be17dc5c52ab541e0d3bf25ba1a32a62889/contracts/allowlist/oal-architecture.png -------------------------------------------------------------------------------- /contracts/bridge/x/v3/README.md: -------------------------------------------------------------------------------- 1 | # Immutable X Contracts 2 | 3 | ## Immutable Contract Addresses 4 | 5 | | Environment/Network | Core (StarkEx Bridge) Contract | User Registration Contract | 6 | |--------------------------|-------------------------------------------------------------------------------------------------------------------------------| ----------------------------------------------------------------------------------------------------------------------------- | 7 | | **Sandbox (Sepolia)** | [0x2d5C349fD8464DA06a3f90b4B0E9195F3d1b7F98](https://sepolia.etherscan.io/address/0x2d5C349fD8464DA06a3f90b4B0E9195F3d1b7F98) | [0x1c97ada273c9a52253f463042f29117090cd7d83](https://goerli.etherscan.io/address/0x1C97Ada273C9A52253f463042f29117090Cd7D83) | 8 | | **Production (Mainnet)** | [0x5fdcca53617f4d2b9134b29090c87d01058e27e9](https://etherscan.io/address/0x5FDCCA53617f4d2b9134B29090C87D01058e27e9) | [0x72a06bf2a1CE5e39cBA06c0CAb824960B587d64c](https://etherscan.io/address/0x72a06bf2a1CE5e39cBA06c0CAb824960B587d64c) | 9 | -------------------------------------------------------------------------------- /contracts/bridge/x/v3/RegistrationV3.sol: -------------------------------------------------------------------------------- 1 | // Copyright (c) Immutable Pty Ltd 2018 - 2024 2 | // SPDX-License-Identifier: MIT 3 | // solhint-disable compiler-version 4 | pragma solidity ^0.8.11; 5 | 6 | import {CoreV3} from "./CoreV3.sol"; 7 | 8 | contract RegistrationV3 { 9 | CoreV3 public immutable imx; 10 | 11 | constructor(CoreV3 _imx) { 12 | imx = _imx; 13 | } 14 | 15 | function registerAndDepositNft( 16 | address ethKey, 17 | uint256 starkKey, 18 | bytes calldata signature, 19 | uint256 assetType, 20 | uint256 vaultId, 21 | uint256 tokenId 22 | ) external { 23 | imx.registerUser(ethKey, starkKey, signature); 24 | imx.depositNft(starkKey, assetType, vaultId, tokenId); 25 | } 26 | 27 | function registerAndWithdraw( 28 | address ethKey, 29 | uint256 starkKey, 30 | bytes calldata signature, 31 | uint256 assetType 32 | ) external { 33 | imx.registerUser(ethKey, starkKey, signature); 34 | imx.withdraw(starkKey, assetType); 35 | } 36 | 37 | function registerAndWithdrawTo( 38 | address ethKey, 39 | uint256 starkKey, 40 | bytes calldata signature, 41 | uint256 assetType, 42 | address recipient 43 | ) external { 44 | imx.registerUser(ethKey, starkKey, signature); 45 | imx.withdrawTo(starkKey, assetType, recipient); 46 | } 47 | 48 | function registerAndWithdrawNft( 49 | address ethKey, 50 | uint256 starkKey, 51 | bytes calldata signature, 52 | uint256 assetType, 53 | uint256 tokenId 54 | ) external { 55 | imx.registerUser(ethKey, starkKey, signature); 56 | imx.withdrawNft(starkKey, assetType, tokenId); 57 | } 58 | 59 | function registerAndWithdrawNftTo( 60 | address ethKey, 61 | uint256 starkKey, 62 | bytes calldata signature, 63 | uint256 assetType, 64 | uint256 tokenId, 65 | address recipient 66 | ) external { 67 | imx.registerUser(ethKey, starkKey, signature); 68 | imx.withdrawNftTo(starkKey, assetType, tokenId, recipient); 69 | } 70 | 71 | function regsiterAndWithdrawAndMint( 72 | address ethKey, 73 | uint256 starkKey, 74 | bytes calldata signature, 75 | uint256 assetType, 76 | bytes calldata mintingBlob 77 | ) external { 78 | imx.registerUser(ethKey, starkKey, signature); 79 | imx.withdrawAndMint(starkKey, assetType, mintingBlob); 80 | } 81 | 82 | function isRegistered(uint256 starkKey) public view returns (bool) { 83 | return imx.getEthKey(starkKey) != address(0); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /contracts/bridge/x/v4/README.md: -------------------------------------------------------------------------------- 1 | # Immutable X Contracts 2 | 3 | The immutable x v4 contracts provide functionality for enabling immutable-x users to off-ramp assets from the StarkEx network to Ethereum network. 4 | From v4 onwards, Starkex changed the deposit, withdrawal and registration flows to be more efficient and more trustless, and this contract acts as a wrapper around the StarkEx contract to provide a more user-friendly interface. 5 | 6 | # Status 7 | 8 | Contract audits and threat models: 9 | 10 | | Description | Date |Version Audited | Link to Report | 11 | |----------------|------------------|-----------------|----------------| 12 | | Internal audit | Feb 16, 2024 | [4f0fd701e357e2bc9c94d13a6a46babdb6a5fbf2](https://github.com/immutable/contracts/tree/4f0fd701e357e2bc9c94d13a6a46babdb6a5fbf2/contracts/bridge/x/v4) | [202402-internal-audit-immutablex-bridge-v4](../../../../audits/bridge/x/202402-internal-audit-immutablex-bridge-v4.pdf) | 13 | 14 | 15 | ## Immutable Contract Addresses 16 | 17 | | Environment/Network | Core (StarkEx Bridge) Contract | User Registration Contract | 18 | |--------------------------|--------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------| 19 | | **Dev (Sepolia)** | [0x590c809bd5ff50dcb39e4320b60139b29b880174](https://sepolia.etherscan.io/address/0x590c809bd5ff50dcb39e4320b60139b29b880174) | [0x31D79A2b1E0150b73D243826b93ba7BCaE7fCB60](https://sepolia.etherscan.io/address/0x31D79A2b1E0150b73D243826b93ba7BCaE7fCB60) | 20 | | **Sandbox (Sepolia)** | [0x2d5C349fD8464DA06a3f90b4B0E9195F3d1b7F98](https://sepolia.etherscan.io/address/0x2d5C349fD8464DA06a3f90b4B0E9195F3d1b7F98) | [0xd1527c65c6287ec5ab816d328eb83bb4cb690e92](https://sepolia.etherscan.io/address/0xd1527c65c6287ec5ab816d328eb83bb4cb690e92) | 21 | | **Production (Mainnet)** | [0x5fdcca53617f4d2b9134b29090c87d01058e27e9](https://etherscan.io/address/0x5FDCCA53617f4d2b9134B29090C87D01058e27e9) | [0xac88a57943b5BBa1ecd931F8494cAd0B7F717590](https://etherscan.io/address/0xac88a57943b5BBa1ecd931F8494cAd0B7F717590) | 22 | 23 | ## RegistrationV4 24 | 25 | This contract is a wrapper around the StarkEx contract to provide a more user-friendly interface for executing multiple transactions on the StarkEx contract at once. 26 | 27 | ## CoreV4 28 | 29 | This contract is an interface for the StarkEx Core contract v4 version. 30 | It is used to interact with the StarkEx Core contract from the Registration contract. 31 | The Core contract is used to register and withdraw users and assets from the StarkEx system. 32 | -------------------------------------------------------------------------------- /contracts/bridge/x/v4/RegistrationV4.sol: -------------------------------------------------------------------------------- 1 | // Copyright (c) Immutable Pty Ltd 2018 - 2024 2 | // SPDX-License-Identifier: MIT 3 | pragma solidity >=0.8.19 <0.8.29; 4 | 5 | import {CoreV4} from "./CoreV4.sol"; 6 | 7 | /** 8 | * Emitted when there are no funds to withdraw when calling `withdrawAll`. 9 | */ 10 | error NoFundsToWithdraw(uint256 ethKey, uint256 starkKey); 11 | 12 | /** 13 | * RegistrationV4 is a wrapper around the StarkEx contract to provide a more user-friendly interface for executing multiple transactions on the StarkEx contract at once. 14 | * This contract is not upgradeable. If an issue is found with this contract, a new version will be deployed. 15 | */ 16 | contract RegistrationV4 { 17 | CoreV4 public immutable imx; 18 | 19 | constructor(address payable _imx) { 20 | imx = CoreV4(_imx); 21 | } 22 | 23 | function registerAndWithdrawAll( 24 | address ethKey, 25 | uint256 starkKey, 26 | bytes calldata signature, 27 | uint256 assetType 28 | ) external { 29 | if (!isRegistered(starkKey)) { 30 | imx.registerEthAddress(ethKey, starkKey, signature); 31 | } 32 | withdrawAll(uint160(ethKey), starkKey, assetType); 33 | } 34 | 35 | function withdrawAll(uint256 ethKey, uint256 starkKey, uint256 assetType) public { 36 | uint256 ethKeyBalance = imx.getWithdrawalBalance(ethKey, assetType); 37 | uint256 starkKeyBalance = imx.getWithdrawalBalance(starkKey, assetType); 38 | if (ethKeyBalance == 0 && starkKeyBalance == 0) { 39 | revert NoFundsToWithdraw(ethKey, starkKey); 40 | } 41 | 42 | if (ethKeyBalance > 0) { 43 | imx.withdraw(ethKey, assetType); 44 | } 45 | 46 | if (starkKeyBalance > 0) { 47 | imx.withdraw(starkKey, assetType); 48 | } 49 | } 50 | 51 | function registerAndWithdrawNft( 52 | address ethKey, 53 | uint256 starkKey, 54 | bytes calldata signature, 55 | uint256 assetType, 56 | uint256 tokenId 57 | ) external { 58 | if (!isRegistered(starkKey)) { 59 | imx.registerEthAddress(ethKey, starkKey, signature); 60 | } 61 | imx.withdrawNft(starkKey, assetType, tokenId); 62 | } 63 | 64 | function registerWithdrawAndMint( 65 | address ethKey, 66 | uint256 starkKey, 67 | bytes calldata signature, 68 | uint256 assetType, 69 | bytes calldata mintingBlob 70 | ) external { 71 | if (!isRegistered(starkKey)) { 72 | imx.registerEthAddress(ethKey, starkKey, signature); 73 | } 74 | imx.withdrawAndMint(starkKey, assetType, mintingBlob); 75 | } 76 | 77 | function getVersion() external view returns (string memory) { 78 | return imx.VERSION(); 79 | } 80 | 81 | function isRegistered(uint256 starkKey) public view returns (bool) { 82 | return imx.getEthKey(starkKey) != address(0); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /contracts/deployer/create/OwnableCreateDeploy.sol: -------------------------------------------------------------------------------- 1 | // Copyright Immutable Pty Ltd 2018 - 2024 2 | // SPDX-License-Identifier: MIT 3 | pragma solidity >=0.8.19 <0.8.29; 4 | 5 | /** 6 | * @title OwnableCreateDeploy Contract 7 | * @notice This contract deploys new contracts using the `CREATE` opcode and is used as part of 8 | * the `CREATE3` deployment method. 9 | * @dev The contract can only be called by the owner of the contract, which is set to the deployer of the contract. 10 | * @dev This is a copy of the `CreateDeploy` contract in Axelar's SDK, with a modification that adds basic access control to the deployment function. 11 | * see: https://github.com/axelarnetwork/axelar-gmp-sdk-solidity/blob/5f15a1036215f8b9c8eeb6438d352172b430dd38/contracts/deploy/CreateDeploy.sol 12 | */ 13 | contract OwnableCreateDeploy { 14 | // Address that is authorised to call the deploy function. 15 | address private immutable owner; 16 | 17 | constructor() { 18 | owner = msg.sender; 19 | } 20 | /** 21 | * @dev Deploys a new contract with the specified bytecode using the `CREATE` opcode. 22 | * @param bytecode The bytecode of the contract to be deployed 23 | */ 24 | // slither-disable-next-line locked-ether 25 | function deploy(bytes memory bytecode) external payable { 26 | // solhint-disable-next-line custom-errors, reason-string 27 | require(msg.sender == owner, "CreateDeploy: caller is not the owner"); 28 | // solhint-disable no-inline-assembly 29 | assembly { 30 | if iszero(create(callvalue(), add(bytecode, 32), mload(bytecode))) { 31 | revert(0, 0) 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /contracts/deployer/create3/OwnableCreate3.sol: -------------------------------------------------------------------------------- 1 | // Copyright Immutable Pty Ltd 2018 - 2024 2 | // SPDX-License-Identifier: MIT 3 | pragma solidity >=0.8.19 <0.8.29; 4 | 5 | import {IDeploy} from "@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IDeploy.sol"; 6 | import {ContractAddress} from "@axelar-network/axelar-gmp-sdk-solidity/contracts/libs/ContractAddress.sol"; 7 | 8 | import {OwnableCreate3Address} from "./OwnableCreate3Address.sol"; 9 | import {OwnableCreateDeploy} from "../create/OwnableCreateDeploy.sol"; 10 | 11 | /** 12 | * @title OwnableCreate3 contract 13 | * @notice This contract can be used to deploy a contract with a deterministic address that depends only on 14 | * the deployer address and deployment salt, not the contract bytecode and constructor parameters. 15 | * @dev This contract is a copy of the `Create3` contract in Axelar's SDK, with a minor modification to use `OwnableCreateDeploy` instead of `CreateDeploy`. 16 | * See: https://github.com/axelarnetwork/axelar-gmp-sdk-solidity/blob/1d3dd9a42abd37a315c18ec51163ddc5e5a08c21/contracts/deploy/Create3.sol 17 | */ 18 | contract OwnableCreate3 is OwnableCreate3Address, IDeploy { 19 | using ContractAddress for address; 20 | 21 | /** 22 | * @notice Deploys a new contract using the `CREATE3` method. 23 | * @dev This function first deploys the CreateDeploy contract using 24 | * the `CREATE2` opcode and then utilizes the CreateDeploy to deploy the 25 | * new contract with the `CREATE` opcode. 26 | * @param bytecode The bytecode of the contract to be deployed 27 | * @param deploySalt A salt to influence the contract address 28 | * @return deployed The address of the deployed contract 29 | */ 30 | // Slither 0.10.4 is mistakenly seeing this as dead code. It is called 31 | // from OwnableCreate3Deployer.deploy and could be called from other contracts. 32 | // slither-disable-next-line dead-code 33 | function _create3(bytes memory bytecode, bytes32 deploySalt) internal returns (address deployed) { 34 | deployed = _create3Address(deploySalt); 35 | 36 | if (bytecode.length == 0) revert EmptyBytecode(); 37 | if (deployed.isContract()) revert AlreadyDeployed(); 38 | 39 | // Deploy using create2 40 | OwnableCreateDeploy create = new OwnableCreateDeploy{salt: deploySalt}(); 41 | 42 | if (address(create) == address(0)) revert DeployFailed(); 43 | 44 | // Deploy using create 45 | create.deploy(bytecode); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /contracts/deployer/create3/OwnableCreate3Address.sol: -------------------------------------------------------------------------------- 1 | // Copyright Immutable Pty Ltd 2018 - 2024 2 | // SPDX-License-Identifier: MIT 3 | pragma solidity >=0.8.19 <0.8.29; 4 | 5 | import {OwnableCreateDeploy} from "../create/OwnableCreateDeploy.sol"; 6 | 7 | /** 8 | * @title OwnableCreate3Address contract 9 | * @notice This contract can be used to predict the deterministic deployment address of a contract deployed with the `CREATE3` technique. 10 | * @dev This contract is a copy of the `Create3Address` contract in Axelar's SDK, with a minor modification to use `OwnableCreateDeploy` instead of `CreateDeploy`. 11 | * See: https://github.com/axelarnetwork/axelar-gmp-sdk-solidity/blob/1d3dd9a42abd37a315c18ec51163ddc5e5a08c21/contracts/deploy/Create3Address.sol 12 | */ 13 | abstract contract OwnableCreate3Address { 14 | /// @dev bytecode hash of the CreateDeploy helper contract 15 | bytes32 internal immutable createDeployBytecodeHash; 16 | 17 | constructor() { 18 | // Slither is mistakenly seeing the expansion of type(OwnableCreateDeploy).creationCode 19 | // as a very large number. 20 | // slither-disable-next-line too-many-digits 21 | createDeployBytecodeHash = keccak256(type(OwnableCreateDeploy).creationCode); 22 | } 23 | 24 | /** 25 | * @notice Compute the deployed address that will result from the `CREATE3` method. 26 | * @param deploySalt A salt to influence the contract address 27 | * @return deployed The deterministic contract address if it was deployed 28 | */ 29 | function _create3Address(bytes32 deploySalt) internal view returns (address deployed) { 30 | address deployer = address( 31 | uint160(uint256(keccak256(abi.encodePacked(hex"ff", address(this), deploySalt, createDeployBytecodeHash)))) 32 | ); 33 | 34 | deployed = address(uint160(uint256(keccak256(abi.encodePacked(hex"d694", deployer, hex"01"))))); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /contracts/errors/Errors.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: Apache 2.0 2 | pragma solidity >=0.8.19 <0.8.29; 3 | 4 | interface IImmutableERC721Errors { 5 | /// @dev Caller tried to mint an already burned token 6 | error IImmutableERC721TokenAlreadyBurned(uint256 tokenId); 7 | 8 | /// @dev Caller tried to mint an already burned token 9 | error IImmutableERC721SendingToZerothAddress(); 10 | 11 | /// @dev Caller tried to mint an already burned token 12 | error IImmutableERC721MismatchedTransferLengths(); 13 | 14 | /// @dev Caller tried to mint a tokenid that is above the hybrid threshold 15 | error IImmutableERC721IDAboveThreshold(uint256 tokenId); 16 | 17 | /// @dev Caller is not approved or owner 18 | error IImmutableERC721NotOwnerOrOperator(uint256 tokenId); 19 | 20 | /// @dev Current token owner is not what was expected 21 | error IImmutableERC721MismatchedTokenOwner(uint256 tokenId, address currentOwner); 22 | 23 | /// @dev Signer is zeroth address 24 | error SignerCannotBeZerothAddress(); 25 | 26 | /// @dev Deadline exceeded for permit 27 | error PermitExpired(); 28 | 29 | /// @dev Derived signature is invalid (EIP721 and EIP1271) 30 | error InvalidSignature(); 31 | } 32 | 33 | interface OperatorAllowlistEnforcementErrors { 34 | /// @dev Error thrown when the operatorAllowlist address does not implement the IOperatorAllowlist interface 35 | error AllowlistDoesNotImplementIOperatorAllowlist(); 36 | 37 | /// @dev Error thrown when calling address is not OperatorAllowlist 38 | error CallerNotInAllowlist(address caller); 39 | 40 | /// @dev Error thrown when 'from' address is not OperatorAllowlist 41 | error TransferFromNotInAllowlist(address from); 42 | 43 | /// @dev Error thrown when 'to' address is not OperatorAllowlist 44 | error TransferToNotInAllowlist(address to); 45 | 46 | /// @dev Error thrown when approve target is not OperatorAllowlist 47 | error ApproveTargetNotInAllowlist(address target); 48 | 49 | /// @dev Error thrown when approve target is not OperatorAllowlist 50 | error ApproverNotInAllowlist(address approver); 51 | } 52 | 53 | interface IImmutableERC1155Errors { 54 | /// @dev Deadline exceeded for permit 55 | error PermitExpired(); 56 | 57 | /// @dev Derived signature is invalid (EIP721 and EIP1271) 58 | error InvalidSignature(); 59 | } 60 | -------------------------------------------------------------------------------- /contracts/errors/PaymentSplitterErrors.sol: -------------------------------------------------------------------------------- 1 | // Copyright Immutable Pty Ltd 2018 - 2024 2 | //SPDX-License-Identifier: Apache 2.0 3 | pragma solidity >=0.8.19 <0.8.29; 4 | 5 | interface IPaymentSplitterErrors { 6 | /// @dev caller tried to add payees with shares of unequal length 7 | error PaymentSplitterLengthMismatchSharesPayees(); 8 | 9 | /// @dev caller tried to add payees with length of 0 10 | error PaymentSplitterNoPayeesAdded(); 11 | 12 | /// @dev caller tried to add payee with zeroth address 13 | error PaymentSplitterPayeeZerothAddress(); 14 | 15 | /// @dev caller tried to add payee with 0 shares 16 | error PaymentSplitterPayeeZeroShares(); 17 | 18 | /// @dev caller tried to add shares to account with existing shares 19 | error PaymentSplitterSharesAlreadyExistForPayee(); 20 | } 21 | -------------------------------------------------------------------------------- /contracts/games/gems/GemGame.sol: -------------------------------------------------------------------------------- 1 | // Copyright (c) Immutable Pty Ltd 2018 - 2024 2 | // SPDX-License-Identifier: Apache 2 3 | // solhint-disable not-rely-on-time 4 | 5 | pragma solidity >=0.8.19 <0.8.29; 6 | 7 | import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; 8 | import {Pausable} from "@openzeppelin/contracts/security/Pausable.sol"; 9 | 10 | error Unauthorized(); 11 | error ContractPaused(); 12 | 13 | /** 14 | * @title GemGame - A simple contract that emits an event for the purpose of indexing off-chain 15 | * @author Immutable 16 | * @dev The GemGame contract is not designed to be upgradeable or extended 17 | */ 18 | contract GemGame is AccessControl, Pausable { 19 | /// @notice Indicates that an account has earned a gem 20 | event GemEarned(address indexed account, uint256 timestamp); 21 | 22 | /// @notice Role to allow pausing the contract 23 | bytes32 private constant _PAUSE = keccak256("PAUSE"); 24 | 25 | /// @notice Role to allow unpausing the contract 26 | bytes32 private constant _UNPAUSE = keccak256("UNPAUSE"); 27 | 28 | /** 29 | * @notice Sets the DEFAULT_ADMIN, PAUSE and UNPAUSE roles 30 | * @param _admin The address for the admin role 31 | * @param _pauser The address for the pauser role 32 | * @param _unpauser The address for the unpauser role 33 | */ 34 | constructor(address _admin, address _pauser, address _unpauser) { 35 | _grantRole(DEFAULT_ADMIN_ROLE, _admin); 36 | _grantRole(_PAUSE, _pauser); 37 | _grantRole(_UNPAUSE, _unpauser); 38 | } 39 | 40 | /** 41 | * @notice Pauses the contract 42 | */ 43 | function pause() external { 44 | if (!hasRole(_PAUSE, msg.sender)) revert Unauthorized(); 45 | _pause(); 46 | } 47 | 48 | /** 49 | * @notice Unpauses the contract 50 | */ 51 | function unpause() external { 52 | if (!hasRole(_UNPAUSE, msg.sender)) revert Unauthorized(); 53 | _unpause(); 54 | } 55 | 56 | /** 57 | * @notice Function that emits a `GemEarned` event 58 | */ 59 | function earnGem() external { 60 | if (paused()) revert ContractPaused(); 61 | emit GemEarned(msg.sender, block.timestamp); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /contracts/games/gems/README.md: -------------------------------------------------------------------------------- 1 | # Gem (GM) Game 2 | 3 | The GemGame contract emits a single event for the purpose of indexing off-chain. 4 | 5 | ## Immutable Contract Addresses 6 | 7 | | Environment/Network | Deployment Address | Commit Hash | 8 | |--------------------------|--------------------|-------------| 9 | | imtbl-zkevm-testnet | 0xe19453c43b35563B8105F8B88DeEDcde999671Cb | [97f00aa69c7cfadddc67ca271593eaa0b1eac940](https://github.com/immutable/contracts/tree/97f00aa69c7cfadddc67ca271593eaa0b1eac940/contracts/games/gems) | 10 | | imtbl-zkevm-mainnet | 0x3f04d7a7297d5535595ee0a30071008b54e62a03 | [97f00aa69c7cfadddc67ca271593eaa0b1eac940](https://github.com/immutable/contracts/tree/97f00aa69c7cfadddc67ca271593eaa0b1eac940/contracts/games/gems) | 11 | 12 | # Status 13 | 14 | Contract threat models and audits: 15 | 16 | | Description | Date |Version Audited | Link to Report | 17 | |---------------------------|------------------|-----------------|----------------| 18 | | Internal audit | April 15, 2024 | [97f00aa69c7cfadddc67ca271593eaa0b1eac940](https://github.com/immutable/contracts/tree/97f00aa69c7cfadddc67ca271593eaa0b1eac940/contracts/games/gems) | [202404-internal-audit-gm-game](../../../../audits/games/gems/202404-internal-audit-gm-game.pdf) | | 19 | | Not audited and no threat model | - | - | - | 20 | 21 | 22 | 23 | # Deployment 24 | 25 | **Deploy and verify using CREATE3 factory contract:** 26 | 27 | This repo includes a script for deploying via a CREATE3 factory contract. The script is defined as a test contract as per the examples [here](https://book.getfoundry.sh/reference/forge/forge-script#examples) and can be found in `./script/games/gems/DeployGemGame.sol`. 28 | 29 | See the `.env.example` for required environment variables. 30 | 31 | ```sh 32 | forge script script/games/gems/DeployGemGame.sol --tc DeployGemGame --sig "deploy()" -vvv --rpc-url {rpc-url} --broadcast --verifier-url https://explorer.immutable.com/api --verifier blockscout --verify --gas-price 10gwei 33 | ``` 34 | 35 | Optionally, you can also specify `--ledger` or `--trezor` for hardware deployments. See docs [here](https://book.getfoundry.sh/reference/forge/forge-script#wallet-options---hardware-wallet). 36 | -------------------------------------------------------------------------------- /contracts/mocks/MockDisguisedEOA.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.19 <0.8.29; 3 | 4 | import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; 5 | 6 | // Used in CREATE2 vector 7 | contract MockDisguisedEOA { 8 | IERC721 public immutable tokenAddress; 9 | 10 | constructor(IERC721 _tokenAddress) { 11 | tokenAddress = _tokenAddress; 12 | } 13 | 14 | /// @notice This code is only for testing purposes. Do not use similar 15 | /// @notice constructions in production code as they are open to attack. 16 | /// @dev For details see: https://github.com/crytic/slither/wiki/Detector-Documentation#arbitrary-from-in-transferfrom 17 | function executeTransfer(address from, address recipient, uint256 _tokenId) external { 18 | // slither-disable-next-line arbitrary-send-erc20 19 | tokenAddress.transferFrom(from, recipient, _tokenId); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /contracts/mocks/MockEIP1271Wallet.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.19 <0.8.29; 3 | 4 | import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; 5 | import {IERC1271} from "@openzeppelin/contracts/interfaces/IERC1271.sol"; 6 | import {IERC721Receiver} from "@openzeppelin/contracts/interfaces/IERC721Receiver.sol"; 7 | 8 | contract MockEIP1271Wallet is IERC1271 { 9 | address public immutable owner; 10 | 11 | constructor(address _owner) { 12 | // slither-disable-next-line missing-zero-check 13 | owner = _owner; 14 | } 15 | 16 | function isValidSignature(bytes32 hash, bytes memory signature) public view override returns (bytes4) { 17 | address recoveredAddress = ECDSA.recover(hash, signature); 18 | if (recoveredAddress == owner) { 19 | return this.isValidSignature.selector; 20 | } else { 21 | return 0; 22 | } 23 | } 24 | 25 | function onERC721Received( 26 | address /* operator */, 27 | address /* from */, 28 | uint256 /* tokenId */, 29 | bytes calldata /* data */ 30 | ) external pure returns (bytes4) { 31 | return IERC721Receiver.onERC721Received.selector; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /contracts/mocks/MockFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.19 <0.8.29; 3 | 4 | import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; 5 | import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; 6 | 7 | contract MockFactory { 8 | bytes private constant MOCK_DISGUISED_EOA_BYTECODE = 9 | hex"608060405234801561001057600080fd5b5060405161021338038061021383398101604081905261002f91610054565b600080546001600160a01b0319166001600160a01b0392909216919091179055610084565b60006020828403121561006657600080fd5b81516001600160a01b038116811461007d57600080fd5b9392505050565b610180806100936000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c80639d76ea581461003b578063e58ef8a81461006a575b600080fd5b60005461004e906001600160a01b031681565b6040516001600160a01b03909116815260200160405180910390f35b61007d61007836600461010e565b61007f565b005b6000546040516323b872dd60e01b81526001600160a01b038581166004830152848116602483015260448201849052909116906323b872dd90606401600060405180830381600087803b1580156100d557600080fd5b505af11580156100e9573d6000803e3d6000fd5b50505050505050565b80356001600160a01b038116811461010957600080fd5b919050565b60008060006060848603121561012357600080fd5b61012c846100f2565b925061013a602085016100f2565b915060408401359050925092509256fea2646970667358221220cc26e879b9dbccdd8ff34bda1c5675a4b1a8497cba91bea35b6b744a41374a9a64736f6c63430008130033"; 10 | 11 | function computeAddress(bytes32 salt, bytes32 codeHash) public view returns (address) { 12 | return Create2.computeAddress(salt, codeHash); 13 | } 14 | 15 | function deploy(bytes32 salt, bytes memory code) public { 16 | // slither-disable-next-line unused-return 17 | Create2.deploy(0, salt, code); 18 | } 19 | 20 | function deployMockEOAWithERC721Address(IERC721 tokenAddress, bytes32 salt) external returns (address) { 21 | bytes memory encodedParams = abi.encode(address(tokenAddress)); 22 | bytes memory constructorBytecode = abi.encodePacked(bytes(MOCK_DISGUISED_EOA_BYTECODE), encodedParams); 23 | address mockDisguisedEOAAddress = Create2.deploy(0, salt, constructorBytecode); 24 | 25 | return mockDisguisedEOAAddress; 26 | } 27 | 28 | function computeMockDisguisedEOAAddress(IERC721 tokenAddress, bytes32 salt) external view returns (address) { 29 | bytes memory encodedParams = abi.encode(address(tokenAddress)); 30 | bytes memory constructorBytecode = abi.encodePacked(bytes(MOCK_DISGUISED_EOA_BYTECODE), encodedParams); 31 | address computedAddress = Create2.computeAddress(salt, keccak256(constructorBytecode)); 32 | 33 | return computedAddress; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /contracts/mocks/MockFunctions.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Unlicense 2 | // This file is part of the test code for GuardedMulticaller 3 | pragma solidity >=0.8.19 <0.8.29; 4 | 5 | contract MockFunctions { 6 | error RevertWithData(uint256 value); 7 | 8 | // solhint-disable-next-line no-empty-blocks 9 | function succeed() public pure { 10 | // This function is intentionally left empty to simulate a successful call 11 | } 12 | 13 | function revertWithNoReason() public pure { 14 | // solhint-disable-next-line custom-errors,reason-string 15 | revert(); 16 | } 17 | 18 | // solhint-disable-next-line no-empty-blocks 19 | function nonPermitted() public pure { 20 | // This function is intentionally left empty to simulate a non-permitted action 21 | } 22 | 23 | function succeedWithUint256(uint256 value) public pure returns (uint256) { 24 | return value; 25 | } 26 | 27 | function revertWithData(uint256 value) public pure { 28 | // solhint-disable-next-line custom-errors,reason-string 29 | revert RevertWithData(value); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /contracts/mocks/MockMarketplace.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.19 <0.8.29; 3 | 4 | import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; 5 | import {IERC2981} from "@openzeppelin/contracts/interfaces/IERC2981.sol"; 6 | 7 | contract MockMarketplace { 8 | error ZeroAddress(); 9 | 10 | IERC721 public immutable tokenAddress; 11 | IERC2981 public immutable royaltyAddress; 12 | 13 | constructor(address _tokenAddress) { 14 | tokenAddress = IERC721(_tokenAddress); 15 | royaltyAddress = IERC2981(_tokenAddress); 16 | } 17 | 18 | function executeTransfer(address recipient, uint256 _tokenId) public { 19 | tokenAddress.transferFrom(msg.sender, recipient, _tokenId); 20 | } 21 | 22 | /// @notice This code is only for testing purposes. Do not use similar 23 | /// @notice constructions in production code as they are open to attack. 24 | /// @dev For details see: https://github.com/crytic/slither/wiki/Detector-Documentation#arbitrary-from-in-transferfrom 25 | function executeTransferFrom(address from, address to, uint256 _tokenId) public { 26 | // slither-disable-next-line arbitrary-send-erc20 27 | tokenAddress.transferFrom(from, to, _tokenId); 28 | } 29 | 30 | function executeApproveForAll(address operator, bool approved) public { 31 | tokenAddress.setApprovalForAll(operator, approved); 32 | } 33 | 34 | /// @notice This code is only for testing purposes. Do not use similar 35 | /// @notice constructions in production code as they are open to attack. 36 | /// @dev For details see: https://github.com/crytic/slither/wiki/Detector-Documentation#arbitrary-from-in-transferfrom 37 | function executeTransferRoyalties(address from, address recipient, uint256 _tokenId, uint256 price) public payable { 38 | if (from == address(0)) { 39 | revert ZeroAddress(); 40 | } 41 | // solhint-disable-next-line custom-errors 42 | require(msg.value == price, "insufficient msg.value"); 43 | (address receiver, uint256 royaltyAmount) = royaltyAddress.royaltyInfo(_tokenId, price); 44 | if (receiver == address(0)) { 45 | revert ZeroAddress(); 46 | } 47 | uint256 sellerAmt = msg.value - royaltyAmount; 48 | payable(receiver).transfer(royaltyAmount); 49 | payable(from).transfer(sellerAmt); 50 | // slither-disable-next-line arbitrary-send-erc20 51 | tokenAddress.transferFrom(from, recipient, _tokenId); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /contracts/mocks/MockOnReceive.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.19 <0.8.29; 3 | 4 | import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; 5 | 6 | contract MockOnReceive { 7 | IERC721 public immutable tokenAddress; 8 | address private immutable recipient; 9 | 10 | // slither-disable-next-line missing-zero-check 11 | constructor(IERC721 _tokenAddress, address _recipient) { 12 | tokenAddress = _tokenAddress; 13 | recipient = _recipient; 14 | } 15 | 16 | // Attempt to transfer token to another address on receive 17 | function onERC721Received( 18 | address, 19 | /* operator */ address, 20 | /* from */ uint256 tokenId, 21 | bytes calldata /* data */ 22 | ) public returns (bytes4) { 23 | tokenAddress.transferFrom(address(this), recipient, tokenId); 24 | return this.onERC721Received.selector; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /contracts/mocks/MockWallet.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.19 <0.8.29; 3 | 4 | import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; 5 | import {IERC1155} from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol"; 6 | 7 | contract MockWallet { 8 | event Received(address, address, uint256, uint256, bytes); 9 | 10 | event ReceivedBatch(address, address, uint256[], uint256[], bytes); 11 | 12 | /// @notice This code is only for testing purposes. Do not use similar 13 | /// @notice constructions in production code as they are open to attack. 14 | /// @dev For details see: https://github.com/crytic/slither/wiki/Detector-Documentation#arbitrary-from-in-transferfrom 15 | function transferNFT(address token, address from, address to, uint256 tokenId) external { 16 | // slither-disable-next-line arbitrary-send-erc20 17 | IERC721(token).transferFrom(from, to, tokenId); 18 | } 19 | 20 | function transfer1155(address token, address from, address to, uint256 tokenId, uint256 amount) external { 21 | IERC1155(token).safeTransferFrom(from, to, tokenId, amount, ""); 22 | } 23 | 24 | function onERC721Received( 25 | address operator, 26 | address from, 27 | uint256 tokenId, 28 | bytes calldata data 29 | ) external returns (bytes4) { 30 | emit Received(operator, from, tokenId, 1, data); 31 | return bytes4(keccak256("onERC721Received(address,address,uint256,bytes)")); 32 | } 33 | 34 | function batchTransfer1155( 35 | address token, 36 | address from, 37 | address to, 38 | uint256[] memory tokenIds, 39 | uint256[] memory amounts 40 | ) external { 41 | IERC1155(token).safeBatchTransferFrom(from, to, tokenIds, amounts, ""); 42 | } 43 | 44 | function onERC1155Received( 45 | address operator, 46 | address from, 47 | uint256 id, 48 | uint256 value, 49 | bytes calldata data 50 | ) external returns (bytes4) { 51 | emit Received(operator, from, id, value, data); 52 | return bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)")); 53 | } 54 | 55 | function onERC1155BatchReceived( 56 | address operator, 57 | address from, 58 | uint256[] calldata ids, 59 | uint256[] calldata values, 60 | bytes calldata data 61 | ) external returns (bytes4) { 62 | emit ReceivedBatch(operator, from, ids, values, data); 63 | return bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)")); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /contracts/mocks/MockWalletFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.19 <0.8.29; 3 | 4 | contract MockWalletFactory { 5 | bytes private constant WALLET_CREATION_CODE = 6 | hex"608060405234801561001057600080fd5b5060405161029f38038061029f8339818101604052810190610032919061009e565b803055506100cb565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600061006b82610040565b9050919050565b61007b81610060565b811461008657600080fd5b50565b60008151905061009881610072565b92915050565b6000602082840312156100b4576100b361003b565b5b60006100c284828501610089565b91505092915050565b6101c5806100da6000396000f3fe6080604052600436106100225760003560e01c806390611127146100a857610076565b36610076573373ffffffffffffffffffffffffffffffffffffffff16347f606834f57405380c4fb88d1f4850326ad3885f014bab3b568dfbf7a041eef73860405161006c90610113565b60405180910390a3005b60006100806100d3565b90503660008037600080366000845af43d6000803e80600081146100a3573d6000f35b3d6000fd5b3480156100b457600080fd5b506100bd6100d3565b6040516100ca9190610174565b60405180910390f35b60003054905090565b600082825260208201905092915050565b50565b60006100fd6000836100dc565b9150610108826100ed565b600082019050919050565b6000602082019050818103600083015261012c816100f0565b9050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600061015e82610133565b9050919050565b61016e81610153565b82525050565b60006020820190506101896000830184610165565b9291505056fea2646970667358221220d43fa02972046db2bc81804ebf600d5b46b97e55c738ea899a28224e111b588564736f6c63430008110033"; 7 | 8 | function getAddress(address _mainModule, bytes32 _salt) public view returns (address _address) { 9 | bytes32 _hash = keccak256( 10 | abi.encodePacked( 11 | bytes1(0xff), 12 | address(this), 13 | _salt, 14 | keccak256(abi.encodePacked(WALLET_CREATION_CODE, uint256(uint160(_mainModule)))) 15 | ) 16 | ); 17 | return address(uint160(uint256(_hash))); 18 | } 19 | 20 | // slither-disable-next-line locked-ether 21 | function deploy(address _mainModule, bytes32 _salt) public payable returns (address _contract) { 22 | bytes memory code = abi.encodePacked(WALLET_CREATION_CODE, uint256(uint160(_mainModule))); 23 | // solhint-disable-next-line no-inline-assembly 24 | assembly { 25 | _contract := create2(callvalue(), add(code, 32), mload(code), _salt) 26 | } 27 | // check deployment success 28 | // solhint-disable-next-line custom-errors 29 | require(_contract != address(0), "WalletFactory: deployment failed"); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /contracts/random/README.md: -------------------------------------------------------------------------------- 1 | # Random Number Generation 2 | 3 | Immutable has thoroughly investigated on-chain random number generation and use of off-chain random 4 | providers such as Supra and Chainlink. To create a secure on-chain random source requires an off-chain service to inteact with the chain regularly. At present, there is not sufficient demand for on-chain random for Immutable to invest in creating such a service. The source code, tests, and threat model from the investigation are contained in the [random2 branch](https://github.com/immutable/contracts/tree/random2). 5 | In particular, teams considering using on-chain random number generation should read the [threat model document](https://github.com/immutable/contracts/blob/random2/audits/random/202403-threat-model-random.md) as this describes in detail how on-chain randon number generation can be used securely. 6 | 7 | -------------------------------------------------------------------------------- /contracts/staking/IWIMX.sol: -------------------------------------------------------------------------------- 1 | // Copyright Immutable Pty Ltd 2018 - 2023 2 | // SPDX-License-Identifier: Apache 2.0 3 | pragma solidity >=0.8.19 <0.8.29; 4 | 5 | import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 6 | 7 | /* 8 | * @notice Interface for the Wrapped IMX (wIMX) contract. 9 | * @dev Based on the interface for the [Wrapped IMX contract](https://etherscan.io/token/0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2#code) 10 | */ 11 | interface IWIMX is IERC20 { 12 | /** 13 | * @notice Emitted when native IMX is deposited to the contract, and a corresponding amount of wIMX are minted 14 | * @param account The address of the account that deposited the tokens. 15 | * @param value The amount of tokens that were deposited. 16 | */ 17 | event Deposit(address indexed account, uint256 value); 18 | 19 | /** 20 | * @notice Emitted when wIMX is withdrawn from the contract, and a corresponding amount of wIMX are burnt. 21 | * @param account The address of the account that withdrew the tokens. 22 | * @param value The amount of tokens that were withdrawn. 23 | */ 24 | event Withdrawal(address indexed account, uint256 value); 25 | 26 | /** 27 | * @notice Deposit native IMX to the contract and mint an equal amount of wrapped IMX to msg.sender. 28 | */ 29 | function deposit() external payable; 30 | 31 | /** 32 | * @notice Withdraw given amount of native IMX to msg.sender after burning an equal amount of wrapped IMX. 33 | * @param value The amount to withdraw. 34 | */ 35 | function withdraw(uint256 value) external; 36 | } 37 | -------------------------------------------------------------------------------- /contracts/staking/StakeHolderERC20.sol: -------------------------------------------------------------------------------- 1 | // Copyright (c) Immutable Pty Ltd 2018 - 2025 2 | // SPDX-License-Identifier: Apache 2 3 | pragma solidity >=0.8.19 <0.8.29; 4 | 5 | import {IERC20Upgradeable} from "openzeppelin-contracts-upgradeable-4.9.3/token/ERC20/IERC20Upgradeable.sol"; 6 | import {SafeERC20Upgradeable} from "openzeppelin-contracts-upgradeable-4.9.3/token/ERC20/utils/SafeERC20Upgradeable.sol"; 7 | import {IStakeHolder, StakeHolderBase} from "./StakeHolderBase.sol"; 8 | 9 | /** 10 | * @title StakeHolderERC20: allows anyone to stake any amount of an ERC20 token and to then remove all or part of that stake. 11 | * @dev The StakeHolderERC20 contract is designed to be upgradeable. 12 | */ 13 | contract StakeHolderERC20 is StakeHolderBase { 14 | using SafeERC20Upgradeable for IERC20Upgradeable; 15 | 16 | /// @notice The token used for staking. 17 | IERC20Upgradeable internal token; 18 | 19 | /** 20 | * @notice Initialises the upgradeable contract, setting up admin accounts. 21 | * @param _roleAdmin the address to grant `DEFAULT_ADMIN_ROLE` to 22 | * @param _upgradeAdmin the address to grant `UPGRADE_ROLE` to 23 | * @param _distributeAdmin the address to grant `DISTRIBUTE_ROLE` to. 24 | * @param _token the token to use for staking. 25 | */ 26 | function initialize( 27 | address _roleAdmin, 28 | address _upgradeAdmin, 29 | address _distributeAdmin, 30 | address _token 31 | ) public initializer { 32 | __StakeHolderERC20_init(_roleAdmin, _upgradeAdmin, _distributeAdmin, _token); 33 | } 34 | 35 | function __StakeHolderERC20_init( 36 | address _roleAdmin, 37 | address _upgradeAdmin, 38 | address _distributeAdmin, 39 | address _token 40 | ) internal onlyInitializing { 41 | __StakeHolderBase_init(_roleAdmin, _upgradeAdmin, _distributeAdmin); 42 | token = IERC20Upgradeable(_token); 43 | } 44 | 45 | /** 46 | * @inheritdoc IStakeHolder 47 | */ 48 | function getToken() external view returns (address) { 49 | return address(token); 50 | } 51 | 52 | /** 53 | * @inheritdoc StakeHolderBase 54 | */ 55 | function _sendValue(address _to, uint256 _amount) internal override { 56 | token.safeTransfer(_to, _amount); 57 | } 58 | 59 | /** 60 | * @inheritdoc StakeHolderBase 61 | */ 62 | function _checksAndTransfer(uint256 _amount) internal override { 63 | if (msg.value != 0) { 64 | revert NonPayable(); 65 | } 66 | token.safeTransferFrom(msg.sender, address(this), _amount); 67 | } 68 | 69 | /// @notice storage gap for additional variables for upgrades 70 | // slither-disable-start unused-state 71 | // solhint-disable-next-line var-name-mixedcase 72 | uint256[50] private __StakeHolderERC20Gap; 73 | // slither-disable-end unused-state 74 | } 75 | -------------------------------------------------------------------------------- /contracts/staking/StakeHolderNative.sol: -------------------------------------------------------------------------------- 1 | // Copyright (c) Immutable Pty Ltd 2018 - 2025 2 | // SPDX-License-Identifier: Apache 2 3 | pragma solidity >=0.8.19 <0.8.29; 4 | 5 | import {IStakeHolder, StakeHolderBase} from "./StakeHolderBase.sol"; 6 | 7 | /** 8 | * @title StakeHolder: allows anyone to stake any amount of native IMX and to then remove all or part of that stake. 9 | * @dev The StakeHolder contract is designed to be upgradeable. 10 | */ 11 | contract StakeHolderNative is StakeHolderBase { 12 | /// @notice Error: Unstake transfer failed. 13 | error UnstakeTransferFailed(); 14 | 15 | /** 16 | * @notice Initialises the upgradeable contract, setting up admin accounts. 17 | * @param _roleAdmin the address to grant `DEFAULT_ADMIN_ROLE` to 18 | * @param _upgradeAdmin the address to grant `UPGRADE_ROLE` to 19 | * @param _distributeAdmin the address to grant `DISTRIBUTE_ROLE` to 20 | */ 21 | function initialize(address _roleAdmin, address _upgradeAdmin, address _distributeAdmin) public initializer { 22 | __StakeHolderBase_init(_roleAdmin, _upgradeAdmin, _distributeAdmin); 23 | } 24 | 25 | /** 26 | * @inheritdoc IStakeHolder 27 | */ 28 | function getToken() external view virtual returns (address) { 29 | return address(0); 30 | } 31 | 32 | /** 33 | * @inheritdoc StakeHolderBase 34 | */ 35 | function _sendValue(address _to, uint256 _amount) internal virtual override { 36 | // slither-disable-next-line low-level-calls,arbitrary-send-eth 37 | (bool success, bytes memory returndata) = payable(_to).call{value: _amount}(""); 38 | if (!success) { 39 | // Look for revert reason and bubble it up if present. 40 | // Revert reasons should contain an error selector, which is four bytes long. 41 | if (returndata.length >= 4) { 42 | // solhint-disable-next-line no-inline-assembly 43 | assembly { 44 | let returndata_size := mload(returndata) 45 | revert(add(32, returndata), returndata_size) 46 | } 47 | } else { 48 | revert UnstakeTransferFailed(); 49 | } 50 | } 51 | } 52 | 53 | /** 54 | * @inheritdoc StakeHolderBase 55 | */ 56 | function _checksAndTransfer(uint256 _amount) internal virtual override { 57 | // Check that the amount matches the msg.value. 58 | if (_amount != msg.value) { 59 | revert MismatchMsgValueAmount(msg.value, _amount); 60 | } 61 | } 62 | 63 | /// @notice storage gap for additional variables for upgrades 64 | // slither-disable-start unused-state 65 | // solhint-disable-next-line var-name-mixedcase 66 | uint256[50] private __StakeHolderNativeGap; 67 | // slither-disable-end unused-state 68 | } 69 | -------------------------------------------------------------------------------- /contracts/staking/StakeHolderWIMX.sol: -------------------------------------------------------------------------------- 1 | // Copyright (c) Immutable Pty Ltd 2018 - 2025 2 | // SPDX-License-Identifier: Apache 2 3 | pragma solidity >=0.8.19 <0.8.29; 4 | 5 | import {IStakeHolder, StakeHolderBase, StakeHolderNative} from "./StakeHolderNative.sol"; 6 | import {IWIMX} from "./IWIMX.sol"; 7 | 8 | /** 9 | * @title StakeHolderWIMX: allows anyone to stake any amount of IMX and to then remove all or part of that stake. 10 | * @dev Stake can be added and withdrawn either as native IMX only. 11 | * The StakeHolderWIMX contract is designed to be upgradeable. 12 | */ 13 | contract StakeHolderWIMX is StakeHolderNative { 14 | /// @notice The token used for staking. 15 | IWIMX internal wIMX; 16 | 17 | /** 18 | * @notice Initialises the upgradeable contract, setting up admin accounts. 19 | * @param _roleAdmin the address to grant `DEFAULT_ADMIN_ROLE` to 20 | * @param _upgradeAdmin the address to grant `UPGRADE_ROLE` to 21 | * @param _distributeAdmin the address to grant `DISTRIBUTE_ROLE` to 22 | * @param _wIMXToken The address of the WIMX contract. 23 | */ 24 | function initialize( 25 | address _roleAdmin, 26 | address _upgradeAdmin, 27 | address _distributeAdmin, 28 | address _wIMXToken 29 | ) public initializer { 30 | __StakeHolderBase_init(_roleAdmin, _upgradeAdmin, _distributeAdmin); 31 | wIMX = IWIMX(_wIMXToken); 32 | } 33 | 34 | receive() external payable { 35 | // Receive IMX sent by the WIMX contract when wIMX.withdraw() is called. 36 | } 37 | 38 | /** 39 | * @inheritdoc IStakeHolder 40 | */ 41 | function getToken() external view override returns (address) { 42 | return address(wIMX); 43 | } 44 | 45 | /** 46 | * @inheritdoc StakeHolderBase 47 | */ 48 | function _sendValue(address _to, uint256 _amount) internal override { 49 | // Convert WIMX to native IMX 50 | wIMX.withdraw(_amount); 51 | 52 | super._sendValue(_to, _amount); 53 | } 54 | 55 | /** 56 | * @inheritdoc StakeHolderBase 57 | */ 58 | function _checksAndTransfer(uint256 _amount) internal override { 59 | super._checksAndTransfer(_amount); 60 | 61 | // Convert native IMX to WIMX. 62 | // slither-disable-next-line arbitrary-send-eth 63 | wIMX.deposit{value: _amount}(); 64 | } 65 | 66 | /// @notice storage gap for additional variables for upgrades 67 | // slither-disable-start unused-state 68 | // solhint-disable-next-line var-name-mixedcase 69 | uint256[50] private __StakeHolderWIMXGap; 70 | // slither-disable-end unused-state 71 | } 72 | -------------------------------------------------------------------------------- /contracts/staking/staking-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/immutable/contracts/9bc03be17dc5c52ab541e0d3bf25ba1a32a62889/contracts/staking/staking-architecture.png -------------------------------------------------------------------------------- /contracts/token/erc1155/abstract/IERC1155Permit.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Permit.sol) 3 | 4 | pragma solidity >=0.8.19 <0.8.29; 5 | 6 | interface IERC1155Permit { 7 | function permit(address owner, address spender, bool approved, uint256 deadline, bytes memory sig) external; 8 | function nonces(address owner) external view returns (uint256); 9 | 10 | // solhint-disable-next-line func-name-mixedcase 11 | function DOMAIN_SEPARATOR() external view returns (bytes32); 12 | } 13 | -------------------------------------------------------------------------------- /contracts/token/erc1155/preset/ImmutableERC1155.sol: -------------------------------------------------------------------------------- 1 | // Copyright Immutable Pty Ltd 2018 - 2023 2 | // SPDX-License-Identifier: Apache 2.0 3 | pragma solidity >=0.8.19 <0.8.29; 4 | 5 | import {ImmutableERC1155Base} from "../abstract/ImmutableERC1155Base.sol"; 6 | 7 | /** 8 | * @title ImmutableERC1155 9 | * @author @jasonzwli, Immutable 10 | */ 11 | contract ImmutableERC1155 is ImmutableERC1155Base { 12 | /// ===== Constructor ===== 13 | 14 | /** 15 | * @notice Grants `DEFAULT_ADMIN_ROLE` to the supplied `owner` address 16 | * 17 | * Sets the name and symbol for the collection 18 | * Sets the default admin to `owner` 19 | * Sets the `baseURI` 20 | * Sets the royalty receiver and amount (this can not be changed once set) 21 | * @param owner The address that will be granted the `DEFAULT_ADMIN_ROLE` 22 | * @param name_ The name of the collection 23 | * @param baseURI_ The base URI for the collection 24 | * @param contractURI_ The contract URI for the collection 25 | * @param _operatorAllowlist The address of the OAL 26 | * @param _receiver The address that will receive the royalty payments 27 | * @param _feeNumerator The percentage of the sale price that will be paid as a royalty 28 | */ 29 | constructor( 30 | address owner, 31 | string memory name_, 32 | string memory baseURI_, 33 | string memory contractURI_, 34 | address _operatorAllowlist, 35 | address _receiver, 36 | uint96 _feeNumerator 37 | ) ImmutableERC1155Base(owner, name_, baseURI_, contractURI_, _operatorAllowlist, _receiver, _feeNumerator) {} 38 | 39 | /// ===== External functions ===== 40 | 41 | /** 42 | * @notice Mints a new token 43 | * @param to The address that will receive the minted tokens 44 | * @param id The id of the token to mint 45 | * @param value The amount of tokens to mint 46 | * @param data Additional data 47 | */ 48 | function safeMint(address to, uint256 id, uint256 value, bytes memory data) external onlyRole(MINTER_ROLE) { 49 | super._mint(to, id, value, data); 50 | } 51 | 52 | /** 53 | * @notice Mints a batch of new tokens with different ids to the same recipient 54 | * @param to The address that will receive the minted tokens 55 | * @param ids The ids of the tokens to mint 56 | * @param values The amounts of tokens to mint 57 | * @param data Additional data 58 | */ 59 | function safeMintBatch( 60 | address to, 61 | uint256[] calldata ids, 62 | uint256[] calldata values, 63 | bytes memory data 64 | ) external onlyRole(MINTER_ROLE) { 65 | super._mintBatch(to, ids, values, data); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /contracts/token/erc20/README.md: -------------------------------------------------------------------------------- 1 | # ERC 20 Tokens 2 | 3 | This directory contains ERC 20 token contracts that game studios could choose to use 4 | directly or extend. 5 | 6 | | Contract | Description | 7 | |--------------------------------------- |-----------------------------------------------| 8 | | preset/ImmutableERC20MinterBurnerPermit| Provides basic ERC 20 Permit, Capped token supply and burn capability. | 9 | | preset/ImmutableERC20FixedSupplyNoBurn | ERC 20 contract with a fixed supply defined at deployment. | 10 | 11 | ## ImmutableERC20MinterBurnerPermit 12 | 13 | This contract contains Permit methods, allowing the token owner to give a third party operator a Permit which is a signed message that can be used by the third party to give approval to themselves to operate on the tokens owned by the original owner. Users take care when signing messages. If they inadvertantly sign a malicious permit, then the attacker could use use it to gain access to the user's tokens. Read more on the EIP here: [EIP-2612](https://eips.ethereum.org/EIPS/eip-2612). 14 | # Status 15 | 16 | Contract threat models and audits: 17 | 18 | | Description | Date |Version Audited | Link to Report | 19 | |---------------------------|------------------|-----------------|----------------| 20 | | Internal audit | March 28, 2024 | [b7adf0d7](https://github.com/immutable/contracts/tree/b7adf0d702ea71ae43b65f904c1b18d7cdfbb4a2) | [202403-internal-audit-immutable-erc20.pdf](../../../audits/token/202403-internal-audit-immutable-erc20.pdf) | 21 | | Internal audit | April 16. 2024 | [aa6c1d4](https://github.com/immutable/contracts/tree/aa6c1d43a4165a6e4d8cde302fe34b424b99bd32) | [202404-internal-audit-immutable-erc20.pdf](../../../audits/token/202404-internal-audit-immutable-erc20.pdf) 22 | 23 | -------------------------------------------------------------------------------- /contracts/token/erc20/preset/Errors.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: Apache 2.0 2 | pragma solidity >=0.8.19 <0.8.29; 3 | 4 | interface IImmutableERC20Errors { 5 | error RenounceOwnershipNotAllowed(); 6 | } 7 | -------------------------------------------------------------------------------- /contracts/token/erc20/preset/ImmutableERC20FixedSupplyNoBurn.sol: -------------------------------------------------------------------------------- 1 | // Copyright (c) Immutable Pty Ltd 2018 - 2024 2 | // SPDX-License-Identifier: MIT 3 | pragma solidity >=0.8.19 <0.8.29; 4 | 5 | import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; 6 | import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 7 | import {IImmutableERC20Errors} from "./Errors.sol"; 8 | 9 | /** 10 | * @notice ERC 20 contract that mints a fixed total supply of tokens when the contract 11 | * is deployed. 12 | * @dev This contract has the concept of an owner, called _hubOwner in the constructor. 13 | * This account has no rights to execute any administrative actions within the contract, 14 | * with the exception of transferOwnership. This account is accessed via the owner() 15 | * function. The Immutable Hub uses this function to help associate the ERC 20 contract 16 | * with a specific Immutable Hub account. 17 | */ 18 | contract ImmutableERC20FixedSupplyNoBurn is Ownable, ERC20 { 19 | /** 20 | * @dev Mints `_totalSupply` number of token and transfers them to `_owner`. 21 | * 22 | * @param _name Name of the token. 23 | * @param _symbol Token symbol. 24 | * @param _totalSupply Supply of the token. 25 | * @param _treasurer Initial owner of entire supply of all tokens. 26 | * @param _hubOwner The account associated with Immutable Hub. 27 | */ 28 | constructor( 29 | string memory _name, 30 | string memory _symbol, 31 | uint256 _totalSupply, 32 | address _treasurer, 33 | address _hubOwner 34 | ) ERC20(_name, _symbol) { 35 | _mint(_treasurer, _totalSupply); 36 | _transferOwnership(_hubOwner); 37 | } 38 | 39 | /** 40 | * @notice Prevent calls to renounce ownership. 41 | */ 42 | function renounceOwnership() public pure override { 43 | revert IImmutableERC20Errors.RenounceOwnershipNotAllowed(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /contracts/token/erc721/abstract/IERC4494.sol: -------------------------------------------------------------------------------- 1 | // Copyright Immutable Pty Ltd 2018 - 2023 2 | // SPDX-License-Identifier: Apache 2.0 3 | pragma solidity >=0.8.19 <0.8.29; 4 | 5 | import {IERC165} from "@openzeppelin/contracts/interfaces/IERC165.sol"; 6 | 7 | /// 8 | /// @dev Interface for token permits for ERC721 9 | /// 10 | interface IERC4494 is IERC165 { 11 | /// ERC165 bytes to add to interface array - set in parent contract 12 | /// 13 | /// _INTERFACE_ID_ERC4494 = 0x5604e225 14 | 15 | /// @notice Function to approve by way of owner signature 16 | /// @param spender the address to approve 17 | /// @param tokenId the index of the NFT to approve the spender on 18 | /// @param deadline a timestamp expiry for the permit 19 | /// @param sig a traditional or EIP-2098 signature 20 | function permit(address spender, uint256 tokenId, uint256 deadline, bytes memory sig) external; 21 | 22 | /// @notice Returns the nonce of an NFT - useful for creating permits 23 | /// @param tokenId the index of the NFT to get the nonce of 24 | /// @return the uint256 representation of the nonce 25 | function nonces(uint256 tokenId) external view returns (uint256); 26 | 27 | /// @notice Returns the domain separator used in the encoding of the signature for permits, as defined by EIP-712 28 | /// @return the bytes32 domain separator 29 | // solhint-disable-next-line func-name-mixedcase 30 | function DOMAIN_SEPARATOR() external view returns (bytes32); 31 | } 32 | -------------------------------------------------------------------------------- /contracts/token/erc721/erc721psi/ERC721PsiBurnable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /** 3 | * ______ _____ _____ ______ ___ __ _ _ _ 4 | * | ____| __ \ / ____|____ |__ \/_ | || || | 5 | * | |__ | |__) | | / / ) || | \| |/ | 6 | * | __| | _ /| | / / / / | |\_ _/ 7 | * | |____| | \ \| |____ / / / /_ | | | | 8 | * |______|_| \_\\_____|/_/ |____||_| |_| 9 | */ 10 | pragma solidity >=0.8.19 <0.8.29; 11 | 12 | import {BitMaps} from "solidity-bits/contracts/BitMaps.sol"; 13 | import {ERC721Psi} from "./ERC721Psi.sol"; 14 | 15 | abstract contract ERC721PsiBurnable is ERC721Psi { 16 | using BitMaps for BitMaps.BitMap; 17 | 18 | BitMaps.BitMap private _burnedToken; 19 | 20 | /** 21 | * @dev Destroys `tokenId`. 22 | * The approval is cleared when the token is burned. 23 | * 24 | * Requirements: 25 | * 26 | * - `tokenId` must exist. 27 | * 28 | * Emits a {Transfer} event. 29 | */ 30 | function _burn(uint256 tokenId) internal virtual { 31 | address from = ownerOf(tokenId); 32 | _beforeTokenTransfers(from, address(0), tokenId, 1); 33 | _burnedToken.set(tokenId); 34 | 35 | emit Transfer(from, address(0), tokenId); 36 | 37 | _afterTokenTransfers(from, address(0), tokenId, 1); 38 | } 39 | 40 | /** 41 | * @dev Returns whether `tokenId` exists. 42 | * 43 | * Tokens can be managed by their owner or approved accounts via {approve} or {setApprovalForAll}. 44 | * 45 | * Tokens start existing when they are minted (`_mint`), 46 | * and stop existing when they are burned (`_burn`). 47 | */ 48 | function _exists(uint256 tokenId) internal view virtual override returns (bool) { 49 | if (_burnedToken.get(tokenId)) { 50 | return false; 51 | } 52 | return super._exists(tokenId); 53 | } 54 | 55 | /** 56 | * @dev See {IERC721Enumerable-totalSupply}. 57 | */ 58 | function totalSupply() public view virtual override returns (uint256) { 59 | return _totalMinted() - _burned(); 60 | } 61 | 62 | /** 63 | * @dev Returns number of token burned. 64 | */ 65 | function _burned() internal view returns (uint256 burned) { 66 | uint256 startBucket = _startTokenId() >> 8; 67 | uint256 lastBucket = (_nextTokenId() >> 8) + 1; 68 | 69 | for (uint256 i = startBucket; i < lastBucket; i++) { 70 | uint256 bucket = _burnedToken.getBucket(i); 71 | burned += _popcount(bucket); 72 | } 73 | } 74 | 75 | /** 76 | * @dev Returns number of set bits. 77 | */ 78 | function _popcount(uint256 x) private pure returns (uint256 count) { 79 | unchecked { 80 | for (count = 0; x != 0; count++) { 81 | x &= x - 1; 82 | } 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /contracts/token/erc721/erc721psi/ERC721PsiBurnableV2.sol: -------------------------------------------------------------------------------- 1 | // Copyright Immutable Pty Ltd 2018 - 2025 2 | // SPDX-License-Identifier: Apache 2.0 3 | /** 4 | * Inspired by ERC721Psi: https://github.com/estarriolvetch/ERC721Psi 5 | */ 6 | pragma solidity >=0.8.19 <0.8.29; 7 | 8 | import {ERC721PsiV2} from "./ERC721PsiV2.sol"; 9 | 10 | abstract contract ERC721PsiBurnableV2 is ERC721PsiV2 { 11 | /** 12 | * @dev Destroys `tokenId`. 13 | * The approval is cleared when the token is burned. 14 | * 15 | * Requirements: 16 | * 17 | * - `tokenId` must exist. 18 | * 19 | * Emits a {Transfer} event. 20 | */ 21 | function _burn(uint256 _tokenId) internal virtual { 22 | // Note: To get here, exists must be true. Hence, it is OK to ignore exists. 23 | uint256 groupNumber; 24 | uint256 groupOffset; 25 | address owner; 26 | (groupNumber, groupOffset, , owner) = _tokenInfo(_tokenId); 27 | 28 | _beforeTokenTransfers(owner, address(0), _tokenId, 1); 29 | 30 | TokenGroup storage group = tokenOwners[groupNumber]; 31 | group.burned = _setBit(group.burned, groupOffset); 32 | 33 | // Update balances 34 | balances[owner]--; 35 | // _burn is called in a loop in burn batch, and hence a more efficient batch 36 | // burning process would be to have this update to supply happen outside the loop. 37 | // However, this would mean changing code across the codebase. 38 | // slither-disable-next-line costly-loop 39 | supply--; 40 | 41 | emit Transfer(owner, address(0), _tokenId); 42 | 43 | _afterTokenTransfers(owner, address(0), _tokenId, 1); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /contracts/token/erc721/erc721psi/README.md: -------------------------------------------------------------------------------- 1 | # Immutable ERC721 Mint by Quantity Implementation Contracts 2 | 3 | The ERC721Psi code in this directory was forked from https://github.com/estarriolvetch/ERC721Psi, with the changes listed in the ERC721Psi Changelog section below. 4 | 5 | The ERC721PsiV2 leverages the ERC721Psi code, slightly increasing the minting gas usage but reducing the gas usage for most other functions. The differences between the ERC721Psi and ERC721PsiV2 are listed in the section ERC721PsiV2 and ERC721Psi Differences section. 6 | 7 | 8 | ## ERC721Psi Differences From Upstream 9 | 10 | - ERC721Psi: changed `_safeMint(address to, uint256 quantity) internal virtual` to call `ERC721PSI._mint` explicitly to avoid calling ERC721 methods when both are imported in a child contract 11 | - ERC721Psi: changed `_safeMint(address to, uint256 quantity, bytes memory _data` to call `ERC721PSI._mint` explicitly to avoid calling ERC721 methods when both are imported in a child contract 12 | 13 | 14 | ## ERC721PsiV2 and ERC721Psi Differences 15 | 16 | - Switched from `solidity-bits'` `BitMaps` implementation to using the `TokenGroup` struct. In `ERC721Psi`, `BitMaps` are used as arrays of bits that have to be traversed. One `TokenGroup` struct holds the token ids for a 256 NFTs. The first NFT in a group is at a multiple of 256. The owner of an NFT for a `TokenGroup` is the `defaultOwner` specified in the `TokenGroup` struct, unless the owner is specified in the `tokenOwners` map. The result of this change is that the owner of a token can be determined using a deterministic amount of gas for `ERC721PsiV2`. 17 | - In `ERC721Psi`, newly minted NFTs are minted to the next available token id. In `ERC721PsiV2`, newly minted NFTs are minted to the next token id that is a multiple of 256. This means that each new mint by quantity is minted to a new token group. 18 | - For `ERC721PsiV2`, when the mint by quantity request is not a multiple of 256 NFTs, there are unused token ids. These token ids are added to a `burned` map in the `TokenGroup`, thus making those token ids unavailable. 19 | - In `ERC721PsiV2`, the balances of account holders and the total supply are maintained as state variables, rather than calculating them when needed, as they are in `ERC721Psi`. The result of this change is that `balanceOf` and `totalSupply` use a deterministic amount of gas. 20 | -------------------------------------------------------------------------------- /contracts/token/erc721/interfaces/IImmutableERC721ByQuantity.sol: -------------------------------------------------------------------------------- 1 | // Copyright Immutable Pty Ltd 2018 - 2025 2 | // SPDX-License-Identifier: Apache 2.0 3 | pragma solidity >=0.8.19 <0.8.29; 4 | 5 | import {IImmutableERC721} from "./IImmutableERC721.sol"; 6 | 7 | interface IImmutableERC721ByQuantity is IImmutableERC721 { 8 | /** 9 | * @notice Allows minter to mint a number of tokens sequentially to a specified address 10 | * @param to the address to mint the token to 11 | * @param quantity the number of tokens to mint 12 | */ 13 | function mintByQuantity(address to, uint256 quantity) external; 14 | 15 | /** 16 | * @notice Allows minter to mint a number of tokens sequentially to a specified address with hooks 17 | * and checks 18 | * @param to the address to mint the token to 19 | * @param quantity the number of tokens to mint 20 | */ 21 | function safeMintByQuantity(address to, uint256 quantity) external; 22 | 23 | /** 24 | * @notice Allows minter to mint a number of tokens sequentially to a number of specified addresses 25 | * @param mints the list of Mint struct containing the to, and the number of tokens to mint 26 | */ 27 | function mintBatchByQuantity(Mint[] calldata mints) external; 28 | 29 | /** 30 | * @notice Allows minter to safe mint a number of tokens sequentially to a number of specified addresses 31 | * @param mints the list of Mint struct containing the to, and the number of tokens to mint 32 | */ 33 | function safeMintBatchByQuantity(Mint[] calldata mints) external; 34 | 35 | /** 36 | * @notice checks to see if tokenID exists in the collection 37 | * @param tokenId the id of the token to check 38 | * 39 | */ 40 | function exists(uint256 tokenId) external view returns (bool); 41 | 42 | /** 43 | * @notice returns the threshold that divides tokens that are minted by id and 44 | * minted by quantity 45 | */ 46 | function mintBatchByQuantityThreshold() external pure returns (uint256); 47 | } 48 | -------------------------------------------------------------------------------- /contracts/token/erc721/interfaces/IImmutableERC721ByQuantityV2.sol: -------------------------------------------------------------------------------- 1 | // Copyright Immutable Pty Ltd 2018 - 2025 2 | // SPDX-License-Identifier: Apache 2.0 3 | pragma solidity >=0.8.19 <0.8.29; 4 | 5 | import {IImmutableERC721ByQuantity} from "./IImmutableERC721ByQuantity.sol"; 6 | 7 | interface IImmutableERC721ByQuantityV2 is IImmutableERC721ByQuantity { 8 | /** 9 | * @notice returns the next token id that will be minted for the first 10 | * NFT in a call to mintByQuantity or safeMintByQuantity. 11 | */ 12 | function mintBatchByQuantityNextTokenId() external pure returns (uint256); 13 | } 14 | -------------------------------------------------------------------------------- /contracts/token/erc721/interfaces/IImmutableERC721Errors.sol: -------------------------------------------------------------------------------- 1 | // Copyright Immutable Pty Ltd 2018 - 2025 2 | // SPDX-License-Identifier: Apache 2.0 3 | pragma solidity >=0.8.19 <0.8.29; 4 | 5 | interface IImmutableERC721Errors { 6 | /// @dev Caller tried to mint an already burned token 7 | error IImmutableERC721TokenAlreadyBurned(uint256 tokenId); 8 | 9 | /// @dev Caller tried to mint to address 0x00 10 | error IImmutableERC721SendingToZerothAddress(); 11 | 12 | /// @dev SafeTransferFromBatch was called, and lengths of the tos and tokenIds arrays did not match 13 | error IImmutableERC721MismatchedTransferLengths(); 14 | 15 | /// @dev Caller tried to mint a tokenid that is above the hybrid threshold 16 | error IImmutableERC721IDAboveThreshold(uint256 tokenId); 17 | 18 | /// @dev Caller is not approved or owner 19 | error IImmutableERC721NotOwnerOrOperator(uint256 tokenId); 20 | 21 | /// @dev Current token owner is not what was expected 22 | error IImmutableERC721MismatchedTokenOwner(uint256 tokenId, address currentOwner); 23 | 24 | /// @dev Signer is zeroth address 25 | error SignerCannotBeZerothAddress(); 26 | 27 | /// @dev Deadline exceeded for permit 28 | error PermitExpired(); 29 | 30 | /// @dev Derived signature is invalid (EIP721 and EIP1271) 31 | error InvalidSignature(); 32 | } 33 | -------------------------------------------------------------------------------- /contracts/token/erc721/interfaces/IImmutableERC721Structs.sol: -------------------------------------------------------------------------------- 1 | // Copyright Immutable Pty Ltd 2018 - 2025 2 | // SPDX-License-Identifier: Apache 2.0 3 | pragma solidity >=0.8.19 <0.8.29; 4 | 5 | interface IImmutableERC721Structs { 6 | /** 7 | * @notice A singular batch transfer request. The length of the tos and tokenIds must be matching 8 | * batch transfers will transfer the specified ids to their matching address via index. 9 | * 10 | */ 11 | struct TransferRequest { 12 | address from; 13 | address[] tos; 14 | uint256[] tokenIds; 15 | } 16 | 17 | /// @notice A singular safe burn request. 18 | struct IDBurn { 19 | address owner; 20 | uint256[] tokenIds; 21 | } 22 | 23 | /// @notice A singular Mint by id request 24 | struct IDMint { 25 | address to; 26 | uint256[] tokenIds; 27 | } 28 | 29 | /// @notice A singular Mint by quantity request 30 | struct Mint { 31 | address to; 32 | uint256 quantity; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /contracts/token/erc721/x/Asset.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // solhint-disable compiler-version 3 | pragma solidity ^0.8.4; 4 | 5 | import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; 6 | import {Mintable} from "./Mintable.sol"; 7 | 8 | // slither-disable-start missing-inheritance 9 | contract Asset is ERC721, Mintable { 10 | constructor( 11 | address _owner, 12 | string memory _name, 13 | string memory _symbol, 14 | address _imx 15 | ) ERC721(_name, _symbol) Mintable(_owner, _imx) {} 16 | 17 | function _mintFor(address user, uint256 id, bytes memory) internal override { 18 | _safeMint(user, id); 19 | } 20 | } 21 | // slither-disable-end missing-inheritance 22 | -------------------------------------------------------------------------------- /contracts/token/erc721/x/IMintable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // solhint-disable compiler-version 3 | pragma solidity ^0.8.4; 4 | 5 | interface IMintable { 6 | function mintFor(address to, uint256 quantity, bytes calldata mintingBlob) external; 7 | } 8 | -------------------------------------------------------------------------------- /contracts/token/erc721/x/Mintable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // solhint-disable compiler-version, custom-errors, reason-string 3 | pragma solidity ^0.8.4; 4 | 5 | import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; 6 | import {IMintable} from "./IMintable.sol"; 7 | import {Minting} from "./utils/Minting.sol"; 8 | 9 | abstract contract Mintable is Ownable, IMintable { 10 | address public immutable imx; 11 | mapping(uint256 id => bytes blueprint) public blueprints; 12 | 13 | event AssetMinted(address to, uint256 id, bytes blueprint); 14 | 15 | constructor(address _owner, address _imx) { 16 | imx = _imx; 17 | require(_owner != address(0), "Owner must not be empty"); 18 | transferOwnership(_owner); 19 | } 20 | 21 | modifier onlyOwnerOrIMX() { 22 | require(msg.sender == imx || msg.sender == owner(), "Function can only be called by owner or IMX"); 23 | _; 24 | } 25 | 26 | function mintFor(address user, uint256 quantity, bytes calldata mintingBlob) external override onlyOwnerOrIMX { 27 | require(quantity == 1, "Mintable: invalid quantity"); 28 | (uint256 id, bytes memory blueprint) = Minting.split(mintingBlob); 29 | _mintFor(user, id, blueprint); 30 | blueprints[id] = blueprint; 31 | emit AssetMinted(user, id, blueprint); 32 | } 33 | 34 | function _mintFor(address to, uint256 id, bytes memory blueprint) internal virtual; 35 | } 36 | -------------------------------------------------------------------------------- /contracts/token/erc721/x/utils/Minting.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // solhint-disable compiler-version, custom-errors 3 | pragma solidity ^0.8.4; 4 | 5 | import {Bytes} from "./Bytes.sol"; 6 | 7 | library Minting { 8 | // Split the minting blob into token_id and blueprint portions 9 | // {token_id}:{blueprint} 10 | 11 | function split(bytes calldata blob) internal pure returns (uint256, bytes memory) { 12 | int256 index = Bytes.indexOf(blob, ":", 0); 13 | require(index >= 0, "Separator must exist"); 14 | // Trim the { and } from the parameters 15 | uint256 tokenID = Bytes.toUint(blob[1:uint256(index) - 1]); 16 | uint256 blueprintLength = blob.length - uint256(index) - 3; 17 | if (blueprintLength == 0) { 18 | return (tokenID, bytes("")); 19 | } 20 | bytes calldata blueprint = blob[uint256(index) + 2:blob.length - 1]; 21 | return (tokenID, blueprint); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /contracts/trading/seaport/conduit/ConduitController.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // solhint-disable 3 | pragma solidity ^0.8.14; 4 | 5 | import {ConduitController} from "seaport-core/src/conduit/ConduitController.sol"; 6 | -------------------------------------------------------------------------------- /contracts/trading/seaport/interfaces/ImmutableSeaportEvents.sol: -------------------------------------------------------------------------------- 1 | // Copyright (c) Immutable Pty Ltd 2018 - 2023 2 | // SPDX-License-Identifier: Apache-2 3 | // solhint-disable compiler-version 4 | pragma solidity ^0.8.17; 5 | 6 | /** 7 | * @notice ImmutableSeaportEvents contains events 8 | * related to the ImmutableSeaport contract 9 | */ 10 | interface ImmutableSeaportEvents { 11 | /** 12 | * @dev Emit an event when an allowed zone status is updated 13 | */ 14 | event AllowedZoneSet(address zoneAddress, bool allowed); 15 | } 16 | -------------------------------------------------------------------------------- /contracts/trading/seaport/test/SeaportTestContracts.sol: -------------------------------------------------------------------------------- 1 | // Copyright (c) Immutable Pty Ltd 2018 - 2023 2 | // SPDX-License-Identifier: Apache-2 3 | // solhint-disable 4 | pragma solidity >=0.8.4; 5 | 6 | /** 7 | * @dev Import test contract helpers from Immutable pinned fork of OpenSea's seaport 8 | * These are not deployed - they are only used for testing 9 | */ 10 | import "seaport/contracts/test/TestERC721.sol"; 11 | import "seaport/contracts/test/TestZone.sol"; 12 | -------------------------------------------------------------------------------- /contracts/trading/seaport/validators/ReadOnlyOrderValidator.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // solhint-disable 3 | pragma solidity ^0.8.17; 4 | 5 | import {ReadOnlyOrderValidator} from "seaport/contracts/helpers/order-validator/lib/ReadOnlyOrderValidator.sol"; 6 | -------------------------------------------------------------------------------- /contracts/trading/seaport/validators/SeaportValidator.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // solhint-disable 3 | pragma solidity ^0.8.17; 4 | 5 | import {SeaportValidator} from "seaport/contracts/helpers/order-validator/SeaportValidator.sol"; 6 | -------------------------------------------------------------------------------- /contracts/trading/seaport/validators/SeaportValidatorHelper.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // solhint-disable 3 | pragma solidity ^0.8.17; 4 | 5 | import {SeaportValidatorHelper} from "seaport/contracts/helpers/order-validator/lib/SeaportValidatorHelper.sol"; 6 | -------------------------------------------------------------------------------- /contracts/trading/seaport/zones/immutable-signed-zone/v1/interfaces/SIP5Interface.sol: -------------------------------------------------------------------------------- 1 | // Copyright (c) Immutable Pty Ltd 2018 - 2023 2 | // SPDX-License-Identifier: Apache-2 3 | // solhint-disable compiler-version 4 | pragma solidity ^0.8.17; 5 | 6 | import {Schema} from "seaport-types/src/lib/ConsiderationStructs.sol"; 7 | 8 | /** 9 | * @dev SIP-5: Contract Metadata Interface for Seaport Contracts 10 | * https://github.com/ProjectOpenSea/SIPs/blob/main/SIPS/sip-5.md 11 | */ 12 | // This contract name re-use is OK because the SIP5Interface is an interface and not a deployable contract. 13 | // slither-disable-next-line name-reused 14 | interface SIP5Interface { 15 | /** 16 | * @dev An event that is emitted when a SIP-5 compatible contract is deployed. 17 | */ 18 | event SeaportCompatibleContractDeployed(); 19 | 20 | /** 21 | * @dev Returns Seaport metadata for this contract, returning the 22 | * contract name and supported schemas. 23 | * 24 | * @return name The contract name 25 | * @return schemas The supported SIPs 26 | */ 27 | function getSeaportMetadata() external view returns (string memory name, Schema[] memory schemas); 28 | } 29 | -------------------------------------------------------------------------------- /contracts/trading/seaport/zones/immutable-signed-zone/v1/interfaces/SIP6EventsAndErrors.sol: -------------------------------------------------------------------------------- 1 | // Copyright (c) Immutable Pty Ltd 2018 - 2023 2 | // SPDX-License-Identifier: Apache-2 3 | // solhint-disable compiler-version 4 | pragma solidity ^0.8.17; 5 | 6 | /** 7 | * @notice SIP6EventsAndErrors contains errors and events 8 | * related to zone interaction as specified in the SIP6. 9 | */ 10 | // This contract name re-use is OK because the SIP6EventsAndErrors is an interface and not a deployable contract. 11 | // slither-disable-next-line name-reused 12 | interface SIP6EventsAndErrors { 13 | /** 14 | * @dev Revert with an error if SIP6 version is not supported 15 | */ 16 | error UnsupportedExtraDataVersion(uint8 version); 17 | } 18 | -------------------------------------------------------------------------------- /contracts/trading/seaport/zones/immutable-signed-zone/v1/interfaces/SIP7EventsAndErrors.sol: -------------------------------------------------------------------------------- 1 | // Copyright (c) Immutable Pty Ltd 2018 - 2023 2 | // SPDX-License-Identifier: Apache-2 3 | // solhint-disable compiler-version 4 | pragma solidity ^0.8.17; 5 | 6 | /** 7 | * @notice SIP7EventsAndErrors contains errors and events 8 | * related to zone interaction as specified in the SIP7. 9 | */ 10 | 11 | interface SIP7EventsAndErrors { 12 | /** 13 | * @dev Emit an event when a new signer is added. 14 | */ 15 | event SignerAdded(address signer); 16 | 17 | /** 18 | * @dev Emit an event when a signer is removed. 19 | */ 20 | event SignerRemoved(address signer); 21 | 22 | /** 23 | * @dev Revert with an error if trying to add a signer that is 24 | * already active. 25 | */ 26 | error SignerAlreadyActive(address signer); 27 | 28 | /** 29 | * @dev Revert with an error if trying to remove a signer that is 30 | * not active 31 | */ 32 | error SignerNotActive(address signer); 33 | 34 | /** 35 | * @dev Revert with an error if a new signer is the zero address. 36 | */ 37 | error SignerCannotBeZeroAddress(); 38 | 39 | /** 40 | * @dev Revert with an error if a removed signer is trying to be 41 | * reauthorized. 42 | */ 43 | error SignerCannotBeReauthorized(address signer); 44 | 45 | /** 46 | * @dev Revert with an error when the signature has expired. 47 | */ 48 | error SignatureExpired(uint256 currentTimestamp, uint256 expiration, bytes32 orderHash); 49 | 50 | /** 51 | * @dev Revert with an error if the fulfiller does not match. 52 | */ 53 | error InvalidFulfiller(address expectedFulfiller, address actualFulfiller, bytes32 orderHash); 54 | 55 | /** 56 | * @dev Revert with an error if a substandard validation fails 57 | */ 58 | error SubstandardViolation(uint256 substandardId, string reason, bytes32 orderHash); 59 | 60 | /** 61 | * @dev Revert with an error if supplied order extraData is invalid 62 | * or improperly formatted. 63 | */ 64 | error InvalidExtraData(string reason, bytes32 orderHash); 65 | } 66 | -------------------------------------------------------------------------------- /contracts/trading/seaport/zones/immutable-signed-zone/v1/interfaces/SIP7Interface.sol: -------------------------------------------------------------------------------- 1 | // Copyright (c) Immutable Pty Ltd 2018 - 2023 2 | // SPDX-License-Identifier: Apache-2 3 | // solhint-disable compiler-version 4 | pragma solidity ^0.8.17; 5 | 6 | /** 7 | * @title SIP7Interface 8 | * @author ryanio, Immutable 9 | * @notice ImmutableSignedZone is an implementation of SIP-7 that requires orders 10 | * to be signed by an approved signer. 11 | * https://github.com/ProjectOpenSea/SIPs/blob/main/SIPS/sip-7.md 12 | * 13 | */ 14 | // This contract name re-use is OK because the SIP7Interface is an interface and not a deployable contract. 15 | // slither-disable-next-line name-reused 16 | interface SIP7Interface { 17 | /** 18 | * @dev The struct for storing signer info. 19 | */ 20 | struct SignerInfo { 21 | bool active; 22 | /// If the signer is currently active. 23 | bool previouslyActive; 24 | } 25 | /// If the signer has been active before. 26 | 27 | /** 28 | * @notice Add a new signer to the zone. 29 | * 30 | * @param signer The new signer address to add. 31 | */ 32 | function addSigner(address signer) external; 33 | 34 | /** 35 | * @notice Remove an active signer from the zone. 36 | * 37 | * @param signer The signer address to remove. 38 | */ 39 | function removeSigner(address signer) external; 40 | 41 | /** 42 | * @notice Update the API endpoint returned by this zone. 43 | * 44 | * @param newApiEndpoint The new API endpoint. 45 | */ 46 | function updateAPIEndpoint(string calldata newApiEndpoint) external; 47 | 48 | /** 49 | * @notice Returns signing information about the zone. 50 | * 51 | * @return domainSeparator The domain separator used for signing. 52 | * @return apiEndpoint The API endpoint to get signatures for orders 53 | * using this zone. 54 | */ 55 | function sip7Information() 56 | external 57 | view 58 | returns ( 59 | bytes32 domainSeparator, 60 | string memory apiEndpoint, 61 | uint256[] memory substandards, 62 | string memory documentationURI 63 | ); 64 | } 65 | -------------------------------------------------------------------------------- /contracts/trading/seaport/zones/immutable-signed-zone/v2/ZoneAccessControl.sol: -------------------------------------------------------------------------------- 1 | // Copyright (c) Immutable Pty Ltd 2018 - 2024 2 | // SPDX-License-Identifier: Apache-2 3 | 4 | // solhint-disable-next-line compiler-version 5 | pragma solidity 0.8.20; 6 | 7 | import {AccessControl} from "openzeppelin-contracts-5.0.2/access/AccessControl.sol"; 8 | import {IAccessControl} from "openzeppelin-contracts-5.0.2/access/IAccessControl.sol"; 9 | import {AccessControlEnumerable} from "openzeppelin-contracts-5.0.2/access/extensions/AccessControlEnumerable.sol"; 10 | import {ZoneAccessControlEventsAndErrors} from "../../../../../../contracts/trading/seaport/zones/immutable-signed-zone/v2/interfaces/ZoneAccessControlEventsAndErrors.sol"; 11 | 12 | /** 13 | * @notice ZoneAccessControl encapsulates access control functionality for the zone. 14 | */ 15 | abstract contract ZoneAccessControl is AccessControlEnumerable, ZoneAccessControlEventsAndErrors { 16 | /// @dev Zone manager manages the zone. 17 | bytes32 public constant ZONE_MANAGER_ROLE = bytes32("ZONE_MANAGER"); 18 | 19 | /** 20 | * @notice Constructor to setup initial default admin. 21 | * 22 | * @param owner The address to assign the DEFAULT_ADMIN_ROLE. 23 | */ 24 | constructor(address owner) { 25 | // Grant admin role to the specified owner. 26 | _grantRole(DEFAULT_ADMIN_ROLE, owner); 27 | } 28 | 29 | /** 30 | * @inheritdoc AccessControl 31 | */ 32 | function revokeRole( 33 | bytes32 role, 34 | address account 35 | ) public override(AccessControl, IAccessControl) onlyRole(getRoleAdmin(role)) { 36 | if (role == DEFAULT_ADMIN_ROLE && super.getRoleMemberCount(DEFAULT_ADMIN_ROLE) == 1) { 37 | revert LastDefaultAdminRole(account); 38 | } 39 | 40 | super.revokeRole(role, account); 41 | } 42 | 43 | /** 44 | * @inheritdoc AccessControl 45 | */ 46 | function renounceRole(bytes32 role, address callerConfirmation) public override(AccessControl, IAccessControl) { 47 | if (role == DEFAULT_ADMIN_ROLE && super.getRoleMemberCount(DEFAULT_ADMIN_ROLE) == 1) { 48 | revert LastDefaultAdminRole(callerConfirmation); 49 | } 50 | 51 | super.renounceRole(role, callerConfirmation); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /contracts/trading/seaport/zones/immutable-signed-zone/v2/interfaces/SIP5EventsAndErrors.sol: -------------------------------------------------------------------------------- 1 | // Copyright (c) Immutable Pty Ltd 2018 - 2024 2 | // SPDX-License-Identifier: Apache-2 3 | 4 | // solhint-disable compiler-version 5 | pragma solidity ^0.8.17; 6 | 7 | /** 8 | * @notice SIP5EventsAndErrors contains errors and events 9 | * related to zone interaction as specified in the SIP-5. 10 | */ 11 | interface SIP5EventsAndErrors { 12 | /** 13 | * @dev An event that is emitted when a SIP-5 compatible contract is deployed. 14 | */ 15 | event SeaportCompatibleContractDeployed(); 16 | } 17 | -------------------------------------------------------------------------------- /contracts/trading/seaport/zones/immutable-signed-zone/v2/interfaces/SIP5Interface.sol: -------------------------------------------------------------------------------- 1 | // Copyright (c) Immutable Pty Ltd 2018 - 2024 2 | // SPDX-License-Identifier: Apache-2 3 | 4 | // solhint-disable compiler-version 5 | pragma solidity ^0.8.17; 6 | 7 | import {Schema} from "seaport-types/src/lib/ConsiderationStructs.sol"; 8 | import {SIP5EventsAndErrors} from "./SIP5EventsAndErrors.sol"; 9 | 10 | /** 11 | * @dev SIP-5: Contract Metadata Interface for Seaport Contracts 12 | * https://github.com/ProjectOpenSea/SIPs/blob/main/SIPS/sip-5.md 13 | */ 14 | // This contract name re-use is OK because the SIP5Interface is an interface and not a deployable contract. 15 | // slither-disable-next-line name-reused 16 | interface SIP5Interface is SIP5EventsAndErrors { 17 | /** 18 | * @dev Returns Seaport metadata for this contract, returning the 19 | * contract name and supported schemas. 20 | * 21 | * @return name The contract name 22 | * @return schemas The supported SIPs 23 | */ 24 | function getSeaportMetadata() external view returns (string memory name, Schema[] memory schemas); 25 | } 26 | -------------------------------------------------------------------------------- /contracts/trading/seaport/zones/immutable-signed-zone/v2/interfaces/SIP6EventsAndErrors.sol: -------------------------------------------------------------------------------- 1 | // Copyright (c) Immutable Pty Ltd 2018 - 2024 2 | // SPDX-License-Identifier: Apache-2 3 | 4 | // solhint-disable compiler-version 5 | pragma solidity ^0.8.17; 6 | 7 | /** 8 | * @notice SIP6EventsAndErrors contains errors and events 9 | * related to zone interaction as specified in the SIP-6. 10 | */ 11 | // This contract name re-use is OK because the SIP6EventsAndErrors is an interface and not a deployable contract. 12 | // slither-disable-next-line name-reused 13 | interface SIP6EventsAndErrors { 14 | /** 15 | * @dev Revert with an error if SIP-6 version byte is not supported. 16 | */ 17 | error UnsupportedExtraDataVersion(uint8 version); 18 | } 19 | -------------------------------------------------------------------------------- /contracts/trading/seaport/zones/immutable-signed-zone/v2/interfaces/SIP6Interface.sol: -------------------------------------------------------------------------------- 1 | // Copyright (c) Immutable Pty Ltd 2018 - 2024 2 | // SPDX-License-Identifier: Apache-2 3 | 4 | // solhint-disable compiler-version 5 | pragma solidity ^0.8.17; 6 | 7 | import {SIP6EventsAndErrors} from "./SIP6EventsAndErrors.sol"; 8 | 9 | /** 10 | * @dev SIP-6: Multi-Zone ExtraData 11 | * https://github.com/ProjectOpenSea/SIPs/blob/main/SIPS/sip-6.md 12 | */ 13 | // solhint-disable no-empty-blocks 14 | interface SIP6Interface is SIP6EventsAndErrors {} 15 | -------------------------------------------------------------------------------- /contracts/trading/seaport/zones/immutable-signed-zone/v2/interfaces/SIP7Interface.sol: -------------------------------------------------------------------------------- 1 | // Copyright (c) Immutable Pty Ltd 2018 - 2024 2 | // SPDX-License-Identifier: Apache-2 3 | 4 | // solhint-disable compiler-version 5 | pragma solidity ^0.8.17; 6 | 7 | import {SIP7EventsAndErrors} from "./SIP7EventsAndErrors.sol"; 8 | 9 | /** 10 | * @title SIP7Interface 11 | * @author ryanio, Immutable 12 | * @notice ImmutableSignedZone is an implementation of SIP-7 that requires orders 13 | * to be signed by an approved signer. 14 | * https://github.com/ProjectOpenSea/SIPs/blob/main/SIPS/sip-7.md 15 | * 16 | */ 17 | // This contract name re-use is OK because the SIP7Interface is an interface and not a deployable contract. 18 | // slither-disable-next-line name-reused 19 | interface SIP7Interface is SIP7EventsAndErrors { 20 | /** 21 | * @dev The struct for storing signer info. 22 | */ 23 | struct SignerInfo { 24 | /// @dev If the signer is currently active. 25 | bool active; 26 | /// @dev If the signer has been active before. 27 | bool previouslyActive; 28 | } 29 | 30 | /** 31 | * @notice Add a new signer to the zone. 32 | * 33 | * @param signer The new signer address to add. 34 | */ 35 | function addSigner(address signer) external; 36 | 37 | /** 38 | * @notice Remove an active signer from the zone. 39 | * 40 | * @param signer The signer address to remove. 41 | */ 42 | function removeSigner(address signer) external; 43 | 44 | /** 45 | * @notice Update the API endpoint returned by this zone. 46 | * 47 | * @param newApiEndpoint The new API endpoint. 48 | */ 49 | function updateAPIEndpoint(string calldata newApiEndpoint) external; 50 | 51 | /** 52 | * @notice Update the documentation URI returned by this zone. 53 | * 54 | * @param newDocumentationURI The new documentation URI. 55 | */ 56 | function updateDocumentationURI(string calldata newDocumentationURI) external; 57 | 58 | /** 59 | * @notice Returns signing information about the zone. 60 | * 61 | * @return domainSeparator The domain separator used for signing. 62 | * @return apiEndpoint The API endpoint to get signatures for orders 63 | * using this zone. 64 | */ 65 | function sip7Information() 66 | external 67 | view 68 | returns ( 69 | bytes32 domainSeparator, 70 | string memory apiEndpoint, 71 | uint256[] memory substandards, 72 | string memory documentationURI 73 | ); 74 | } 75 | -------------------------------------------------------------------------------- /contracts/trading/seaport/zones/immutable-signed-zone/v2/interfaces/ZoneAccessControlEventsAndErrors.sol: -------------------------------------------------------------------------------- 1 | // Copyright (c) Immutable Pty Ltd 2018 - 2024 2 | // SPDX-License-Identifier: Apache-2 3 | 4 | // solhint-disable compiler-version 5 | pragma solidity ^0.8.17; 6 | 7 | /** 8 | * @notice ZoneAccessControlEventsAndErrors contains errors and events 9 | * related to zone access control. 10 | */ 11 | interface ZoneAccessControlEventsAndErrors { 12 | /** 13 | * @dev Revert with an error if revoking last DEFAULT_ADMIN_ROLE. 14 | */ 15 | error LastDefaultAdminRole(address account); 16 | } 17 | -------------------------------------------------------------------------------- /deploy/utils.ts: -------------------------------------------------------------------------------- 1 | export const getImmutableBridgeAddress = (network: string) => { 2 | switch (network) { 3 | case "sepolia": 4 | return "0x2d5C349fD8464DA06a3f90b4B0E9195F3d1b7F98"; // sepolia 5 | case "mainnet": 6 | return "0x5FDCCA53617f4d2b9134B29090C87D01058e27e9"; 7 | } 8 | throw Error("Invalid network selected"); 9 | }; 10 | 11 | export const sleep = (ms: number) => { 12 | return new Promise((resolve) => setTimeout(resolve, ms)); 13 | }; 14 | -------------------------------------------------------------------------------- /deploy/x/README.md: -------------------------------------------------------------------------------- 1 | # Asset Contract Deployment 2 | 3 | How to deploy the Asset contract to allow minting on Immutable X. 4 | 5 | 1. Install project dependencies: 6 | 7 | ```shell 8 | yarn install 9 | ``` 10 | 11 | 2. Make a copy of `.env.examples` and rename the copy to `.env`: 12 | 13 | ```shell 14 | cp .env.example .env 15 | ``` 16 | 17 | 3. Update the environment variables in `.env`. You will need the following: 18 | 19 | - [An Etherscan API key](https://docs.etherscan.io/getting-started/viewing-api-usage-statistics). 20 | - [Alchemy API keys for Sepolia and Mainnet](https://docs.alchemy.com/docs/alchemy-quickstart-guide#1key-create-an-alchemy-key). 21 | - Private key for a wallet with enough ETH to deploy the contract. 22 | 23 | 4. Generate contract artifacts 24 | 25 | ```shell 26 | yarn compile 27 | ``` 28 | 29 | 5. Deploy the Asset contract 30 | 31 | ```shell 32 | yarn hardhat deploy:x:asset --network sepolia --name "" --symbol 33 | ``` 34 | 35 | The deploy task will deploy the contract to the specified network, wait 5 mins to allow time for the contract to deploy, then verify the contract. 36 | -------------------------------------------------------------------------------- /deploy/x/asset.ts: -------------------------------------------------------------------------------- 1 | import { task } from "hardhat/config"; 2 | import { getImmutableBridgeAddress, sleep } from "../utils"; 3 | 4 | // Deploy Immutable X Asset contract 5 | // this is used in the Zero-to-Hero guide 6 | // https://docs.immutable.com/docs/x/zero-to-hero-nft-minting/ 7 | const deployAsset = task("deploy:x:asset", "Deploy the Asset contract") 8 | .addParam("name", "Contract name") 9 | .addParam("symbol", "Contract symbol") 10 | .setAction(async (taskArgs, hre) => { 11 | const [deployer] = await hre.ethers.getSigners(); 12 | 13 | const owner = deployer.address; 14 | const { name, symbol } = taskArgs; 15 | const allowedNetworks = ["mainnet", "sepolia"]; 16 | 17 | if (!allowedNetworks.includes(hre.network.name)) { 18 | throw new Error(`please pass a valid --network [ ${allowedNetworks.join(" | ")} ]`); 19 | } 20 | 21 | const Asset = await hre.ethers.getContractFactory("Asset"); 22 | const immutableBridgeAddress = getImmutableBridgeAddress(hre.network.name); 23 | const asset = await Asset.deploy(owner, name, symbol, immutableBridgeAddress); 24 | 25 | console.log("Deployed Contract Address:", asset.address); 26 | console.log("Verifying contract in 5 minutes..."); 27 | await sleep(60000 * 5); 28 | 29 | await hre.run("verify:verify", { 30 | address: asset.address, 31 | constructorArguments: [owner, name, symbol, immutableBridgeAddress], 32 | }); 33 | }); 34 | 35 | export default deployAsset; 36 | -------------------------------------------------------------------------------- /foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = 'contracts' 3 | out = 'foundry-out' 4 | # libs = ["lib", "node_modules"] 5 | libs = ["lib"] 6 | fs_permissions = [{ access = "read", path = "./foundry-out" }] 7 | 8 | # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options 9 | 10 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | export * from "./clients"; 2 | export * from "./abi"; 3 | -------------------------------------------------------------------------------- /perfTest/README.md: -------------------------------------------------------------------------------- 1 | # Gas / Performance test 2 | 3 | To run these tests: 4 | 5 | ``` 6 | forge test -C perfTest --match-path "./perfTest/**" -vvv --block-gas-limit 1000000000000 7 | ``` 8 | 9 | To run tests for just one contract do something similar to: 10 | 11 | ``` 12 | forge test -C perfTest --match-path "./perfTest/**/ImmutableERC721V2ByQuantityPerfPrefill.t.sol" -vvv --block-gas-limit 1000000000000 13 | 14 | ``` -------------------------------------------------------------------------------- /perfTest/token/erc721/ImmutableERC721ByIdPerf.t.sol: -------------------------------------------------------------------------------- 1 | // Copyright Immutable Pty Ltd 2018 - 2025 2 | // SPDX-License-Identifier: Apache 2.0 3 | 4 | pragma solidity >=0.8.19 <0.8.29; 5 | 6 | import "forge-std/Test.sol"; 7 | import {ERC721PerfTest} from "./ERC721Perf.t.sol"; 8 | import {ImmutableERC721} from "../../../contracts/token/erc721/preset/ImmutableERC721.sol"; 9 | import {IImmutableERC721ByQuantity} from "../../../contracts/token/erc721/interfaces/IImmutableERC721ByQuantity.sol"; 10 | import {IImmutableERC721} from "../../../contracts/token/erc721/interfaces/IImmutableERC721.sol"; 11 | import {ImmutableERC721MintByID} from "../../../contracts/token/erc721/preset/ImmutableERC721MintByID.sol"; 12 | 13 | /** 14 | * Contract for ERC 721 by ID perfromance tests, for ImmutableERC721.sol (that is, v1). 15 | */ 16 | contract ImmutableERC721ByIdPerfTest is ERC721PerfTest { 17 | function setUpStart() public virtual override { 18 | super.setUpStart(); 19 | 20 | ImmutableERC721MintByID immutableERC721 = new ImmutableERC721MintByID( 21 | owner, name, symbol, baseURI, contractURI, address(allowlist), feeReceiver, feeNumerator 22 | ); 23 | 24 | // ImmutableERC721 does not implement the interface, and hence must be cast to the 25 | // interface type. 26 | erc721 = IImmutableERC721(address(immutableERC721)); 27 | 28 | vm.prank(owner); 29 | erc721.grantMinterRole(minter); 30 | 31 | // Mint the first NFT to prefillUser1 32 | firstNftId = 0; 33 | vm.prank(minter); 34 | erc721.mint(prefillUser1, firstNftId); 35 | } 36 | 37 | function notOwnedRevertError(uint256 /* _tokenIdToBeBurned */) public pure override returns (bytes memory) { 38 | return "ERC721: caller is not token owner or approved"; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /perfTest/token/erc721/ImmutableERC721ByIdPerfPrefill.t.sol: -------------------------------------------------------------------------------- 1 | // Copyright Immutable Pty Ltd 2018 - 2025 2 | // SPDX-License-Identifier: Apache 2.0 3 | 4 | pragma solidity >=0.8.19 <0.8.29; 5 | 6 | import {ImmutableERC721ByIdPerfTest} from "./ImmutableERC721ByIdPerf.t.sol"; 7 | import {ImmutableERC721} from "../../../contracts/token/erc721/preset/ImmutableERC721.sol"; 8 | import {IImmutableERC721ByQuantity} from "../../../contracts/token/erc721/interfaces/IImmutableERC721ByQuantity.sol"; 9 | 10 | /** 11 | * ImmutableERC721ByIdPerfTest, but prefilling the contract with data. 12 | */ 13 | contract ImmutableERC721ByIdPerfPrefillTest is ImmutableERC721ByIdPerfTest { 14 | function setUpStart() public override { 15 | super.setUpStart(); 16 | prefillWithNfts(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /perfTest/token/erc721/ImmutableERC721ByQuantityPerf.t.sol: -------------------------------------------------------------------------------- 1 | // Copyright Immutable Pty Ltd 2018 - 2025 2 | // SPDX-License-Identifier: Apache 2.0 3 | 4 | pragma solidity >=0.8.19 <0.8.29; 5 | 6 | import "forge-std/Test.sol"; 7 | import {ERC721ByQuantityPerfTest} from "./ERC721ByQuantityPerf.t.sol"; 8 | import {ImmutableERC721} from "../../../contracts/token/erc721/preset/ImmutableERC721.sol"; 9 | import {IImmutableERC721ByQuantity} from "../../../contracts/token/erc721/interfaces/IImmutableERC721ByQuantity.sol"; 10 | import {IImmutableERC721} from "../../../contracts/token/erc721/interfaces/IImmutableERC721.sol"; 11 | 12 | /** 13 | * Contract for ERC 721 by quantity performance tests, for ImmutableERC721.sol (that is, v1). 14 | */ 15 | contract ImmutableERC721ByQuantityPerfTest is ERC721ByQuantityPerfTest { 16 | function setUpStart() public virtual override { 17 | super.setUpStart(); 18 | 19 | ImmutableERC721 immutableERC721 = new ImmutableERC721( 20 | owner, name, symbol, baseURI, contractURI, address(allowlist), feeReceiver, feeNumerator 21 | ); 22 | 23 | // ImmutableERC721 does not implement the interface, and hence must be cast to the 24 | // interface type. 25 | erc721BQ = IImmutableERC721ByQuantity(address(immutableERC721)); 26 | erc721 = IImmutableERC721(address(immutableERC721)); 27 | 28 | vm.prank(owner); 29 | erc721.grantMinterRole(minter); 30 | 31 | // Mint the first NFT to prefillUser1 32 | vm.recordLogs(); 33 | vm.prank(minter); 34 | erc721BQ.safeMintByQuantity(prefillUser1, 1); 35 | Vm.Log[] memory entries = vm.getRecordedLogs(); 36 | // Expect 1 of 37 | // event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); 38 | assertEq(entries.length, 1, "More logs than expected"); 39 | firstNftId = uint256(entries[0].topics[3]); 40 | //emit log_named_bytes32("First NFT ID", bytes32(firstNftId)); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /perfTest/token/erc721/ImmutableERC721ByQuantityPerfPrefill.t.sol: -------------------------------------------------------------------------------- 1 | // Copyright Immutable Pty Ltd 2018 - 2025 2 | // SPDX-License-Identifier: Apache 2.0 3 | 4 | pragma solidity >=0.8.19 <0.8.29; 5 | 6 | import {ImmutableERC721ByQuantityPerfTest} from "./ImmutableERC721ByQuantityPerf.t.sol"; 7 | 8 | /** 9 | * ImmutableERC721ByQuantityPerfTest, but prefilling the contract with data. 10 | */ 11 | contract ImmutableERC721ByQuantityPerfPrefillTest is ImmutableERC721ByQuantityPerfTest { 12 | function setUpStart() public override { 13 | super.setUpStart(); 14 | prefillWithNfts(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /perfTest/token/erc721/ImmutableERC721V2ByQuantityPerf.t.sol: -------------------------------------------------------------------------------- 1 | // Copyright Immutable Pty Ltd 2018 - 2025 2 | // SPDX-License-Identifier: Apache 2.0 3 | 4 | pragma solidity >=0.8.19 <0.8.29; 5 | 6 | import "forge-std/Test.sol"; 7 | import {ERC721ByQuantityPerfTest} from "./ERC721ByQuantityPerf.t.sol"; 8 | import {ImmutableERC721V2} from "../../../contracts/token/erc721/preset/ImmutableERC721V2.sol"; 9 | import {IImmutableERC721ByQuantity} from "../../../contracts/token/erc721/interfaces/IImmutableERC721ByQuantity.sol"; 10 | import {IImmutableERC721} from "../../../contracts/token/erc721/interfaces/IImmutableERC721.sol"; 11 | 12 | /** 13 | * Contract for ERC 721 by quantity performance tests, for ImmutableERC721V2.sol. 14 | */ 15 | contract ImmutableERC721V2ByQuantityPerfTest is ERC721ByQuantityPerfTest { 16 | function setUpStart() public virtual override { 17 | super.setUpStart(); 18 | 19 | ImmutableERC721V2 immutableERC721 = new ImmutableERC721V2( 20 | owner, name, symbol, baseURI, contractURI, address(allowlist), feeReceiver, feeNumerator 21 | ); 22 | 23 | // ImmutableERC721 does not implement the interface, and hence must be cast to the 24 | // interface type. 25 | erc721BQ = IImmutableERC721ByQuantity(address(immutableERC721)); 26 | erc721 = IImmutableERC721(address(immutableERC721)); 27 | 28 | vm.prank(owner); 29 | erc721.grantMinterRole(minter); 30 | 31 | // Mint the first NFT to prefillUser1 32 | vm.recordLogs(); 33 | vm.prank(minter); 34 | erc721BQ.safeMintByQuantity(prefillUser1, 1); 35 | firstNftId = findFirstNftId(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /perfTest/token/erc721/ImmutableERC721V2ByQuantityPerfPrefill.t.sol: -------------------------------------------------------------------------------- 1 | // Copyright Immutable Pty Ltd 2018 - 2025 2 | // SPDX-License-Identifier: Apache 2.0 3 | 4 | pragma solidity >=0.8.19 <0.8.29; 5 | 6 | import {ImmutableERC721V2ByQuantityPerfTest} from "./ImmutableERC721V2ByQuantityPerf.t.sol"; 7 | 8 | /** 9 | * ImmutableERC721ByQuantityPerfTest, but prefilling the contract with data. 10 | */ 11 | contract ImmutableERC721V2ByQuantityPerfPrefillTest is ImmutableERC721V2ByQuantityPerfTest { 12 | function setUpStart() public override { 13 | super.setUpStart(); 14 | prefillWithNfts(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /readmecheck.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | file_to_find=README.md 3 | parent_dir=contracts 4 | fail=0 5 | 6 | find "$parent_dir" -type d | 7 | { 8 | while IFS= read -r subdir; do 9 | if [ ! -f "$subdir/$file_to_find" ]; then 10 | echo README.md not found in "$subdir" 11 | fail=1 12 | fi 13 | done 14 | 15 | if [ $fail -eq 1 ]; then 16 | echo Found at least one directory missing a README.md. 17 | echo NOT FAILING BUILD 18 | # exit 1 19 | exit 0 20 | else 21 | echo All directories have a README.md file. 22 | exit 0 23 | fi 24 | } 25 | -------------------------------------------------------------------------------- /remappings.txt: -------------------------------------------------------------------------------- 1 | @openzeppelin/contracts/=lib/openzeppelin-contracts-4.9.3/contracts/ 2 | openzeppelin-contracts-5.0.2/=lib/openzeppelin-contracts-5.0.2/contracts/ 3 | openzeppelin-contracts-upgradeable-4.9.3/=lib/openzeppelin-contracts-upgradeable-4.9.3/contracts/ 4 | openzeppelin-contracts-4.9.3/=lib/openzeppelin-contracts-4.9.3/contracts/ 5 | solidity-bits/=lib/solidity-bits/ 6 | solidity-bytes-utils/=lib/solidity-bytes-utils/ 7 | seaport/contracts/=lib/immutable-seaport-1.5.0+im1.3/contracts/ 8 | seaport-core/=lib/immutable-seaport-core-1.5.0+im1/ 9 | seaport-types/=lib/immutable-seaport-1.5.0+im1.3/lib/seaport-types/ 10 | @axelar-network/axelar-gmp-sdk-solidity=lib/axelar-gmp-sdk-solidity -------------------------------------------------------------------------------- /script/bridge/x/v4/DeployRegistrationV4.s.sol: -------------------------------------------------------------------------------- 1 | // Copyright (c) Immutable Pty Ltd 2018 - 2024 2 | // SPDX-License-Identifier: MIT 3 | pragma solidity ^0.8.20; 4 | 5 | import {RegistrationV4} from "../../../../contracts/bridge/x/v4/RegistrationV4.sol"; 6 | import {Script} from "forge-std/Script.sol"; 7 | 8 | contract DeployRegistrationV4 is Script { 9 | address public MAINNET_CONTRACT = 0x5FDCCA53617f4d2b9134B29090C87D01058e27e9; 10 | uint256 private MAINNET_CHAIN_ID = 1; 11 | 12 | RegistrationV4 public registration; 13 | 14 | function run() external returns (RegistrationV4) { 15 | require(block.chainid == MAINNET_CHAIN_ID, "wrong chain id, please use mainnet chain"); 16 | 17 | vm.startBroadcast(); 18 | registration = new RegistrationV4(payable(MAINNET_CONTRACT)); 19 | vm.stopBroadcast(); 20 | return registration; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /script/bridge/x/v4/DeployRegistrationV4Dev.s.sol: -------------------------------------------------------------------------------- 1 | // Copyright (c) Immutable Pty Ltd 2018 - 2024 2 | // SPDX-License-Identifier: MIT 3 | pragma solidity ^0.8.20; 4 | 5 | import {RegistrationV4} from "../../../../contracts/bridge/x/v4/RegistrationV4.sol"; 6 | import {Script} from "forge-std/Script.sol"; 7 | 8 | contract DeployRegistrationV4Dev is Script { 9 | address public DEV_CONTRACT = 0x590C809bd5FF50DCb39e4320b60139B29B880174; 10 | uint256 private SEPOLIA_CHAIN_ID = 11155111; 11 | 12 | RegistrationV4 public registration; 13 | 14 | function run() external returns (RegistrationV4) { 15 | require(block.chainid == SEPOLIA_CHAIN_ID, "wrong chain id, please use sepolia chain"); 16 | 17 | vm.startBroadcast(); 18 | registration = new RegistrationV4(payable(DEV_CONTRACT)); 19 | vm.stopBroadcast(); 20 | return registration; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /script/bridge/x/v4/DeployRegistrationV4Sandbox.s.sol: -------------------------------------------------------------------------------- 1 | // Copyright (c) Immutable Pty Ltd 2018 - 2024 2 | // SPDX-License-Identifier: MIT 3 | pragma solidity ^0.8.20; 4 | 5 | import {RegistrationV4} from "../../../../contracts/bridge/x/v4/RegistrationV4.sol"; 6 | import {Script} from "forge-std/Script.sol"; 7 | 8 | contract DeployRegistrationV4Sandbox is Script { 9 | address public SANDBOX_CONTRACT = 0x2d5C349fD8464DA06a3f90b4B0E9195F3d1b7F98; 10 | uint256 private SEPOLIA_CHAIN_ID = 11155111; 11 | 12 | RegistrationV4 public registration; 13 | 14 | function run() external returns (RegistrationV4) { 15 | require(block.chainid == SEPOLIA_CHAIN_ID, "wrong chain id, please use sepolia chain"); 16 | 17 | vm.startBroadcast(); 18 | registration = new RegistrationV4(payable(SANDBOX_CONTRACT)); 19 | vm.stopBroadcast(); 20 | return registration; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /script/staking/deployComplex.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | FUNCTION_TO_EXECUTE='deployComplex()' 3 | # Set-up variables and execute forge 4 | source $(dirname "$0")/common.sh 5 | 6 | 7 | -------------------------------------------------------------------------------- /script/staking/deployDeployer.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | FUNCTION_TO_EXECUTE='deployDeployer()' 3 | # Set-up variables and execute forge 4 | source $(dirname "$0")/common.sh 5 | 6 | 7 | -------------------------------------------------------------------------------- /script/staking/deploySimple.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | FUNCTION_TO_EXECUTE='deploySimple()' 3 | # Set-up variables and execute forge 4 | source $(dirname "$0")/common.sh 5 | 6 | 7 | -------------------------------------------------------------------------------- /script/staking/stake.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | FUNCTION_TO_EXECUTE='stake()' 3 | # Set-up variables and execute forge 4 | source $(dirname "$0")/common.sh 5 | 6 | 7 | -------------------------------------------------------------------------------- /script/staking/unstake.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | FUNCTION_TO_EXECUTE='unstake()' 3 | # Set-up variables and execute forge 4 | source $(dirname "$0")/common.sh 5 | 6 | 7 | -------------------------------------------------------------------------------- /script/trading/seaport/DeployImmutableSignedZoneV2Dev.s.sol: -------------------------------------------------------------------------------- 1 | // Copyright (c) Immutable Pty Ltd 2018 - 2024 2 | // SPDX-License-Identifier: Apache-2 3 | 4 | import {Script} from "forge-std/Script.sol"; 5 | import {ImmutableSignedZoneV2} from 6 | "../../../contracts/trading/seaport/zones/immutable-signed-zone/v2/ImmutableSignedZoneV2.sol"; 7 | 8 | // solhint-disable-next-line compiler-version 9 | pragma solidity 0.8.20; 10 | 11 | // Deploy ImmutableSignedZoneV2 to dev environment (without create3) 12 | contract DeployImmutableSignedZoneV2Dev is Script { 13 | function run() external { 14 | vm.startBroadcast(); 15 | 16 | // replace args with test values if necessary 17 | ImmutableSignedZoneV2 c = new ImmutableSignedZoneV2( 18 | "ImmutableSignedZone", "", "", address(0xC606830D8341bc9F5F5Dd7615E9313d2655B505D) 19 | ); 20 | 21 | c.grantRole(bytes32("ZONE_MANAGER"), address(0xC606830D8341bc9F5F5Dd7615E9313d2655B505D)); 22 | 23 | // set server side signer address 24 | c.addSigner(address(0xBE63B9F9F2Ed97fac4b71630268bC050ddB53395)); 25 | 26 | vm.stopBroadcast(); 27 | } 28 | } 29 | 30 | // forge script script/trading/seaport/DeployImmutableSignedZoneV2Dev.s.sol:DeployImmutableSignedZoneV2Dev --rpc-url "https://rpc.dev.immutable.com" --broadcast -vvvv --priority-gas-price 10000000000 --with-gas-price 11000000000 --private-key=xx 31 | -------------------------------------------------------------------------------- /slither.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "detectors_to_exclude": "naming-convention, solc-version, similar-names, timestamp, assembly", 3 | 4 | "filter_paths": "lib" 5 | } 6 | 7 | -------------------------------------------------------------------------------- /test/allowlist/MockOAL.sol: -------------------------------------------------------------------------------- 1 | // Copyright Immutable Pty Ltd 2018 - 2023 2 | // SPDX-License-Identifier: Apache 2.0 3 | pragma solidity >=0.8.19 <0.8.29; 4 | 5 | import {OperatorAllowlistUpgradeable} from "../../contracts/allowlist/OperatorAllowlistUpgradeable.sol"; 6 | 7 | /* 8 | OperatorAllowlist is an implementation of a Allowlist registry, storing addresses and bytecode 9 | which are allowed to be approved operators and execute transfers of interfacing token contracts (e.g. ERC721/ERC1155). 10 | The registry will be a deployed contract that tokens may interface with and point to. 11 | OperatorAllowlist is not designed to be upgradeable or extended. 12 | */ 13 | 14 | contract MockOperatorAllowlistUpgradeable is OperatorAllowlistUpgradeable { 15 | uint256 public mockInt; 16 | 17 | function setMockValue(uint256 val) public { 18 | mockInt = val; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /test/deployer/create2/Create2Utils.sol: -------------------------------------------------------------------------------- 1 | // Copyright Immutable Pty Ltd 2018 - 2023 2 | // SPDX-License-Identifier: Apache 2.0 3 | pragma solidity >=0.8.19 <0.8.29; 4 | 5 | import "forge-std/Test.sol"; 6 | 7 | contract Create2Utils is Test { 8 | function predictCreate2Address(bytes memory _bytecode, address _deployer, address _sender, bytes32 _salt) 9 | public 10 | pure 11 | returns (address) 12 | { 13 | bytes32 deploySalt = keccak256(abi.encode(_sender, _salt)); 14 | return address( 15 | uint160(uint256(keccak256(abi.encodePacked(hex"ff", address(_deployer), deploySalt, keccak256(_bytecode))))) 16 | ); 17 | } 18 | 19 | function createSaltFromKey(string memory key, address owner) public pure returns (bytes32) { 20 | return keccak256(abi.encode(address(owner), key)); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /test/deployer/create3/Create3Utils.sol: -------------------------------------------------------------------------------- 1 | // Copyright Immutable Pty Ltd 2018 - 2023 2 | // SPDX-License-Identifier: Apache 2.0 3 | pragma solidity >=0.8.19 <0.8.29; 4 | 5 | import "forge-std/Test.sol"; 6 | import {IDeployer} from "@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IDeployer.sol"; 7 | 8 | contract Create3Utils is Test { 9 | function predictCreate3Address(IDeployer _deployer, address _sender, bytes32 _salt) public view returns (address) { 10 | return _deployer.deployedAddress("", _sender, _salt); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/games/gems/GemGame.t.sol: -------------------------------------------------------------------------------- 1 | // Copyright Immutable Pty Ltd 2018 - 2024 2 | // SPDX-License-Identifier: Apache 2.0 3 | // solhint-disable not-rely-on-time 4 | 5 | pragma solidity >=0.8.19 <0.8.29; 6 | 7 | import "forge-std/Test.sol"; 8 | import {GemGame, Unauthorized, ContractPaused} from "../../../contracts/games/gems/GemGame.sol"; 9 | 10 | contract GemGameTest is Test { 11 | event GemEarned(address indexed account, uint256 timestamp); 12 | 13 | GemGame private _gemGame; 14 | 15 | function setUp() public { 16 | _gemGame = new GemGame(address(this), address(this), address(this)); 17 | } 18 | 19 | function testEarnGemEmitsGemEarnedEvent() public { 20 | vm.expectEmit(true, true, false, false); 21 | emit GemEarned(address(this), block.timestamp); 22 | _gemGame.earnGem(); 23 | } 24 | 25 | function testEarnGemContractPausedReverts() public { 26 | // pause the contract 27 | _gemGame.pause(); 28 | 29 | // attempt to earn a gem 30 | vm.expectRevert(ContractPaused.selector); 31 | _gemGame.earnGem(); 32 | } 33 | 34 | function testPausePausesContract() public { 35 | // pause the contract 36 | _gemGame.pause(); 37 | 38 | assertEq(_gemGame.paused(), true, "GemGame should be paused"); 39 | } 40 | 41 | function testPauseWithoutPauseRoleReverts() public { 42 | // revoke the pause role 43 | _gemGame.revokeRole(keccak256("PAUSE"), address(this)); 44 | 45 | // attempt to pause 46 | vm.expectRevert(Unauthorized.selector); 47 | _gemGame.pause(); 48 | } 49 | 50 | function testUnpauseUnpausesContract() public { 51 | // pause the contract 52 | _gemGame.pause(); 53 | 54 | // unpause the contract 55 | _gemGame.unpause(); 56 | 57 | assertEq(_gemGame.paused(), false, "GemGame should be unpaused"); 58 | } 59 | 60 | function testUnpauseWithoutPauseRoleReverts() public { 61 | // revoke the unpause role 62 | _gemGame.revokeRole(keccak256("UNPAUSE"), address(this)); 63 | 64 | // attempt to unpause 65 | vm.expectRevert(Unauthorized.selector); 66 | _gemGame.unpause(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /test/multicall/SigUtils.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.19 <0.8.29; 3 | 4 | import {GuardedMulticaller2} from "../../contracts/multicall/GuardedMulticaller2.sol"; 5 | 6 | contract SigUtils { 7 | bytes32 private constant _TYPE_HASH = 8 | keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); 9 | 10 | bytes32 internal constant CALL_TYPEHASH = keccak256("Call(address target,string functionSignature,bytes data)"); 11 | 12 | bytes32 internal constant MULTICALL_TYPEHASH = 13 | keccak256( 14 | "Multicall(bytes32 ref,Call[] calls,uint256 deadline)Call(address target,string functionSignature,bytes data)" 15 | ); 16 | 17 | bytes32 private immutable cachedDomainSeparator; 18 | 19 | constructor(string memory _name, string memory _version, address _verifyingContract) { 20 | cachedDomainSeparator = keccak256(abi.encode(_TYPE_HASH, keccak256(bytes(_name)), keccak256(bytes(_version)), block.chainid, _verifyingContract)); 21 | } 22 | 23 | function _hashCallArray(GuardedMulticaller2.Call[] calldata _calls) internal pure returns (bytes32) { 24 | bytes32[] memory hashedCallArr = new bytes32[](_calls.length); 25 | for (uint256 i = 0; i < _calls.length; i++) { 26 | hashedCallArr[i] = keccak256( 27 | abi.encode(CALL_TYPEHASH, _calls[i].target, _calls[i].functionSignature, _calls[i].data) 28 | ); 29 | } 30 | return keccak256(abi.encode(hashedCallArr)); 31 | } 32 | 33 | function hashTypedData( 34 | bytes32 _reference, 35 | GuardedMulticaller2.Call[] calldata _calls, 36 | uint256 _deadline 37 | ) public view returns (bytes32) { 38 | bytes32 digest = keccak256(abi.encode(MULTICALL_TYPEHASH, _reference, _hashCallArray(_calls), _deadline)); 39 | return keccak256(abi.encodePacked("\x19\x01", cachedDomainSeparator, digest)); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /test/payment-splitter/MockERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.9; 3 | 4 | // Basic ERC20. It is deployed by the tests in order to help testing the PaymentSplitter ERC20 payment feature 5 | 6 | import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 7 | 8 | contract MockERC20 is ERC20 { 9 | constructor(string memory name, string memory symbol) ERC20(name, symbol) {} 10 | 11 | function mint(address account, uint256 amount) public { 12 | _mint(account, amount); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/staking/StakeHolderAttackWallet.sol: -------------------------------------------------------------------------------- 1 | // Copyright Immutable Pty Ltd 2018 - 2024 2 | // SPDX-License-Identifier: Apache 2.0 3 | pragma solidity >=0.8.19 <0.8.29; 4 | 5 | import {StakeHolderNative} from "../../contracts/staking/StakeHolderNative.sol"; 6 | 7 | // Wallet designed to attempt reentrancy attacks 8 | contract StakeHolderAttackWallet { 9 | StakeHolderNative public stakeHolder; 10 | constructor(address _stakeHolder) { 11 | stakeHolder = StakeHolderNative(_stakeHolder); 12 | } 13 | receive() external payable { 14 | // Assumung the call to unstake is for a "whole" number, say 1 ether, then 15 | // this if statement will be chosen first time through the loop. The second 16 | // time through, the msg.value will have the bottom bit set, and this if 17 | // statement will be skipped. 18 | if (msg.value & 0x01 == 0) { 19 | stakeHolder.unstake(msg.value + 1); 20 | } 21 | } 22 | function stake(uint256 _amount) external { 23 | stakeHolder.stake{value: _amount}(_amount); 24 | } 25 | function unstake(uint256 _amount) external { 26 | stakeHolder.unstake(_amount); 27 | } 28 | } 29 | 30 | -------------------------------------------------------------------------------- /test/staking/StakeHolderBase.t.sol: -------------------------------------------------------------------------------- 1 | // Copyright Immutable Pty Ltd 2018 - 2024 2 | // SPDX-License-Identifier: Apache 2.0 3 | // solhint-disable not-rely-on-time 4 | 5 | pragma solidity >=0.8.19 <0.8.29; 6 | 7 | // solhint-disable-next-line no-global-import 8 | import "forge-std/Test.sol"; 9 | import {IStakeHolder} from "../../contracts/staking/IStakeHolder.sol"; 10 | import {StakeHolderNative} from "../../contracts/staking/StakeHolderNative.sol"; 11 | 12 | abstract contract StakeHolderBaseTest is Test { 13 | 14 | bytes32 public defaultAdminRole; 15 | bytes32 public upgradeRole; 16 | bytes32 public distributeRole; 17 | 18 | IStakeHolder public stakeHolder; 19 | 20 | address public roleAdmin; 21 | address public upgradeAdmin; 22 | address public distributeAdmin; 23 | 24 | address public staker1; 25 | address public staker2; 26 | address public staker3; 27 | address public bank; 28 | 29 | function setUp() public virtual { 30 | roleAdmin = makeAddr("RoleAdmin"); 31 | upgradeAdmin = makeAddr("UpgradeAdmin"); 32 | distributeAdmin = makeAddr("DistributeAdmin"); 33 | 34 | staker1 = makeAddr("Staker1"); 35 | staker2 = makeAddr("Staker2"); 36 | staker3 = makeAddr("Staker3"); 37 | bank = makeAddr("bank"); 38 | 39 | StakeHolderNative temp = new StakeHolderNative(); 40 | defaultAdminRole = temp.DEFAULT_ADMIN_ROLE(); 41 | upgradeRole = temp.UPGRADE_ROLE(); 42 | distributeRole = temp.DISTRIBUTE_ROLE(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /test/staking/StakeHolderConfigERC20.t.sol: -------------------------------------------------------------------------------- 1 | // Copyright Immutable Pty Ltd 2018 - 2024 2 | // SPDX-License-Identifier: Apache 2.0 3 | pragma solidity >=0.8.19 <0.8.29; 4 | 5 | // solhint-disable-next-line no-global-import 6 | import "forge-std/Test.sol"; 7 | import {StakeHolderERC20} from "../../contracts/staking/StakeHolderERC20.sol"; 8 | import {IStakeHolder} from "../../contracts/staking/IStakeHolder.sol"; 9 | import {StakeHolderBase} from "../../contracts/staking/StakeHolderBase.sol"; 10 | import {StakeHolderConfigBaseTest} from "./StakeHolderConfigBase.t.sol"; 11 | import {ERC1967Proxy} from "openzeppelin-contracts-4.9.3/proxy/ERC1967/ERC1967Proxy.sol"; 12 | 13 | contract StakeHolderERC20V2 is StakeHolderERC20 { 14 | function upgradeStorage(bytes memory /* _data */) external override(StakeHolderBase) { 15 | version = 1; 16 | } 17 | } 18 | 19 | contract StakeHolderConfigERC20Test is StakeHolderConfigBaseTest { 20 | 21 | function setUp() public override { 22 | super.setUp(); 23 | 24 | StakeHolderERC20 impl = new StakeHolderERC20(); 25 | 26 | bytes memory initData = abi.encodeWithSelector( 27 | StakeHolderERC20.initialize.selector, roleAdmin, upgradeAdmin, distributeAdmin, address(0) 28 | ); 29 | 30 | ERC1967Proxy proxy = new ERC1967Proxy(address(impl), initData); 31 | stakeHolder = IStakeHolder(address(proxy)); 32 | } 33 | 34 | function _deployV1() internal override returns(IStakeHolder) { 35 | return IStakeHolder(address(new StakeHolderERC20())); 36 | } 37 | 38 | function _deployV2() internal override returns(IStakeHolder) { 39 | return IStakeHolder(address(new StakeHolderERC20V2())); 40 | } 41 | } -------------------------------------------------------------------------------- /test/staking/StakeHolderConfigNative.t.sol: -------------------------------------------------------------------------------- 1 | // Copyright Immutable Pty Ltd 2018 - 2024 2 | // SPDX-License-Identifier: Apache 2.0 3 | pragma solidity >=0.8.19 <0.8.29; 4 | 5 | // solhint-disable-next-line no-global-import 6 | import "forge-std/Test.sol"; 7 | import {StakeHolderNative} from "../../contracts/staking/StakeHolderNative.sol"; 8 | import {IStakeHolder} from "../../contracts/staking/IStakeHolder.sol"; 9 | import {StakeHolderBase} from "../../contracts/staking/StakeHolderBase.sol"; 10 | import {StakeHolderConfigBaseTest} from "./StakeHolderConfigBase.t.sol"; 11 | import {ERC1967Proxy} from "openzeppelin-contracts-4.9.3/proxy/ERC1967/ERC1967Proxy.sol"; 12 | 13 | contract StakeHolderNativeV2 is StakeHolderNative { 14 | function upgradeStorage(bytes memory /* _data */) external override(StakeHolderBase) { 15 | version = 1; 16 | } 17 | } 18 | 19 | 20 | contract StakeHolderConfigNativeTest is StakeHolderConfigBaseTest { 21 | 22 | function setUp() public override { 23 | super.setUp(); 24 | 25 | StakeHolderNative impl = new StakeHolderNative(); 26 | 27 | bytes memory initData = abi.encodeWithSelector( 28 | StakeHolderNative.initialize.selector, roleAdmin, upgradeAdmin, distributeAdmin 29 | ); 30 | 31 | ERC1967Proxy proxy = new ERC1967Proxy(address(impl), initData); 32 | stakeHolder = IStakeHolder(address(proxy)); 33 | } 34 | 35 | function _deployV1() internal override returns(IStakeHolder) { 36 | return IStakeHolder(address(new StakeHolderNative())); 37 | } 38 | 39 | function _deployV2() internal override returns(IStakeHolder) { 40 | return IStakeHolder(address(new StakeHolderNativeV2())); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /test/staking/StakeHolderConfigWIMX.t.sol: -------------------------------------------------------------------------------- 1 | // Copyright Immutable Pty Ltd 2018 - 2024 2 | // SPDX-License-Identifier: Apache 2.0 3 | pragma solidity >=0.8.19 <0.8.29; 4 | 5 | // solhint-disable-next-line no-global-import 6 | import "forge-std/Test.sol"; 7 | import {StakeHolderWIMX} from "../../contracts/staking/StakeHolderWIMX.sol"; 8 | import {WIMX} from "../../contracts/staking/WIMX.sol"; 9 | import {IStakeHolder} from "../../contracts/staking/IStakeHolder.sol"; 10 | import {StakeHolderBase} from "../../contracts/staking/StakeHolderBase.sol"; 11 | import {StakeHolderConfigBaseTest} from "./StakeHolderConfigBase.t.sol"; 12 | import {ERC1967Proxy} from "openzeppelin-contracts-4.9.3/proxy/ERC1967/ERC1967Proxy.sol"; 13 | 14 | contract StakeHolderWIMXV2 is StakeHolderWIMX { 15 | function upgradeStorage(bytes memory /* _data */) external override(StakeHolderBase) { 16 | version = 1; 17 | } 18 | } 19 | 20 | contract StakeHolderConfigWIMXTest is StakeHolderConfigBaseTest { 21 | 22 | function setUp() public override { 23 | super.setUp(); 24 | 25 | StakeHolderWIMX impl = new StakeHolderWIMX(); 26 | 27 | bytes memory initData = abi.encodeWithSelector( 28 | StakeHolderWIMX.initialize.selector, roleAdmin, upgradeAdmin, distributeAdmin, address(0) 29 | ); 30 | 31 | ERC1967Proxy proxy = new ERC1967Proxy(address(impl), initData); 32 | stakeHolder = IStakeHolder(address(proxy)); 33 | } 34 | 35 | function _deployV1() internal override returns(IStakeHolder) { 36 | return IStakeHolder(address(new StakeHolderWIMX())); 37 | } 38 | 39 | function _deployV2() internal override returns(IStakeHolder) { 40 | return IStakeHolder(address(new StakeHolderWIMXV2())); 41 | } 42 | } -------------------------------------------------------------------------------- /test/staking/StakeHolderInitBase.t.sol: -------------------------------------------------------------------------------- 1 | // Copyright Immutable Pty Ltd 2018 - 2024 2 | // SPDX-License-Identifier: Apache 2.0 3 | pragma solidity >=0.8.19 <0.8.29; 4 | 5 | // solhint-disable-next-line no-global-import 6 | import "forge-std/Test.sol"; 7 | import {StakeHolderBaseTest} from "./StakeHolderBase.t.sol"; 8 | 9 | abstract contract StakeHolderInitBaseTest is StakeHolderBaseTest { 10 | function testGetVersion() public { 11 | uint256 ver = stakeHolder.version(); 12 | assertEq(ver, 0, "Expect initial version of storage layout to be V0"); 13 | } 14 | 15 | function testStakersInit() public { 16 | assertEq(stakeHolder.getNumStakers(), 0, "Expect no stakers at deployment time"); 17 | } 18 | 19 | function testAdmins() public { 20 | assertEq(stakeHolder.getRoleMemberCount(defaultAdminRole), 1, "Expect one role admin"); 21 | assertEq(stakeHolder.getRoleMemberCount(upgradeRole), 1, "Expect one upgrade admin"); 22 | assertEq(stakeHolder.getRoleMemberCount(distributeRole), 1, "Expect one distribute admin"); 23 | assertTrue(stakeHolder.hasRole(defaultAdminRole, roleAdmin), "Expect roleAdmin is role admin"); 24 | assertTrue(stakeHolder.hasRole(upgradeRole, upgradeAdmin), "Expect upgradeAdmin is upgrade admin"); 25 | assertTrue(stakeHolder.hasRole(distributeRole, distributeAdmin), "Expect distributeAdmin is distribute admin"); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /test/staking/StakeHolderInitERC20.t.sol: -------------------------------------------------------------------------------- 1 | // Copyright Immutable Pty Ltd 2018 - 2024 2 | // SPDX-License-Identifier: Apache 2.0 3 | pragma solidity >=0.8.19 <0.8.29; 4 | 5 | // solhint-disable-next-line no-global-import 6 | import "forge-std/Test.sol"; 7 | import {StakeHolderERC20} from "../../contracts/staking/StakeHolderERC20.sol"; 8 | import {IStakeHolder} from "../../contracts/staking/IStakeHolder.sol"; 9 | import {StakeHolderBase} from "../../contracts/staking/StakeHolderBase.sol"; 10 | import {StakeHolderInitBaseTest} from "./StakeHolderInitBase.t.sol"; 11 | import {ERC1967Proxy} from "openzeppelin-contracts-4.9.3/proxy/ERC1967/ERC1967Proxy.sol"; 12 | 13 | contract StakeHolderInitERC20Test is StakeHolderInitBaseTest { 14 | 15 | function setUp() public override { 16 | super.setUp(); 17 | 18 | StakeHolderERC20 impl = new StakeHolderERC20(); 19 | 20 | bytes memory initData = abi.encodeWithSelector( 21 | StakeHolderERC20.initialize.selector, roleAdmin, upgradeAdmin, distributeAdmin, address(0) 22 | ); 23 | 24 | ERC1967Proxy proxy = new ERC1967Proxy(address(impl), initData); 25 | stakeHolder = IStakeHolder(address(proxy)); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /test/staking/StakeHolderInitNative.t.sol: -------------------------------------------------------------------------------- 1 | // Copyright Immutable Pty Ltd 2018 - 2024 2 | // SPDX-License-Identifier: Apache 2.0 3 | pragma solidity >=0.8.19 <0.8.29; 4 | 5 | // solhint-disable-next-line no-global-import 6 | import "forge-std/Test.sol"; 7 | import {StakeHolderNative} from "../../contracts/staking/StakeHolderNative.sol"; 8 | import {IStakeHolder} from "../../contracts/staking/IStakeHolder.sol"; 9 | import {StakeHolderBase} from "../../contracts/staking/StakeHolderBase.sol"; 10 | import {StakeHolderInitBaseTest} from "./StakeHolderInitBase.t.sol"; 11 | import {ERC1967Proxy} from "openzeppelin-contracts-4.9.3/proxy/ERC1967/ERC1967Proxy.sol"; 12 | 13 | contract StakeHolderInitNativeTest is StakeHolderInitBaseTest { 14 | 15 | function setUp() public override { 16 | super.setUp(); 17 | 18 | StakeHolderNative impl = new StakeHolderNative(); 19 | 20 | bytes memory initData = abi.encodeWithSelector( 21 | StakeHolderNative.initialize.selector, roleAdmin, upgradeAdmin, distributeAdmin 22 | ); 23 | 24 | ERC1967Proxy proxy = new ERC1967Proxy(address(impl), initData); 25 | stakeHolder = IStakeHolder(address(proxy)); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /test/staking/StakeHolderInitWIMX.t.sol: -------------------------------------------------------------------------------- 1 | // Copyright Immutable Pty Ltd 2018 - 2024 2 | // SPDX-License-Identifier: Apache 2.0 3 | pragma solidity >=0.8.19 <0.8.29; 4 | 5 | // solhint-disable-next-line no-global-import 6 | import "forge-std/Test.sol"; 7 | import {StakeHolderWIMX} from "../../contracts/staking/StakeHolderWIMX.sol"; 8 | import {WIMX} from "../../contracts/staking/WIMX.sol"; 9 | import {IStakeHolder} from "../../contracts/staking/IStakeHolder.sol"; 10 | import {StakeHolderBase} from "../../contracts/staking/StakeHolderBase.sol"; 11 | import {StakeHolderInitBaseTest} from "./StakeHolderInitBase.t.sol"; 12 | import {ERC1967Proxy} from "openzeppelin-contracts-4.9.3/proxy/ERC1967/ERC1967Proxy.sol"; 13 | 14 | contract StakeHolderInitWIMXTest is StakeHolderInitBaseTest { 15 | 16 | function setUp() public override { 17 | super.setUp(); 18 | 19 | StakeHolderWIMX impl = new StakeHolderWIMX(); 20 | 21 | bytes memory initData = abi.encodeWithSelector( 22 | StakeHolderWIMX.initialize.selector, roleAdmin, upgradeAdmin, distributeAdmin, address(0) 23 | ); 24 | 25 | ERC1967Proxy proxy = new ERC1967Proxy(address(impl), initData); 26 | stakeHolder = IStakeHolder(address(proxy)); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /test/staking/StakeHolderOperationalERC20.t.sol: -------------------------------------------------------------------------------- 1 | // Copyright Immutable Pty Ltd 2018 - 2024 2 | // SPDX-License-Identifier: Apache 2.0 3 | pragma solidity >=0.8.19 <0.8.29; 4 | 5 | // solhint-disable-next-line no-global-import 6 | import "forge-std/Test.sol"; 7 | import {StakeHolderERC20} from "../../contracts/staking/StakeHolderERC20.sol"; 8 | import {IStakeHolder} from "../../contracts/staking/IStakeHolder.sol"; 9 | import {StakeHolderOperationalBaseTest} from "./StakeHolderOperationalBase.t.sol"; 10 | import {ERC1967Proxy} from "openzeppelin-contracts-4.9.3/proxy/ERC1967/ERC1967Proxy.sol"; 11 | import {ERC20PresetFixedSupply} from "openzeppelin-contracts-4.9.3/token/ERC20/presets/ERC20PresetFixedSupply.sol"; 12 | 13 | contract StakeHolderOperationalERC20Test is StakeHolderOperationalBaseTest { 14 | ERC20PresetFixedSupply erc20; 15 | 16 | 17 | function setUp() public override { 18 | super.setUp(); 19 | 20 | erc20 = new ERC20PresetFixedSupply("Name", "SYM", 1000 ether, bank); 21 | 22 | StakeHolderERC20 impl = new StakeHolderERC20(); 23 | 24 | bytes memory initData = abi.encodeWithSelector( 25 | StakeHolderERC20.initialize.selector, roleAdmin, upgradeAdmin, distributeAdmin, address(erc20) 26 | ); 27 | 28 | ERC1967Proxy proxy = new ERC1967Proxy(address(impl), initData); 29 | stakeHolder = IStakeHolder(address(proxy)); 30 | } 31 | 32 | 33 | function testStakeWithValue() public { 34 | uint256 amount = 100 ether; 35 | vm.deal(staker1, amount); 36 | _deal(staker1, amount); 37 | 38 | vm.prank(staker1); 39 | erc20.approve(address(stakeHolder), amount); 40 | vm.expectRevert(abi.encodeWithSelector(IStakeHolder.NonPayable.selector)); 41 | vm.prank(staker1); 42 | stakeHolder.stake{value: amount}(amount); 43 | } 44 | 45 | function _deal(address _to, uint256 _amount) internal override { 46 | vm.prank(bank); 47 | erc20.transfer(_to, _amount); 48 | } 49 | 50 | function _addStake(address _staker, uint256 _amount, bool _hasError, bytes memory _error) internal override { 51 | vm.prank(_staker); 52 | erc20.approve(address(stakeHolder), _amount); 53 | if (_hasError) { 54 | vm.expectRevert(_error); 55 | } 56 | vm.prank(_staker); 57 | stakeHolder.stake(_amount); 58 | } 59 | function _distributeRewards(address _distributor, uint256 _total, IStakeHolder.AccountAmount[] memory _accountAmounts, 60 | bool _hasError, bytes memory _error) internal override { 61 | vm.prank(_distributor); 62 | erc20.approve(address(stakeHolder), _total); 63 | if (_hasError) { 64 | vm.expectRevert(_error); 65 | } 66 | vm.prank(_distributor); 67 | stakeHolder.distributeRewards(_accountAmounts); 68 | } 69 | function _getBalanceStaker(address _staker) internal view override returns (uint256) { 70 | return erc20.balanceOf(_staker); 71 | } 72 | function _getBalanceStakeHolderContract() internal view override returns (uint256) { 73 | return erc20.balanceOf(address(stakeHolder)); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /test/staking/StakeHolderTimeDelayERC20.t.sol: -------------------------------------------------------------------------------- 1 | // Copyright Immutable Pty Ltd 2018 - 2024 2 | // SPDX-License-Identifier: Apache 2.0 3 | pragma solidity >=0.8.19 <0.8.29; 4 | 5 | // solhint-disable-next-line no-global-import 6 | import "forge-std/Test.sol"; 7 | import {StakeHolderERC20} from "../../contracts/staking/StakeHolderERC20.sol"; 8 | import {IStakeHolder} from "../../contracts/staking/IStakeHolder.sol"; 9 | import {StakeHolderTimeDelayBaseTest} from "./StakeHolderTimeDelayBase.t.sol"; 10 | import {ERC1967Proxy} from "openzeppelin-contracts-4.9.3/proxy/ERC1967/ERC1967Proxy.sol"; 11 | import {ERC20PresetFixedSupply} from "openzeppelin-contracts-4.9.3/token/ERC20/presets/ERC20PresetFixedSupply.sol"; 12 | import {StakeHolderBase} from "../../contracts/staking/StakeHolderBase.sol"; 13 | 14 | contract StakeHolderERC20V2 is StakeHolderERC20 { 15 | function upgradeStorage(bytes memory /* _data */) external override(StakeHolderBase) { 16 | version = 1; 17 | } 18 | } 19 | 20 | 21 | contract StakeHolderTimeDelayERC20Test is StakeHolderTimeDelayBaseTest { 22 | ERC20PresetFixedSupply erc20; 23 | 24 | function setUp() public override { 25 | super.setUp(); 26 | 27 | erc20 = new ERC20PresetFixedSupply("Name", "SYM", 1000 ether, bank); 28 | 29 | StakeHolderERC20 impl = new StakeHolderERC20(); 30 | 31 | bytes memory initData = abi.encodeWithSelector( 32 | StakeHolderERC20.initialize.selector, address(stakeHolderTimeDelay), address(stakeHolderTimeDelay), 33 | distributeAdmin, address(erc20) 34 | ); 35 | 36 | ERC1967Proxy proxy = new ERC1967Proxy(address(impl), initData); 37 | stakeHolder = IStakeHolder(address(proxy)); 38 | } 39 | 40 | function _deployV2() internal override returns(IStakeHolder) { 41 | return IStakeHolder(address(new StakeHolderERC20V2())); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /test/staking/StakeHolderTimeDelayWIMX.t.sol: -------------------------------------------------------------------------------- 1 | // Copyright Immutable Pty Ltd 2018 - 2024 2 | // SPDX-License-Identifier: Apache 2.0 3 | pragma solidity >=0.8.19 <0.8.29; 4 | 5 | // solhint-disable-next-line no-global-import 6 | import "forge-std/Test.sol"; 7 | import {StakeHolderWIMX} from "../../contracts/staking/StakeHolderWIMX.sol"; 8 | import {IStakeHolder} from "../../contracts/staking/IStakeHolder.sol"; 9 | import {StakeHolderTimeDelayBaseTest} from "./StakeHolderTimeDelayBase.t.sol"; 10 | import {ERC1967Proxy} from "openzeppelin-contracts-4.9.3/proxy/ERC1967/ERC1967Proxy.sol"; 11 | import {WIMX} from "../../contracts/staking/WIMX.sol"; 12 | import {StakeHolderBase} from "../../contracts/staking/StakeHolderBase.sol"; 13 | 14 | contract StakeHolderWIMXV2 is StakeHolderWIMX { 15 | function upgradeStorage(bytes memory /* _data */) external override(StakeHolderBase) { 16 | version = 1; 17 | } 18 | } 19 | 20 | 21 | contract StakeHolderTimeDelayERC20Test is StakeHolderTimeDelayBaseTest { 22 | WIMX erc20; 23 | 24 | function setUp() public override { 25 | super.setUp(); 26 | 27 | erc20 = new WIMX(); 28 | 29 | StakeHolderWIMX impl = new StakeHolderWIMX(); 30 | 31 | bytes memory initData = abi.encodeWithSelector( 32 | StakeHolderWIMX.initialize.selector, address(stakeHolderTimeDelay), address(stakeHolderTimeDelay), 33 | distributeAdmin, address(erc20) 34 | ); 35 | 36 | ERC1967Proxy proxy = new ERC1967Proxy(address(impl), initData); 37 | stakeHolder = IStakeHolder(address(proxy)); 38 | } 39 | 40 | function _deployV2() internal override returns(IStakeHolder) { 41 | return IStakeHolder(address(new StakeHolderWIMXV2())); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /test/token/erc20/preset/ImmutableERC20FixedSupplyNoBurn.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.19 <0.8.29; 3 | 4 | import "forge-std/Test.sol"; 5 | 6 | import {ImmutableERC20FixedSupplyNoBurn} from "contracts/token/erc20/preset/ImmutableERC20FixedSupplyNoBurn.sol"; 7 | import {IImmutableERC20Errors} from "contracts/token/erc20/preset/Errors.sol"; 8 | 9 | contract ImmutableERC20FixedSupplyNoBurnTest is Test { 10 | ImmutableERC20FixedSupplyNoBurn public erc20; 11 | 12 | address public treasurer; 13 | address public hubOwner; 14 | string name; 15 | string symbol; 16 | uint256 supply; 17 | 18 | function setUp() public virtual { 19 | hubOwner = makeAddr("hubOwner"); 20 | treasurer = makeAddr("treasurer"); 21 | name = "HappyToken"; 22 | symbol = "HPY"; 23 | supply = 1000000; 24 | 25 | erc20 = new ImmutableERC20FixedSupplyNoBurn(name, symbol, supply, treasurer, hubOwner); 26 | } 27 | 28 | function testInit() public { 29 | assertEq(erc20.name(), name, "name"); 30 | assertEq(erc20.symbol(), symbol, "symbol"); 31 | assertEq(erc20.totalSupply(), supply, "supply"); 32 | assertEq(erc20.balanceOf(treasurer), supply, "initial treasurer balance"); 33 | assertEq(erc20.balanceOf(hubOwner), 0, "initial hub owner balance"); 34 | assertEq(erc20.owner(), hubOwner, "Hub owner"); 35 | } 36 | 37 | function testChangeOwner() public { 38 | address newOwner = makeAddr("newOwner"); 39 | vm.prank(hubOwner); 40 | erc20.transferOwnership(newOwner); 41 | assertEq(erc20.owner(), newOwner, "new owner"); 42 | } 43 | 44 | function testRenounceOwnerBlocked() public { 45 | vm.prank(hubOwner); 46 | vm.expectRevert(abi.encodeWithSelector(IImmutableERC20Errors.RenounceOwnershipNotAllowed.selector)); 47 | erc20.renounceOwnership(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /test/token/erc20/preset/README.md: -------------------------------------------------------------------------------- 1 | # Test Plan for Immutable ERC20 Preset contracts 2 | 3 | ## ImmutableERC20FixedSupplyNoBurn.sol 4 | This section defines tests for contracts/erc20/preset/ImmutableERC20FixedSupplyNoBurn.sol. Note 5 | that this contract extends Open Zeppelin's ERC 20 contract which is extensively tested here: 6 | https://github.com/OpenZeppelin/openzeppelin-contracts/tree/release-v4.9/test/token/ERC20 . 7 | 8 | All of the tests defined in the table below are in test/erc20/preset/ImmutableERC20FixedSupplyNoBurn.t.sol. 9 | 10 | | Test name |Description | Happy Case | Implemented | 11 | |---------------------------------| --------------------------------------------------|------------|-------------| 12 | | testInit | Check constructor. | Yes | Yes | 13 | | testChangeOwner | Check change ownership. | Yes | Yes | 14 | | testRenounceOwnershipBlocked | Ensure renounceOwnership reverts. | No | Yes | 15 | 16 | 17 | ## ImmutableERC20MinterBurnerPermit.sol 18 | This section defines tests for contracts/erc20/preset/ImmutableERC20MinterBurnerPermit.sol. Note 19 | that this contract extends Open Zeppelin's ERC 20 contract which is extensively tested here: 20 | https://github.com/OpenZeppelin/openzeppelin-contracts/tree/release-v4.9/test/token/ERC20 . 21 | 22 | All of the tests defined in the table below are in test/erc20/preset/ImmutableERC20MinterBurnerPermit.t.sol. 23 | 24 | | Test name |Description | Happy Case | Implemented | 25 | |---------------------------------| --------------------------------------------------|------------|-------------| 26 | | testInit | Check constructor. | Yes | Yes | 27 | | testChangeOwner | Check change ownership. | Yes | Yes | 28 | | testRenounceOwnershipBlocked | Ensure renounceOwnership reverts. | No | Yes | 29 | | testOnlyMinterCanMunt | Ensure Only minter role can mint reverts. | No | Yes | 30 | | testMint | Ensure successful minting by minter | No | Yes | 31 | | testCanOnlyMintUpToMaxSupply | Ensure can only mint up to max supply | No | Yes | 32 | | testRenounceLastHubOwnerBlocked | Ensure the last hub owner cannot be renounced | No | Yes | 33 | | testRenounceLastAdminBlocked | Ensure the last default admin cannot be renounced | No | Yes | 34 | | testRenounceAdmin | Ensure admin role can be renounced | No | Yes | 35 | | testRenounceHubOwner | Ensure hub owner role can be renounced | No | Yes | 36 | | testBurnFrom | Ensure allowance is required to burnFrom | Yes | Yes | 37 | | testPermit | Ensure Permit works | Yes | Yes | 38 | -------------------------------------------------------------------------------- /test/token/erc721/ERC721ByQuantityBase.t.sol: -------------------------------------------------------------------------------- 1 | // Copyright Immutable Pty Ltd 2018 - 2025 2 | // SPDX-License-Identifier: Apache 2.0 3 | pragma solidity >=0.8.19 <0.8.29; 4 | 5 | import {ERC721BaseTest} from "./ERC721Base.t.sol"; 6 | import {IImmutableERC721ByQuantity} from "../../../contracts/token/erc721/interfaces/IImmutableERC721ByQuantity.sol"; 7 | 8 | 9 | /** 10 | * Base contract for all ERC 721 by quantity tests. 11 | */ 12 | abstract contract ERC721ByQuantityBaseTest is ERC721BaseTest { 13 | IImmutableERC721ByQuantity public erc721BQ; 14 | 15 | function setUp() public virtual override { 16 | super.setUp(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /test/token/erc721/ERC721ConfigByIdV1.t.sol: -------------------------------------------------------------------------------- 1 | // Copyright Immutable Pty Ltd 2018 - 2025 2 | // SPDX-License-Identifier: Apache 2.0 3 | pragma solidity >=0.8.19 <0.8.29; 4 | 5 | import {ERC721ConfigBaseTest} from "./ERC721ConfigBase.t.sol"; 6 | import {ImmutableERC721MintByID} from "../../../contracts/token/erc721/preset/ImmutableERC721MintByID.sol"; 7 | import {IImmutableERC721} from "../../../contracts/token/erc721/interfaces/IImmutableERC721.sol"; 8 | 9 | contract ERC721ConfigV1ByIdTest is ERC721ConfigBaseTest { 10 | 11 | function setUp() public virtual override { 12 | super.setUp(); 13 | 14 | ImmutableERC721MintByID immutableERC721 = new ImmutableERC721MintByID( 15 | owner, name, symbol, baseURI, contractURI, address(allowlist), feeReceiver, feeNumerator 16 | ); 17 | 18 | // ImmutableERC721 does not implement the interface, and hence must be cast to the 19 | // interface type. 20 | erc721 = IImmutableERC721(address(immutableERC721)); 21 | 22 | vm.prank(owner); 23 | erc721.grantMinterRole(minter); 24 | } 25 | 26 | function notOwnedRevertError(uint256 /* _tokenIdToBeBurned */) public pure override returns (bytes memory) { 27 | return "ERC721: caller is not token owner or approved"; 28 | } 29 | } -------------------------------------------------------------------------------- /test/token/erc721/ERC721ConfigByQuantityBase.t.sol: -------------------------------------------------------------------------------- 1 | // Copyright Immutable Pty Ltd 2018 - 2025 2 | // SPDX-License-Identifier: Apache 2.0 3 | pragma solidity >=0.8.19 <0.8.29; 4 | 5 | import {ERC721ConfigBaseTest} from "./ERC721ConfigBase.t.sol"; 6 | import {IImmutableERC721ByQuantity} from "../../../contracts/token/erc721/interfaces/IImmutableERC721ByQuantity.sol"; 7 | import {IImmutableERC721, IImmutableERC721Errors} from "../../../contracts/token/erc721/interfaces/IImmutableERC721.sol"; 8 | 9 | abstract contract ERC721ConfigByQuantityBaseTest is ERC721ConfigBaseTest { 10 | IImmutableERC721ByQuantity public erc721BQ; 11 | 12 | function notOwnedRevertError(uint256 _tokenIdToBeBurned) public pure override returns (bytes memory) { 13 | return abi.encodeWithSelector(IImmutableERC721Errors.IImmutableERC721NotOwnerOrOperator.selector, _tokenIdToBeBurned); 14 | } 15 | 16 | function testByQuantityContractDeployment() public { 17 | uint256 tokenId = getFirst(); 18 | vm.expectRevert("ERC721Psi: owner query for nonexistent token"); 19 | erc721.ownerOf(tokenId); 20 | } 21 | 22 | 23 | // Note that Open Zeppelin ERC721 contract handles the tokenURI request 24 | function testByQuantityTokenURIWithBaseURISet() public { 25 | uint256 qty = 1; 26 | uint256 tokenId = getFirst(); 27 | vm.prank(minter); 28 | erc721BQ.mintByQuantity(user1, qty); 29 | assertEq( 30 | erc721.tokenURI(tokenId), 31 | string(abi.encodePacked(baseURI, vm.toString(tokenId))) 32 | ); 33 | } 34 | 35 | // Note that Open Zeppelin ERC721 contract handles the tokenURI request 36 | function testByQuantityTokenURIRevertBurnt() public { 37 | uint256 qty = 1; 38 | uint256 tokenId = getFirst(); 39 | vm.prank(minter); 40 | erc721BQ.mintByQuantity(user1, qty); 41 | 42 | vm.prank(user1); 43 | erc721.burn(tokenId); 44 | 45 | vm.expectRevert("ERC721: invalid token ID"); 46 | erc721.tokenURI(tokenId); 47 | } 48 | 49 | 50 | function getFirst() internal view virtual returns (uint256) { 51 | return erc721BQ.mintBatchByQuantityThreshold(); 52 | } 53 | 54 | } -------------------------------------------------------------------------------- /test/token/erc721/ERC721ConfigByQuantityV1.t.sol: -------------------------------------------------------------------------------- 1 | // Copyright Immutable Pty Ltd 2018 - 2025 2 | // SPDX-License-Identifier: Apache 2.0 3 | pragma solidity >=0.8.19 <0.8.29; 4 | 5 | import {ERC721ConfigByQuantityBaseTest} from "./ERC721ConfigByQuantityBase.t.sol"; 6 | import {ImmutableERC721} from "../../../contracts/token/erc721/preset/ImmutableERC721.sol"; 7 | import {IImmutableERC721ByQuantity} from "../../../contracts/token/erc721/interfaces/IImmutableERC721ByQuantity.sol"; 8 | import {IImmutableERC721, IImmutableERC721Errors} from "../../../contracts/token/erc721/interfaces/IImmutableERC721.sol"; 9 | 10 | contract ERC721ConfigByQuantityV1Test is ERC721ConfigByQuantityBaseTest { 11 | 12 | function setUp() public virtual override { 13 | super.setUp(); 14 | 15 | ImmutableERC721 immutableERC721 = new ImmutableERC721( 16 | owner, name, symbol, baseURI, contractURI, address(allowlist), feeReceiver, feeNumerator 17 | ); 18 | 19 | // ImmutableERC721 does not implement the interface, and hence must be cast to the 20 | // interface type. 21 | erc721 = IImmutableERC721(address(immutableERC721)); 22 | erc721BQ = IImmutableERC721ByQuantity(address(immutableERC721)); 23 | 24 | vm.prank(owner); 25 | erc721.grantMinterRole(minter); 26 | } 27 | 28 | } -------------------------------------------------------------------------------- /test/token/erc721/ERC721ConfigByQuantityV2.t.sol: -------------------------------------------------------------------------------- 1 | // Copyright Immutable Pty Ltd 2018 - 2025 2 | // SPDX-License-Identifier: Apache 2.0 3 | pragma solidity >=0.8.19 <0.8.29; 4 | 5 | import {ERC721ConfigByQuantityBaseTest} from "./ERC721ConfigByQuantityBase.t.sol"; 6 | import {ImmutableERC721V2} from "../../../contracts/token/erc721/preset/ImmutableERC721V2.sol"; 7 | import {IImmutableERC721ByQuantity} from "../../../contracts/token/erc721/interfaces/IImmutableERC721ByQuantity.sol"; 8 | import {IImmutableERC721, IImmutableERC721Errors} from "../../../contracts/token/erc721/interfaces/IImmutableERC721.sol"; 9 | 10 | contract ERC721ConfigByQuantityV2Test is ERC721ConfigByQuantityBaseTest { 11 | 12 | function setUp() public virtual override { 13 | super.setUp(); 14 | 15 | ImmutableERC721V2 immutableERC721 = new ImmutableERC721V2( 16 | owner, name, symbol, baseURI, contractURI, address(allowlist), feeReceiver, feeNumerator 17 | ); 18 | 19 | // ImmutableERC721 does not implement the interface, and hence must be cast to the 20 | // interface type. 21 | erc721 = IImmutableERC721(address(immutableERC721)); 22 | erc721BQ = IImmutableERC721ByQuantity(address(immutableERC721)); 23 | 24 | vm.prank(owner); 25 | erc721.grantMinterRole(minter); 26 | } 27 | 28 | function getFirst() internal override view returns (uint256) { 29 | uint256 nominalFirst = erc721BQ.mintBatchByQuantityThreshold(); 30 | return ((nominalFirst / 256) + 1) * 256; 31 | } 32 | } -------------------------------------------------------------------------------- /test/token/erc721/ERC721OperationalByIdV1.t.sol: -------------------------------------------------------------------------------- 1 | // Copyright Immutable Pty Ltd 2018 - 2025 2 | // SPDX-License-Identifier: Apache 2.0 3 | pragma solidity >=0.8.19 <0.8.29; 4 | 5 | import {ERC721OperationalBaseTest} from "./ERC721OperationalBase.t.sol"; 6 | import {ImmutableERC721MintByID} from "../../../contracts/token/erc721/preset/ImmutableERC721MintByID.sol"; 7 | import {IImmutableERC721, IImmutableERC721Errors} from "../../../contracts/token/erc721/interfaces/IImmutableERC721.sol"; 8 | 9 | 10 | // Test the original ImmutableERC721 contract: Operational tests 11 | contract ERC721OperationalV1ByIdTest is ERC721OperationalBaseTest { 12 | 13 | function setUp() public virtual override { 14 | super.setUp(); 15 | 16 | ImmutableERC721MintByID immutableERC721 = new ImmutableERC721MintByID( 17 | owner, name, symbol, baseURI, contractURI, address(allowlist), feeReceiver, feeNumerator 18 | ); 19 | 20 | // ImmutableERC721 does not implement the interface, and hence must be cast to the 21 | // interface type. 22 | erc721 = IImmutableERC721(address(immutableERC721)); 23 | 24 | vm.prank(owner); 25 | erc721.grantMinterRole(minter); 26 | } 27 | 28 | function notOwnedRevertError(uint256 /* _tokenIdToBeBurned */) public pure override returns (bytes memory) { 29 | return "ERC721: caller is not token owner or approved"; 30 | } 31 | } -------------------------------------------------------------------------------- /test/token/erc721/ERC721OperationalByQuantityV1.t.sol: -------------------------------------------------------------------------------- 1 | // Copyright Immutable Pty Ltd 2018 - 2025 2 | // SPDX-License-Identifier: Apache 2.0 3 | pragma solidity >=0.8.19 <0.8.29; 4 | 5 | import {ERC721OperationalByQuantityBaseTest} from "./ERC721OperationalByQuantityBase.t.sol"; 6 | import {ImmutableERC721} from "../../../contracts/token/erc721/preset/ImmutableERC721.sol"; 7 | import {IImmutableERC721ByQuantity} from "../../../contracts/token/erc721/interfaces/IImmutableERC721ByQuantity.sol"; 8 | import {IImmutableERC721, IImmutableERC721Errors} from "../../../contracts/token/erc721/interfaces/IImmutableERC721.sol"; 9 | 10 | 11 | // Test the original ImmutableERC721 contract: Operational tests 12 | contract ERC721OperationalByQuantityV1Test is ERC721OperationalByQuantityBaseTest { 13 | 14 | function setUp() public virtual override { 15 | super.setUp(); 16 | 17 | ImmutableERC721 immutableERC721 = new ImmutableERC721( 18 | owner, name, symbol, baseURI, contractURI, address(allowlist), feeReceiver, feeNumerator 19 | ); 20 | 21 | // ImmutableERC721 does not implement the interface, and hence must be cast to the 22 | // interface type. 23 | erc721BQ = IImmutableERC721ByQuantity(address(immutableERC721)); 24 | erc721 = IImmutableERC721(address(immutableERC721)); 25 | 26 | vm.prank(owner); 27 | erc721.grantMinterRole(minter); 28 | } 29 | 30 | function notOwnedRevertError(uint256 _tokenIdToBeBurned) public pure override returns (bytes memory) { 31 | return abi.encodeWithSelector(IImmutableERC721Errors.IImmutableERC721NotOwnerOrOperator.selector, _tokenIdToBeBurned); 32 | } 33 | } -------------------------------------------------------------------------------- /test/token/erc721/ERC721OperationalByQuantityV2.t.sol: -------------------------------------------------------------------------------- 1 | // Copyright Immutable Pty Ltd 2018 - 2025 2 | // SPDX-License-Identifier: Apache 2.0 3 | pragma solidity >=0.8.19 <0.8.29; 4 | 5 | import {ERC721OperationalByQuantityBaseTest} from "./ERC721OperationalByQuantityBase.t.sol"; 6 | import {ImmutableERC721V2} from "../../../contracts/token/erc721/preset/ImmutableERC721V2.sol"; 7 | import {IImmutableERC721ByQuantity} from "../../../contracts/token/erc721/interfaces/IImmutableERC721ByQuantity.sol"; 8 | import {IImmutableERC721, IImmutableERC721Errors} from "../../../contracts/token/erc721/interfaces/IImmutableERC721.sol"; 9 | 10 | 11 | // Test the original ImmutableERC721 contract: Operational tests 12 | contract ERC721OperationalByQuantityV2Test is ERC721OperationalByQuantityBaseTest { 13 | ImmutableERC721V2 erc721BQv2; 14 | 15 | function setUp() public virtual override { 16 | super.setUp(); 17 | 18 | erc721BQv2 = new ImmutableERC721V2( 19 | owner, name, symbol, baseURI, contractURI, address(allowlist), feeReceiver, feeNumerator 20 | ); 21 | 22 | // ImmutableERC721 does not implement the interface, and hence must be cast to the 23 | // interface type. 24 | erc721BQ = IImmutableERC721ByQuantity(address(erc721BQv2)); 25 | erc721 = IImmutableERC721(address(erc721BQv2)); 26 | 27 | vm.prank(owner); 28 | erc721.grantMinterRole(minter); 29 | } 30 | 31 | 32 | function testMintBatchByQuantityNextTokenId() public { 33 | uint256 nextId = erc721BQv2.mintBatchByQuantityNextTokenId(); 34 | require(nextId == getFirst(), "First"); 35 | 36 | vm.prank(minter); 37 | erc721BQ.mintByQuantity(user1, 1); 38 | uint256 newNextId = erc721BQv2.mintBatchByQuantityNextTokenId(); 39 | require(newNextId == nextId + 256, "After first mint"); 40 | nextId = newNextId; 41 | 42 | vm.prank(minter); 43 | erc721BQ.mintByQuantity(user1, 256); 44 | newNextId = erc721BQv2.mintBatchByQuantityNextTokenId(); 45 | require(newNextId == nextId + 256, "After second mint"); 46 | nextId = newNextId; 47 | 48 | vm.prank(minter); 49 | erc721BQ.mintByQuantity(user1, 257); 50 | newNextId = erc721BQv2.mintBatchByQuantityNextTokenId(); 51 | require(newNextId == nextId + 512, "After third mint"); 52 | } 53 | 54 | function notOwnedRevertError(uint256 _tokenIdToBeBurned) public pure override returns (bytes memory) { 55 | return abi.encodeWithSelector(IImmutableERC721Errors.IImmutableERC721NotOwnerOrOperator.selector, _tokenIdToBeBurned); 56 | } 57 | 58 | function getFirst() internal override view returns (uint256) { 59 | uint256 nominalFirst = erc721BQ.mintBatchByQuantityThreshold(); 60 | return ((nominalFirst / 256) + 1) * 256; 61 | } 62 | 63 | } -------------------------------------------------------------------------------- /test/token/erc721/fuzz/README.md: -------------------------------------------------------------------------------- 1 | # ERC721PsiV2 Fuzzing Suite 2 | 3 | ## Overview 4 | 5 | This fuzzing suite provides comprehensive invariant testing for the ERC721PsiV2 and ERC721PsiBurnableV2 contracts. The suite focuses on testing core functionality, edge cases, and specific features of the PSI implementation, including the unique minting mechanism, burning capabilities, and ownership management. 6 | 7 | ## Contents 8 | 9 | The fuzzing suite tests the following core components: 10 | - Token minting (both individual and batch) 11 | - Token burning 12 | - Ownership tracking 13 | - Balance management 14 | - Token ID sequencing 15 | - Approval system 16 | 17 | All properties tested can be found in `Properties.md`. 18 | 19 | ## Setup 20 | 21 | 1. Installing Echidna: [https://github.com/crytic/echidna](https://github.com/crytic/echidna) 22 | 23 | ## Running the Tests 24 | 25 | ### Echidna Fuzzing 26 | ```bash 27 | echidna test/token/erc721/fuzz/ERC721PsiV2.Echidna.sol \ 28 | --contract ERC721PsiV2Echidna \ 29 | --config test/token/erc721/fuzz/echidna.config.yaml 30 | ``` 31 | 32 | ## Test Configuration 33 | 34 | Echidna Configuration: [./echidna.config.yaml](echidna.config.yaml) 35 | 36 | Foundry Configuration: [../../../../foundry.toml](../../../../foundry.toml) 37 | 38 | ## Scope 39 | 40 | The following contracts are covered in this fuzzing suite: 41 | 42 | ``` 43 | contracts/token/erc721/erc721psi/ERC721PsiV2.sol 44 | contracts/token/erc721/erc721psi/ERC721PsiBurnableV2.sol 45 | ``` 46 | 47 | Key features tested: 48 | 1. PSI-specific minting mechanism (2^128 threshold) 49 | 2. Batch minting functionality 50 | 3. Burning capabilities 51 | 4. Ownership and balance tracking 52 | 5. Token ID sequencing 53 | 6. Approval system 54 | 55 | ## Test Reports 56 | 57 | - Echidna test results: `echidna-report.txt` 58 | - Coverage information: `coverage.txt` 59 | - Corpus directory: `corpus/` 60 | - Foundry test results in console output 61 | 62 | ## Notes 63 | 64 | - The fuzzing suite includes both positive and negative test cases 65 | - Edge cases and boundary conditions are specifically targeted 66 | - Gas optimization checks are included 67 | - All tests are designed to be deterministic and reproducible 68 | -------------------------------------------------------------------------------- /test/token/erc721/fuzz/echidna.config.yaml: -------------------------------------------------------------------------------- 1 | corpusDir: "echidna-corpus" 2 | testMode: assertion 3 | testLimit: 100000 4 | deployer: "0x10000" 5 | sender: ["0x10000", "0x20000", "0x30000"] -------------------------------------------------------------------------------- /test/token/x/Asset.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { ethers } from "hardhat"; 3 | 4 | describe("Asset", function () { 5 | it("Should be able to mint successfully with a valid blueprint", async function () { 6 | const [owner] = await ethers.getSigners(); 7 | 8 | const Asset = await ethers.getContractFactory("Asset"); 9 | 10 | const o = owner.address; 11 | const name = "Gods Unchained"; 12 | const symbol = "GU"; 13 | const imx = owner.address; 14 | const mintable = await Asset.deploy(o, name, symbol, imx); 15 | 16 | const tokenID = "123"; 17 | const blueprint = "1000"; 18 | const blob = toHex(`{${tokenID}}:{${blueprint}}`); 19 | 20 | await mintable.mintFor(owner.address, 1, blob); 21 | 22 | const oo = await mintable.ownerOf(tokenID); 23 | 24 | expect(oo).to.equal(owner.address); 25 | 26 | const bp = await mintable.blueprints(tokenID); 27 | 28 | expect(fromHex(bp)).to.equal(blueprint); 29 | }); 30 | 31 | it("Should be able to mint successfully with an empty blueprint", async function () { 32 | const [owner] = await ethers.getSigners(); 33 | 34 | const Asset = await ethers.getContractFactory("Asset"); 35 | 36 | const o = owner.address; 37 | const name = "Gods Unchained"; 38 | const symbol = "GU"; 39 | const imx = owner.address; 40 | const mintable = await Asset.deploy(o, name, symbol, imx); 41 | 42 | const tokenID = "123"; 43 | const blueprint = ""; 44 | const blob = toHex(`{${tokenID}}:{${blueprint}}`); 45 | 46 | await mintable.mintFor(owner.address, 1, blob); 47 | 48 | const bp = await mintable.blueprints(tokenID); 49 | 50 | expect(fromHex(bp)).to.equal(blueprint); 51 | }); 52 | 53 | it("Should not be able to mint successfully with an invalid blueprint", async function () { 54 | const [owner] = await ethers.getSigners(); 55 | 56 | const Asset = await ethers.getContractFactory("Asset"); 57 | 58 | const o = owner.address; 59 | const name = "Gods Unchained"; 60 | const symbol = "GU"; 61 | const imx = owner.address; 62 | const mintable = await Asset.deploy(o, name, symbol, imx); 63 | 64 | const blob = toHex(`:`); 65 | await expect(mintable.mintFor(owner.address, 1, blob)).to.be.reverted; 66 | }); 67 | }); 68 | 69 | function toHex(str: string) { 70 | let result = ""; 71 | for (let i = 0; i < str.length; i++) { 72 | result += str.charCodeAt(i).toString(16); 73 | } 74 | return "0x" + result; 75 | } 76 | 77 | function fromHex(str1: string) { 78 | const hex = str1.toString().substr(2); 79 | let str = ""; 80 | for (let n = 0; n < hex.length; n += 2) { 81 | str += String.fromCharCode(parseInt(hex.substr(n, 2), 16)); 82 | } 83 | return str; 84 | } 85 | -------------------------------------------------------------------------------- /test/trading/seaport/ImmutableSeaportHarness.t.sol: -------------------------------------------------------------------------------- 1 | // Copyright (c) Immutable Pty Ltd 2018 - 2024 2 | // SPDX-License-Identifier: Apache-2 3 | 4 | // solhint-disable-next-line compiler-version 5 | pragma solidity ^0.8.17; 6 | 7 | import {ImmutableSeaport} from "../../../contracts/trading/seaport/ImmutableSeaport.sol"; 8 | 9 | // solhint-disable func-name-mixedcase 10 | 11 | contract ImmutableSeaportHarness is ImmutableSeaport { 12 | constructor(address conduitController, address owner) ImmutableSeaport(conduitController, owner) {} 13 | 14 | function exposed_domainSeparator() external view returns (bytes32) { 15 | return _domainSeparator(); 16 | } 17 | 18 | function exposed_deriveEIP712Digest(bytes32 domainSeparator, bytes32 orderHash) 19 | external 20 | pure 21 | returns (bytes32 value) 22 | { 23 | return _deriveEIP712Digest(domainSeparator, orderHash); 24 | } 25 | } 26 | 27 | // solhint-enable func-name-mixedcase 28 | -------------------------------------------------------------------------------- /test/trading/seaport/utils/IImmutableERC1155.t.sol: -------------------------------------------------------------------------------- 1 | // Copyright Immutable Pty Ltd 2018 - 2023 2 | // SPDX-License-Identifier: Apache 2.0 3 | 4 | // solhint-disable-next-line compiler-version 5 | pragma solidity ^0.8.17; 6 | 7 | import {IERC1155} from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol"; 8 | 9 | /** 10 | * @notice Interface for Immutable's ERC1155 11 | */ 12 | interface IImmutableERC1155 is IERC1155 { 13 | /** 14 | * @notice Mints a new token 15 | * @param to The address that will receive the minted tokens 16 | * @param id The id of the token to mint 17 | * @param value The amount of tokens to mint 18 | * @param data Additional data 19 | */ 20 | function safeMint(address to, uint256 id, uint256 value, bytes memory data) external; 21 | 22 | /** 23 | * @notice Grants minter role to the user 24 | * @param user The address to grant the MINTER_ROLE to 25 | */ 26 | function grantMinterRole(address user) external; 27 | } 28 | -------------------------------------------------------------------------------- /test/trading/seaport/utils/IImmutableERC721.t.sol: -------------------------------------------------------------------------------- 1 | // Copyright Immutable Pty Ltd 2018 - 2023 2 | // SPDX-License-Identifier: Apache 2.0 3 | 4 | // solhint-disable-next-line compiler-version 5 | pragma solidity ^0.8.17; 6 | 7 | import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; 8 | 9 | /** 10 | * @notice Interface for Immutable's ERC721 11 | */ 12 | interface IImmutableERC721 is IERC721 { 13 | /** 14 | * @notice Allows minter to mint `tokenID` to `to` 15 | * @param to the address to mint the token to 16 | * @param tokenID the ID of the token to mint 17 | */ 18 | function safeMint(address to, uint256 tokenID) external; 19 | 20 | /** 21 | * @notice Allows admin grant `user` `MINTER` role 22 | * @param user The address to grant the `MINTER` role to 23 | */ 24 | function grantMinterRole(address user) external; 25 | } 26 | -------------------------------------------------------------------------------- /test/trading/seaport/utils/IOperatorAllowlistUpgradeable.t.sol: -------------------------------------------------------------------------------- 1 | // Copyright Immutable Pty Ltd 2018 - 2023 2 | // SPDX-License-Identifier: Apache 2.0 3 | 4 | // solhint-disable-next-line compiler-version 5 | pragma solidity ^0.8.17; 6 | 7 | /** 8 | * @notice Required interface of an OperatorAllowlist compliant contract 9 | */ 10 | interface IOperatorAllowlistUpgradeable { 11 | /** 12 | * @notice Grants `DEFAULT_ADMIN_ROLE` to the supplied `admin` address 13 | * @param _roleAdmin the address to grant `DEFAULT_ADMIN_ROLE` to 14 | * @param _upgradeAdmin the address to grant `UPGRADE_ROLE` to 15 | */ 16 | function initialize(address _roleAdmin, address _upgradeAdmin, address _registerarAdmin) external; 17 | 18 | /** 19 | * @notice Adds a list of multiple addresses to Allowlist 20 | * @param addressTargets the addresses to be added to the allowlist 21 | */ 22 | function addAddressesToAllowlist(address[] calldata addressTargets) external; 23 | } 24 | -------------------------------------------------------------------------------- /test/trading/seaport/utils/SigningTestHelper.t.sol: -------------------------------------------------------------------------------- 1 | // Copyright (c) Immutable Pty Ltd 2018 - 2024 2 | // SPDX-License-Identifier: Apache-2 3 | 4 | // solhint-disable-next-line compiler-version 5 | pragma solidity ^0.8.17; 6 | 7 | // solhint-disable-next-line no-global-import 8 | import "forge-std/Test.sol"; 9 | 10 | abstract contract SigningTestHelper is Test { 11 | function _sign(uint256 signerPrivateKey, bytes32 signatureDigest) internal pure returns (bytes memory) { 12 | (uint8 v, bytes32 r, bytes32 s) = vm.sign(signerPrivateKey, signatureDigest); 13 | return abi.encodePacked(r, s, v); 14 | } 15 | 16 | function _signCompact(uint256 signerPrivateKey, bytes32 signatureDigest) internal pure returns (bytes memory) { 17 | (uint8 v, bytes32 r, bytes32 s) = vm.sign(signerPrivateKey, signatureDigest); 18 | if (v != 27) { 19 | // then left-most bit of s has to be flipped to 1. 20 | s = s | bytes32(uint256(1) << 255); 21 | } 22 | return abi.encodePacked(r, s); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /test/trading/seaport/utils/criteria.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "ethers"; 2 | 3 | const { keccak256 } = ethers.utils; 4 | 5 | type BufferElementPositionIndex = { [key: string]: number }; 6 | 7 | export const merkleTree = (tokenIds: ethers.BigNumber[]) => { 8 | const elements = tokenIds 9 | .map((tokenId) => Buffer.from(tokenId.toHexString().slice(2).padStart(64, "0"), "hex")) 10 | .sort(Buffer.compare) 11 | .filter((el, idx, arr) => { 12 | return idx === 0 || !arr[idx - 1].equals(el); 13 | }); 14 | 15 | const bufferElementPositionIndex = elements.reduce((memo: BufferElementPositionIndex, el, index) => { 16 | memo["0x" + el.toString("hex")] = index; 17 | return memo; 18 | }, {}); 19 | 20 | // Create layers 21 | const layers = getLayers(elements); 22 | 23 | const root = "0x" + layers[layers.length - 1][0].toString("hex"); 24 | 25 | const proofs = Object.fromEntries( 26 | elements.map((el) => [ethers.BigNumber.from(el).toString(), getHexProof(el, bufferElementPositionIndex, layers)]) 27 | ); 28 | 29 | const maxProofLength = Math.max(...Object.values(proofs).map((i) => i.length)); 30 | 31 | return { 32 | root, 33 | proofs, 34 | maxProofLength, 35 | }; 36 | }; 37 | 38 | const getLayers = (elements: Buffer[]) => { 39 | if (elements.length === 0) { 40 | throw new Error("empty tree"); 41 | } 42 | 43 | const layers = []; 44 | layers.push(elements.map((el) => Buffer.from(keccak256(el).slice(2), "hex"))); 45 | 46 | // Get next layer until we reach the root 47 | while (layers[layers.length - 1].length > 1) { 48 | layers.push(getNextLayer(layers[layers.length - 1])); 49 | } 50 | 51 | return layers; 52 | }; 53 | 54 | const getNextLayer = (elements: Buffer[]) => { 55 | return elements.reduce((layer: Buffer[], el, idx, arr) => { 56 | if (idx % 2 === 0) { 57 | // Hash the current element with its pair element 58 | layer.push(combinedHash(el, arr[idx + 1])); 59 | } 60 | 61 | return layer; 62 | }, []); 63 | }; 64 | 65 | const combinedHash = (first: Buffer, second: Buffer) => { 66 | if (!first) { 67 | return second; 68 | } 69 | if (!second) { 70 | return first; 71 | } 72 | 73 | return Buffer.from(keccak256(Buffer.concat([first, second].sort(Buffer.compare))).slice(2), "hex"); 74 | }; 75 | 76 | const getHexProof = (el: Buffer, bufferElementPositionIndex: BufferElementPositionIndex, layers: Buffer[][]) => { 77 | let idx = bufferElementPositionIndex["0x" + el.toString("hex")]; 78 | 79 | if (typeof idx !== "number") { 80 | throw new Error("Element does not exist in Merkle tree"); 81 | } 82 | 83 | const proofBuffer = layers.reduce((proof: Buffer[], layer) => { 84 | const pairIdx = idx % 2 === 0 ? idx + 1 : idx - 1; 85 | const pairElement = pairIdx < layer.length ? layer[pairIdx] : null; 86 | 87 | if (pairElement) { 88 | proof.push(pairElement); 89 | } 90 | 91 | idx = Math.floor(idx / 2); 92 | 93 | return proof; 94 | }, []); 95 | 96 | return proofBuffer.map((el) => "0x" + el.toString("hex")); 97 | }; 98 | -------------------------------------------------------------------------------- /test/trading/seaport/utils/deploy-immutable-contracts.ts: -------------------------------------------------------------------------------- 1 | import hre from "hardhat"; 2 | import { ImmutableSeaport, ImmutableSignedZone } from "../../../../typechain-types"; 3 | 4 | // Deploy the Immutable ecosystem contracts, returning the contract 5 | // references 6 | export async function deployImmutableContracts(serverSignerAddress: string): Promise<{ 7 | immutableSeaport: ImmutableSeaport; 8 | immutableSignedZone: ImmutableSignedZone; 9 | conduitKey: string; 10 | conduitAddress: string; 11 | }> { 12 | const accounts = await hre.ethers.getSigners(); 13 | const seaportConduitControllerContractFactory = await hre.ethers.getContractFactory("ConduitController"); 14 | const seaportConduitControllerContract = await seaportConduitControllerContractFactory.deploy(); 15 | await seaportConduitControllerContract.deployed(); 16 | 17 | const readOnlyValidatorFactory = await hre.ethers.getContractFactory("ReadOnlyOrderValidator"); 18 | const readOnlyValidatorContract = await readOnlyValidatorFactory.deploy(); 19 | 20 | const validatorHelperFactory = await hre.ethers.getContractFactory("SeaportValidatorHelper"); 21 | const validatorHelperContract = await validatorHelperFactory.deploy(); 22 | 23 | const seaportValidatorFactory = await hre.ethers.getContractFactory("SeaportValidator"); 24 | await seaportValidatorFactory.deploy( 25 | readOnlyValidatorContract.address, 26 | validatorHelperContract.address, 27 | seaportConduitControllerContract.address 28 | ); 29 | 30 | const immutableSignedZoneFactory = await hre.ethers.getContractFactory("ImmutableSignedZone"); 31 | const immutableSignedZoneContract = (await immutableSignedZoneFactory.deploy( 32 | "ImmutableSignedZone", 33 | "", 34 | "", 35 | accounts[0].address 36 | )) as ImmutableSignedZone; 37 | await immutableSignedZoneContract.deployed(); 38 | 39 | const tx = await immutableSignedZoneContract.addSigner(serverSignerAddress); 40 | await tx.wait(1); 41 | 42 | // conduit key: The conduit key used to deploy the conduit. Note that the first twenty bytes of the conduit key must match the caller of this contract. 43 | const conduitKey = `${accounts[0].address}000000000000000000000000`; 44 | 45 | await (await seaportConduitControllerContract.createConduit(conduitKey, accounts[0].address)).wait(1); 46 | 47 | const { conduit: conduitAddress } = await seaportConduitControllerContract.getConduit(conduitKey); 48 | 49 | const seaportContractFactory = await hre.ethers.getContractFactory("ImmutableSeaport"); 50 | const seaportContract = (await seaportContractFactory.deploy( 51 | seaportConduitControllerContract.address, 52 | accounts[0].address 53 | )) as ImmutableSeaport; 54 | await seaportContract.deployed(); 55 | 56 | // add ImmutableZone 57 | await (await seaportContract.connect(accounts[0]).setAllowedZone(immutableSignedZoneContract.address, true)).wait(1); 58 | await (await seaportConduitControllerContract.updateChannel(conduitAddress, seaportContract.address, true)).wait(1); 59 | 60 | return { 61 | immutableSeaport: seaportContract, 62 | immutableSignedZone: immutableSignedZoneContract, 63 | conduitKey, 64 | conduitAddress, 65 | }; 66 | } 67 | -------------------------------------------------------------------------------- /test/trading/seaport/utils/eip712/bulk-orders.ts: -------------------------------------------------------------------------------- 1 | import { _TypedDataEncoder, keccak256, toUtf8Bytes } from "ethers/lib/utils"; 2 | 3 | import { Eip712MerkleTree } from "./Eip712MerkleTree"; 4 | import { DefaultGetter } from "./defaults"; 5 | import { fillArray } from "./utils"; 6 | 7 | import type { OrderComponents } from "../types"; 8 | import type { EIP712TypeDefinitions } from "./defaults"; 9 | 10 | const bulkOrderType = { 11 | BulkOrder: [{ name: "tree", type: "OrderComponents[2][2][2][2][2][2][2]" }], 12 | OrderComponents: [ 13 | { name: "offerer", type: "address" }, 14 | { name: "zone", type: "address" }, 15 | { name: "offer", type: "OfferItem[]" }, 16 | { name: "consideration", type: "ConsiderationItem[]" }, 17 | { name: "orderType", type: "uint8" }, 18 | { name: "startTime", type: "uint256" }, 19 | { name: "endTime", type: "uint256" }, 20 | { name: "zoneHash", type: "bytes32" }, 21 | { name: "salt", type: "uint256" }, 22 | { name: "conduitKey", type: "bytes32" }, 23 | { name: "counter", type: "uint256" }, 24 | ], 25 | OfferItem: [ 26 | { name: "itemType", type: "uint8" }, 27 | { name: "token", type: "address" }, 28 | { name: "identifierOrCriteria", type: "uint256" }, 29 | { name: "startAmount", type: "uint256" }, 30 | { name: "endAmount", type: "uint256" }, 31 | ], 32 | ConsiderationItem: [ 33 | { name: "itemType", type: "uint8" }, 34 | { name: "token", type: "address" }, 35 | { name: "identifierOrCriteria", type: "uint256" }, 36 | { name: "startAmount", type: "uint256" }, 37 | { name: "endAmount", type: "uint256" }, 38 | { name: "recipient", type: "address" }, 39 | ], 40 | }; 41 | function getBulkOrderTypes(height: number): EIP712TypeDefinitions { 42 | const types = { ...bulkOrderType }; 43 | types.BulkOrder = [{ name: "tree", type: `OrderComponents${`[2]`.repeat(height)}` }]; 44 | return types; 45 | } 46 | 47 | export function getBulkOrderTreeHeight(length: number): number { 48 | return Math.max(Math.ceil(Math.log2(length)), 1); 49 | } 50 | 51 | export function getBulkOrderTree( 52 | orderComponents: OrderComponents[], 53 | startIndex = 0, 54 | height = getBulkOrderTreeHeight(orderComponents.length + startIndex) 55 | ) { 56 | const types = getBulkOrderTypes(height); 57 | const defaultNode = DefaultGetter.from(types, "OrderComponents"); 58 | let elements = [...orderComponents]; 59 | 60 | if (startIndex > 0) { 61 | elements = [...fillArray([] as OrderComponents[], startIndex, defaultNode), ...orderComponents]; 62 | } 63 | const tree = new Eip712MerkleTree(types, "BulkOrder", "OrderComponents", elements, height); 64 | return tree; 65 | } 66 | 67 | export function getBulkOrderTypeHash(height: number): string { 68 | const types = getBulkOrderTypes(height); 69 | const encoder = _TypedDataEncoder.from(types); 70 | const typeString = toUtf8Bytes(encoder._types.BulkOrder); 71 | return keccak256(typeString); 72 | } 73 | 74 | export function getBulkOrderTypeHashes(maxHeight: number): string[] { 75 | const typeHashes: string[] = []; 76 | for (let i = 0; i < maxHeight; i++) { 77 | typeHashes.push(getBulkOrderTypeHash(i + 1)); 78 | } 79 | return typeHashes; 80 | } 81 | -------------------------------------------------------------------------------- /test/trading/seaport/utils/eip712/utils.ts: -------------------------------------------------------------------------------- 1 | import { hexConcat, hexlify, keccak256 } from "ethers/lib/utils"; 2 | 3 | import type { BytesLike } from "ethers"; 4 | 5 | export const makeArray = (len: number, getValue: (i: number) => T) => 6 | Array(len) 7 | .fill(0) 8 | .map((_, i) => getValue(i)); 9 | 10 | export const chunk = (array: T[], size: number) => { 11 | return makeArray(Math.ceil(array.length / size), (i) => array.slice(i * size, (i + 1) * size)); 12 | }; 13 | 14 | export const bufferToHex = (buf: Buffer) => hexlify(buf); 15 | 16 | export const hexToBuffer = (value: string) => Buffer.from(value.slice(2), "hex"); 17 | 18 | export const bufferKeccak = (value: BytesLike) => hexToBuffer(keccak256(value)); 19 | 20 | export const hashConcat = (arr: BytesLike[]) => bufferKeccak(hexConcat(arr)); 21 | 22 | export const fillArray = (arr: T[], length: number, value: T) => { 23 | if (length > arr.length) arr.push(...Array(length - arr.length).fill(value)); 24 | return arr; 25 | }; 26 | 27 | export const getRoot = (elements: (Buffer | string)[], hashLeaves = true) => { 28 | if (elements.length === 0) throw new Error("empty tree"); 29 | 30 | const leaves = elements.map((e) => { 31 | const leaf = Buffer.isBuffer(e) ? e : hexToBuffer(e); 32 | return hashLeaves ? bufferKeccak(leaf) : leaf; 33 | }); 34 | 35 | const layers: Buffer[][] = [leaves]; 36 | 37 | // Get next layer until we reach the root 38 | while (layers[layers.length - 1].length > 1) { 39 | layers.push(getNextLayer(layers[layers.length - 1])); 40 | } 41 | 42 | return layers[layers.length - 1][0]; 43 | }; 44 | 45 | export const getNextLayer = (elements: Buffer[]) => { 46 | return chunk(elements, 2).map(hashConcat); 47 | // return elements.reduce((layer: Buffer[], el, idx, arr) => { 48 | // if (idx % 2 === 0) layer.push(hashConcat(el, arr[idx + 1])); 49 | // return layer; 50 | // }, []); 51 | }; 52 | -------------------------------------------------------------------------------- /test/trading/seaport/utils/erc721.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import hre from "hardhat"; 3 | 4 | import { getOfferOrConsiderationItem, randomBN } from "./encoding"; 5 | 6 | import type { TestERC721 } from "../../../../typechain-types"; 7 | import type { BigNumberish, BigNumber, Contract, Wallet } from "ethers"; 8 | 9 | export async function deployERC721(): Promise { 10 | const erc721Factory = await hre.ethers.getContractFactory("TestERC721"); 11 | const erc721 = (await erc721Factory.deploy()) as TestERC721; 12 | return erc721.deployed(); 13 | } 14 | 15 | export async function mint721(erc721: TestERC721, signer: Wallet | Contract): Promise { 16 | const nftId = randomBN(); 17 | await erc721.mint(signer.address, nftId); 18 | return nftId; 19 | } 20 | 21 | export async function set721ApprovalForAll(erc721: TestERC721, signer: Wallet, spender: string, approved = true) { 22 | return expect(erc721.connect(signer).setApprovalForAll(spender, approved)) 23 | .to.emit(erc721, "ApprovalForAll") 24 | .withArgs(signer.address, spender, approved); 25 | } 26 | 27 | export async function mintAndApprove721(erc721: TestERC721, signer: Wallet, spender: string): Promise { 28 | await set721ApprovalForAll(erc721, signer, spender, true); 29 | return mint721(erc721, signer); 30 | } 31 | 32 | export async function getTestItem721( 33 | tokenAddress: string, 34 | identifierOrCriteria: BigNumberish, 35 | startAmount: BigNumberish = 1, 36 | endAmount: BigNumberish = 1, 37 | recipient?: string 38 | ) { 39 | return getOfferOrConsiderationItem(2, tokenAddress, identifierOrCriteria, startAmount, endAmount, recipient); 40 | } 41 | export function getTestItem721WithCriteria( 42 | tokenAddress: string, 43 | identifierOrCriteria: BigNumberish, 44 | startAmount: BigNumberish = 1, 45 | endAmount: BigNumberish = 1, 46 | recipient?: string 47 | ) { 48 | return getOfferOrConsiderationItem(4, tokenAddress, identifierOrCriteria, startAmount, endAmount, recipient); 49 | } 50 | -------------------------------------------------------------------------------- /test/trading/seaport/utils/faucet.ts: -------------------------------------------------------------------------------- 1 | import { parseEther } from "@ethersproject/units"; 2 | import { ethers } from "hardhat"; 3 | 4 | import { randomHex } from "./encoding"; 5 | 6 | import type { JsonRpcProvider } from "@ethersproject/providers"; 7 | 8 | const TEN_THOUSAND_ETH = parseEther("10000").toHexString().replace("0x0", "0x"); 9 | 10 | export const faucet = async (address: string, provider: JsonRpcProvider) => { 11 | await provider.send("hardhat_setBalance", [address, TEN_THOUSAND_ETH]); 12 | }; 13 | 14 | export const getWalletWithEther = async () => { 15 | const wallet = new ethers.Wallet(randomHex(32), ethers.provider); 16 | await faucet(wallet.address, ethers.provider); 17 | return wallet; 18 | }; 19 | -------------------------------------------------------------------------------- /test/trading/seaport/utils/types.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber } from "ethers"; 2 | 3 | export type ConsiderationItem = { 4 | itemType: number; 5 | token: string; 6 | identifierOrCriteria: BigNumber; 7 | startAmount: BigNumber; 8 | endAmount: BigNumber; 9 | recipient: string; 10 | }; 11 | export type OfferItem = { 12 | itemType: number; 13 | token: string; 14 | identifierOrCriteria: BigNumber; 15 | startAmount: BigNumber; 16 | endAmount: BigNumber; 17 | }; 18 | 19 | export type OrderParameters = { 20 | offerer: string; 21 | zone: string; 22 | offer: OfferItem[]; 23 | consideration: ConsiderationItem[]; 24 | orderType: number; 25 | startTime: string | BigNumber | number; 26 | endTime: string | BigNumber | number; 27 | zoneHash: string; 28 | salt: string; 29 | conduitKey: string; 30 | totalOriginalConsiderationItems: string | BigNumber | number; 31 | }; 32 | 33 | export type CriteriaResolver = { 34 | orderIndex: number; 35 | side: 0 | 1; 36 | index: number; 37 | identifier: BigNumber; 38 | criteriaProof: string[]; 39 | }; 40 | 41 | export type OrderComponents = Omit & { 42 | counter: BigNumber; 43 | }; 44 | -------------------------------------------------------------------------------- /test/trading/seaport/zones/immutable-signed-zone/v2/IImmutableSignedZoneV2Harness.t.sol: -------------------------------------------------------------------------------- 1 | // Copyright (c) Immutable Pty Ltd 2018 - 2024 2 | // SPDX-License-Identifier: Apache-2 3 | 4 | // solhint-disable-next-line compiler-version 5 | pragma solidity ^0.8.17; 6 | 7 | import {ZoneInterface} from "seaport/contracts/interfaces/ZoneInterface.sol"; 8 | import {ReceivedItem, ZoneParameters} from "seaport-types/src/lib/ConsiderationStructs.sol"; 9 | import {SIP7Interface} from 10 | "../../../../../../contracts/trading/seaport/zones/immutable-signed-zone/v2/interfaces/SIP7Interface.sol"; 11 | 12 | // solhint-disable func-name-mixedcase 13 | 14 | interface IImmutableSignedZoneV2Harness is ZoneInterface, SIP7Interface { 15 | function grantRole(bytes32 role, address account) external; 16 | 17 | function DEFAULT_ADMIN_ROLE() external view returns (bytes32); 18 | 19 | function ZONE_MANAGER_ROLE() external view returns (bytes32); 20 | 21 | function exposed_domainSeparator() external view returns (bytes32); 22 | 23 | function exposed_deriveDomainSeparator() external view returns (bytes32 domainSeparator); 24 | 25 | function exposed_getSupportedSubstandards() external pure returns (uint256[] memory substandards); 26 | 27 | function exposed_deriveSignedOrderHash( 28 | address fulfiller, 29 | uint64 expiration, 30 | bytes32 orderHash, 31 | bytes calldata context 32 | ) external view returns (bytes32 signedOrderHash); 33 | 34 | function exposed_validateSubstandards(bytes calldata context, ZoneParameters calldata zoneParameters) 35 | external 36 | pure; 37 | 38 | function exposed_validateSubstandard3(bytes calldata context, ZoneParameters calldata zoneParameters) 39 | external 40 | pure 41 | returns (uint256); 42 | 43 | function exposed_validateSubstandard4(bytes calldata context, ZoneParameters calldata zoneParameters) 44 | external 45 | pure 46 | returns (uint256); 47 | 48 | function exposed_validateSubstandard6(bytes calldata context, ZoneParameters calldata zoneParameters) 49 | external 50 | pure 51 | returns (uint256); 52 | 53 | function exposed_deriveReceivedItemsHash( 54 | ReceivedItem[] calldata receivedItems, 55 | uint256 scalingFactorNumerator, 56 | uint256 scalingFactorDenominator 57 | ) external pure returns (bytes32); 58 | 59 | function exposed_bytes32ArrayIncludes(bytes32[] calldata sourceArray, bytes32[] memory values) 60 | external 61 | pure 62 | returns (bool); 63 | } 64 | 65 | // solhint-enable func-name-mixedcase 66 | -------------------------------------------------------------------------------- /test/utils/DeployAllowlistProxy.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache 2.0 2 | pragma solidity >=0.8.19 <0.8.29; 3 | 4 | import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; 5 | import {OperatorAllowlistUpgradeable} from "../../contracts/allowlist/OperatorAllowlistUpgradeable.sol"; 6 | 7 | /// Deploys the OperatorAllowlistUpgradeable contract behind an ERC1967 Proxy and returns the address of the proxy 8 | contract DeployOperatorAllowlist { 9 | function run(address admin, address upgradeAdmin, address registerarAdmin) external returns (address) { 10 | OperatorAllowlistUpgradeable impl = new OperatorAllowlistUpgradeable(); 11 | 12 | bytes memory initData = abi.encodeWithSelector( 13 | OperatorAllowlistUpgradeable.initialize.selector, admin, upgradeAdmin, registerarAdmin 14 | ); 15 | 16 | ERC1967Proxy proxy = new ERC1967Proxy(address(impl), initData); 17 | 18 | return address(proxy); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /test/utils/DeployMockMarketPlace.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache 2.0 2 | pragma solidity >=0.8.19 <0.8.29; 3 | 4 | import {MockMarketplace} from "../../contracts/mocks/MockMarketplace.sol"; 5 | 6 | /// Deploys the OperatorAllowlistUpgradeable contract behind an ERC1967 Proxy and returns the address of the proxy 7 | contract DeployMockMarketPlace { 8 | function run(address erc721Address) external returns (MockMarketplace) { 9 | MockMarketplace marketplace = new MockMarketplace(erc721Address); 10 | return marketplace; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/utils/DeploySCW.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache 2.0 2 | pragma solidity >=0.8.19 <0.8.29; 3 | 4 | import "forge-std/Test.sol"; 5 | import {MockWallet} from "../../contracts/mocks/MockWallet.sol"; 6 | import {MockWalletFactory} from "../../contracts/mocks/MockWalletFactory.sol"; 7 | 8 | contract DeploySCWallet { 9 | MockWallet public mockWalletModule; 10 | MockWallet public scw; 11 | MockWalletFactory public scmf; 12 | address public scwAddress; 13 | 14 | function run(bytes32 salt) external returns (address, address) { 15 | scmf = new MockWalletFactory(); 16 | mockWalletModule = new MockWallet(); 17 | scmf.deploy(address(mockWalletModule), salt); 18 | scwAddress = scmf.getAddress(address(mockWalletModule), salt); 19 | scw = MockWallet(scwAddress); 20 | return (scwAddress, address(mockWalletModule)); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /test/utils/Sign.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.19 <0.8.29; 3 | 4 | import "forge-std/Test.sol"; 5 | 6 | contract Sign { 7 | bytes32 private _DOMAIN_SEPARATOR; 8 | bytes32 private immutable _PERMIT_TYPEHASH = 9 | keccak256("Permit(address owner,address spender,bool approved,uint256 nonce,uint256 deadline)"); 10 | 11 | constructor(bytes32 DOMAIN_SEPARATOR_) { 12 | _DOMAIN_SEPARATOR = DOMAIN_SEPARATOR_; 13 | } 14 | 15 | function buildPermitDigest(address owner, address spender, bool approved, uint256 nonce, uint256 deadline) 16 | public 17 | view 18 | returns (bytes32) 19 | { 20 | bytes32 structHash = keccak256(abi.encode(_PERMIT_TYPEHASH, owner, spender, approved, nonce, deadline)); 21 | return keccak256(abi.encodePacked("\x19\x01", _DOMAIN_SEPARATOR, structHash)); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2018", 4 | "module": "commonjs", 5 | "strict": true, 6 | "esModuleInterop": true, 7 | "outDir": "dist", 8 | "declaration": true, 9 | "resolveJsonModule": true, 10 | "skipLibCheck": true 11 | }, 12 | "include": ["./index.ts", "./test/**/*.ts"], 13 | "files": ["hardhat.config.ts"] 14 | } 15 | -------------------------------------------------------------------------------- /wagmi.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "@wagmi/cli"; 2 | import { Abi } from "abitype"; 3 | 4 | import GuardedMulticaller from "./foundry-out/GuardedMulticaller.sol/GuardedMulticaller.json"; 5 | import ImmutableERC721 from "./foundry-out/ImmutableERC721.sol/ImmutableERC721.json"; 6 | import ImmutableERC721MintByID from "./foundry-out/ImmutableERC721MintByID.sol/ImmutableERC721MintByID.json"; 7 | import ImmutableERC1155 from "./foundry-out/ImmutableERC1155.sol/ImmutableERC1155.json"; 8 | import PaymentSplitter from "./foundry-out/PaymentSplitter.sol/PaymentSplitter.json"; 9 | import GuardedMulticaller2 from "./foundry-out/GuardedMulticaller2.sol/GuardedMulticaller2.json"; 10 | 11 | // https://github.com/wevm/viem/discussions/1009 12 | export default defineConfig({ 13 | out: "abi/generated.ts", 14 | contracts: [ 15 | { 16 | name: "GuardedMulticaller", 17 | abi: GuardedMulticaller.abi as Abi, 18 | }, 19 | { 20 | name: "ImmutableERC721", 21 | abi: ImmutableERC721.abi as Abi, 22 | }, 23 | { 24 | name: "ImmutableERC721MintByID", 25 | abi: ImmutableERC721MintByID.abi as Abi, 26 | }, 27 | { 28 | name: "ImmutableERC1155", 29 | abi: ImmutableERC1155.abi as Abi, 30 | }, 31 | { 32 | name: "PaymentSplitter", 33 | abi: PaymentSplitter.abi as Abi, 34 | }, 35 | { 36 | name: "GuardedMulticaller2", 37 | abi: GuardedMulticaller2.abi as Abi, 38 | }, 39 | ], 40 | }); 41 | --------------------------------------------------------------------------------