├── .eslintignore ├── .eslintrc.js ├── .gitattributes ├── .github ├── actions │ └── install-dependencies │ │ └── action.yml └── workflows │ └── ci.yml ├── .gitignore ├── .gitmodules ├── .husky └── pre-commit ├── .prettierrc ├── .solcover.js ├── .solhint.json ├── LICENSE ├── README.md ├── audits ├── v1 │ ├── Consensys_Diligence.md │ ├── Quantstamp_Arcadeum_Report_Final.pdf │ └── sequence_quantstamp_audit_feb_2021.pdf └── v2 │ ├── Sequence Wallet - Zellic Audit Report.pdf │ └── consensys-horizon-sequence-wallet-audit-2023-02.pdf ├── config └── PROD.env.sample ├── contracts ├── Factory.sol ├── Wallet.sol ├── hooks │ ├── WalletProxyHook.sol │ └── interfaces │ │ └── IWalletProxy.sol ├── interfaces │ ├── IERC1271Wallet.sol │ ├── receivers │ │ ├── IERC1155Receiver.sol │ │ ├── IERC223Receiver.sol │ │ ├── IERC721Receiver.sol │ │ └── IERC777Receiver.sol │ └── tokens │ │ ├── IERC1155.sol │ │ ├── IERC20.sol │ │ └── IERC721.sol ├── mocks │ ├── AlwaysRevertMock.sol │ ├── CallReceiverMock.sol │ ├── DelegateCallMock.sol │ ├── ERC1155Mock.sol │ ├── ERC165CheckerMock.sol │ ├── ERC20Mock.sol │ ├── ERC721Mock.sol │ ├── GasBurnerMock.sol │ ├── HookCallerMock.sol │ ├── HookMock.sol │ ├── LibBytesImpl.sol │ ├── LibBytesPointerImpl.sol │ ├── LibStringImp.sol │ └── ModuleMock.sol ├── modules │ ├── GuestModule.sol │ ├── MainModule.sol │ ├── MainModuleGasEstimation.sol │ ├── MainModuleUpgradable.sol │ ├── commons │ │ ├── Implementation.sol │ │ ├── ModuleAuth.sol │ │ ├── ModuleAuthConvenience.sol │ │ ├── ModuleAuthFixed.sol │ │ ├── ModuleAuthUpgradable.sol │ │ ├── ModuleCalls.sol │ │ ├── ModuleCreator.sol │ │ ├── ModuleERC165.sol │ │ ├── ModuleERC5719.sol │ │ ├── ModuleExtraAuth.sol │ │ ├── ModuleHooks.sol │ │ ├── ModuleIPFS.sol │ │ ├── ModuleNonce.sol │ │ ├── ModuleOnlyDelegatecall.sol │ │ ├── ModuleSelfAuth.sol │ │ ├── ModuleStorage.sol │ │ ├── ModuleUpdate.sol │ │ ├── gas-estimation │ │ │ ├── ModuleIgnoreAuthUpgradable.sol │ │ │ └── ModuleIgnoreNonceCalls.sol │ │ ├── interfaces │ │ │ ├── IModuleAuth.sol │ │ │ ├── IModuleAuthUpgradable.sol │ │ │ ├── IModuleCalls.sol │ │ │ ├── IModuleCreator.sol │ │ │ ├── IModuleHooks.sol │ │ │ └── IModuleUpdate.sol │ │ └── submodules │ │ │ ├── auth │ │ │ ├── SequenceBaseSig.sol │ │ │ ├── SequenceChainedSig.sol │ │ │ ├── SequenceDynamicSig.sol │ │ │ └── SequenceNoChainIdSig.sol │ │ │ └── nonce │ │ │ └── SubModuleNonce.sol │ └── utils │ │ ├── GasEstimator.sol │ │ ├── MultiCallUtils.sol │ │ ├── RequireUtils.sol │ │ └── SequenceUtils.sol ├── trust │ ├── Trust.sol │ └── TrustFactory.sol └── utils │ ├── LibAddress.sol │ ├── LibBytes.sol │ ├── LibBytesPointer.sol │ ├── LibOptim.sol │ ├── LibString.sol │ └── SignatureValidator.sol ├── foundry.toml ├── foundry_test ├── base │ └── AdvTest.sol ├── hooks │ └── WalletProxyHook.t.sol ├── modules │ ├── commons │ │ ├── Implementation.t.sol │ │ ├── ModuleCalls.t.sol │ │ ├── ModuleERC5719.t.sol │ │ ├── ModuleExtraAuth.t.sol │ │ ├── ModuleIPFS.t.sol │ │ ├── ModuleStorage.t.sol │ │ └── submodules │ │ │ ├── auth │ │ │ ├── SequenceBaseSig.t.sol │ │ │ ├── SequenceChainedSig.t.sol │ │ │ ├── SequenceDynamicSig.t.sol │ │ │ └── SequenceNoChainIdSig.t.sol │ │ │ └── nonce │ │ │ └── SubModuleNonce.t.sol │ └── utils │ │ ├── L2CompressorEncoder.sol │ │ ├── L2CompressorHuff.t.sol │ │ ├── L2CompressorHuffReadExecute.sol │ │ ├── L2CompressorHuffReadFlag.t.sol │ │ ├── L2CompressorHuffReadNonce.sol │ │ ├── L2CompressorHuffReadTx.t.sol │ │ ├── L2CompressorHuffReadTxs.t.sol │ │ └── RequireUtils.t.sol ├── trust │ ├── Trust.t.sol │ └── TrustFactory.t.sol └── utils │ ├── LibAddress.t.sol │ ├── LibBytes.t.sol │ ├── LibBytesPointer.t.sol │ ├── LibOptim.t.sol │ └── SignatureValidator.t.sol ├── funding.json ├── hardhat.config.ts ├── networks ├── arbitrum.json ├── arbitrumGoerli.json ├── arbitrumNova.json ├── avalanche.json ├── avalancheFuji.json ├── bnb.json ├── bnbTestnet.json ├── gnosis.json ├── goerli.json ├── hardhat.json ├── mainnet.json ├── mumbai.json ├── optimism.json ├── polygon.json └── polygonZkevm.json ├── package.json ├── pnpm-lock.yaml ├── remappings.txt ├── run_huff_tests.sh ├── src ├── Errors.huff ├── L2Compressor.huff ├── L2CompressorLib.huff └── imps │ ├── L2CompressorImps.huff │ ├── L2CompressorReadExecute.huff │ ├── L2CompressorReadFlag.huff │ ├── L2CompressorReadNonce.huff │ ├── L2CompressorReadTx.huff │ └── L2CompressorReadTxs.huff ├── test ├── ChainedSignatures.spec.ts ├── ERC165.spec.ts ├── Factory.spec.ts ├── GasEstimation.spec.ts ├── GuestModule.spec.ts ├── LibBytes.spec.ts ├── LibString.spec.ts ├── MainModule.bench.ts ├── MainModule.spec.ts ├── MerkleSignatures.spec.ts ├── MultiCallUtils.spec.ts └── utils │ ├── contracts.ts │ ├── imposter.ts │ ├── index.ts │ ├── sequence.ts │ └── wallet.ts ├── tsconfig.json ├── typings ├── chai-bignumber.d.ts ├── chai-bn.ts └── truffle.d.ts └── utils ├── benchmarker.ts ├── config-loader.ts ├── deploy-contracts.ts └── workers └── bench-worker.ts /.eslintignore: -------------------------------------------------------------------------------- 1 | .eslintrc.js 2 | node_modules 3 | src/gen 4 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | const { off } = require("process") 2 | 3 | module.exports = { 4 | parser: '@typescript-eslint/parser', 5 | parserOptions: { 6 | ecmaVersion: 2018, 7 | sourceType: 'module' 8 | }, 9 | 10 | extends: [ 11 | 'plugin:@typescript-eslint/recommended', 12 | 'plugin:import/errors', 13 | 'plugin:import/warnings', 14 | 'plugin:import/typescript', 15 | 'prettier' 16 | ], 17 | 18 | rules: { 19 | '@typescript-eslint/no-unused-vars': 'off', 20 | '@typescript-eslint/no-explicit-any': 'off', 21 | '@typescript-eslint/no-non-null-assertion': 'off', 22 | '@typescript-eslint/explicit-module-boundary-types': 'off', 23 | '@typescript-eslint/ban-types': 'off', 24 | '@typescript-eslint/ban-ts-comment': 'off', 25 | '@typescript-eslint/no-empty-function': 'off', 26 | '@typescript-eslint/no-inferrable-types': 'off', 27 | '@typescript-eslint/no-var-requires': 'off', 28 | '@typescript-eslint/no-non-null-asserted-optional-chain': 'off', 29 | 30 | 'prefer-spread': 'off', 31 | 'prefer-const': 'off', 32 | 33 | 'import/no-unresolved': 'off', 34 | // 'import/no-default-export': 2, 35 | 'import/no-named-as-default-member': 'off', 36 | 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity 2 | -------------------------------------------------------------------------------- /.github/actions/install-dependencies/action.yml: -------------------------------------------------------------------------------- 1 | name: Setup Node and PNPM dependencies 2 | 3 | runs: 4 | using: 'composite' 5 | 6 | steps: 7 | - name: Setup Node 8 | uses: actions/setup-node@v4 9 | with: 10 | node-version: 20 11 | 12 | - name: Setup PNPM 13 | uses: pnpm/action-setup@v3 14 | with: 15 | version: 9 16 | run_install: false 17 | 18 | - name: Get pnpm store directory 19 | id: pnpm-cache 20 | shell: bash 21 | run: | 22 | echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT 23 | 24 | - name: Setup pnpm cache 25 | uses: actions/cache@v4 26 | with: 27 | path: | 28 | ${{ steps.pnpm-cache.outputs.STORE_PATH }} 29 | key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} 30 | restore-keys: | 31 | ${{ runner.os }}-pnpm-store- 32 | 33 | - name: Install dependencies 34 | shell: bash 35 | run: pnpm install --frozen-lockfile 36 | if: ${{ steps.pnpm-cache.outputs.cache-hit != 'true' }} 37 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: [push] 2 | 3 | name: ci 4 | 5 | jobs: 6 | benchmark: 7 | name: Benchmark 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v4 11 | - uses: ./.github/actions/install-dependencies 12 | - run: pnpm build 13 | - run: pnpm benchmark 14 | 15 | lint-ts: 16 | name: Typescript lint 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v4 20 | - uses: ./.github/actions/install-dependencies 21 | - run: pnpm lint:ts 22 | 23 | lint-sol: 24 | name: Solidity lint 25 | runs-on: ubuntu-latest 26 | steps: 27 | - uses: actions/checkout@v4 28 | - uses: ./.github/actions/install-dependencies 29 | - run: pnpm lint:sol 30 | 31 | test: 32 | name: Test contracts 33 | runs-on: ubuntu-latest 34 | steps: 35 | - uses: actions/checkout@v4 36 | - uses: ./.github/actions/install-dependencies 37 | - run: pnpm build 38 | - run: pnpm test 39 | 40 | coverage: 41 | name: Coverage 42 | runs-on: ubuntu-latest 43 | steps: 44 | - uses: actions/checkout@v4 45 | - uses: ./.github/actions/install-dependencies 46 | - run: pnpm coverage || true 47 | - name: Coveralls 48 | uses: coverallsapp/github-action@master 49 | with: 50 | github-token: ${{ secrets.GITHUB_TOKEN }} 51 | 52 | huff-tests: 53 | name: Huff tests 54 | runs-on: ubuntu-latest 55 | steps: 56 | - uses: actions/checkout@v4 57 | with: 58 | submodules: recursive 59 | 60 | - name: Install Huff 61 | uses: huff-language/huff-toolchain@v3 62 | with: 63 | version: nightly 64 | 65 | - name: Run tests 66 | run: bash ./run_huff_tests.sh 67 | 68 | foundry-tests: 69 | name: Foundry tests 70 | runs-on: ubuntu-latest 71 | steps: 72 | - uses: actions/checkout@v4 73 | with: 74 | submodules: recursive 75 | 76 | - name: Install Foundry 77 | uses: foundry-rs/foundry-toolchain@v1 78 | with: 79 | version: nightly 80 | 81 | - name: Install Huff 82 | uses: huff-language/huff-toolchain@v3 83 | with: 84 | version: nightly 85 | 86 | - name: Run tests 87 | run: FOUNDRY_FUZZ_RUNS=2048 MAX_ARRAY_LEN=32 forge test -vvv 88 | 89 | foundry-tests-long-arrays: 90 | name: Foundry tests (long arrays) 91 | runs-on: ubuntu-latest 92 | steps: 93 | - uses: actions/checkout@v4 94 | with: 95 | submodules: recursive 96 | 97 | - name: Install Foundry 98 | uses: foundry-rs/foundry-toolchain@v1 99 | with: 100 | version: nightly 101 | 102 | - name: Install Huff 103 | uses: huff-language/huff-toolchain@v3 104 | with: 105 | version: nightly 106 | 107 | - name: Run tests 108 | run: FOUNDRY_FUZZ_RUNS=1024 forge test -vvv 109 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | .devcontainer 3 | node_modules/ 4 | 5 | npm-debug.log* 6 | yarn-debug.log* 7 | yarn-error.log* 8 | lerna-debug.log* 9 | 10 | *.js.map 11 | build 12 | coverage 13 | coverage.json 14 | 15 | config/*.env 16 | 17 | artifacts 18 | foundry_artifacts 19 | 20 | cache 21 | gen 22 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/forge-std"] 2 | path = lib/forge-std 3 | url = https://github.com/foundry-rs/forge-std 4 | [submodule "lib/foundry-huff"] 5 | path = lib/foundry-huff 6 | url = https://github.com/huff-language/foundry-huff -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | pnpm test 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "useTabs": false, 4 | "semi": false, 5 | "singleQuote": true, 6 | "trailingComma": "none", 7 | "arrowParens": "avoid", 8 | "printWidth": 130 9 | } 10 | -------------------------------------------------------------------------------- /.solcover.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | skipFiles: ['mocks', 'migrations'] 3 | } 4 | -------------------------------------------------------------------------------- /.solhint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solhint:recommended", 3 | "rules": { 4 | "quotes": "off", 5 | "const-name-snakecase": "off", 6 | "contract-name-camelcase": "off", 7 | "event-name-camelcase": "off", 8 | "func-name-mixedcase": "off", 9 | "func-param-name-mixedcase": "off", 10 | "modifier-name-mixedcase": "off", 11 | "private-vars-leading-underscore": "off", 12 | "use-forbidden-name": "off", 13 | "var-name-mixedcase": "off", 14 | "func-order": "off", 15 | "imports-on-top": "off", 16 | "ordering": "off", 17 | "no-global-import": "off", 18 | "no-unused-vars": "error", 19 | "not-rely-on-time": "off", 20 | "no-inline-assembly": "off", 21 | "visibility-modifier-order": "off", 22 | "compiler-version": ["error", "0.8.18"], 23 | "func-visibility": ["warn", {"ignoreConstructors":true}], 24 | "reason-string": ["warn", {"maxLength": 96}] 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sequence Smart Wallet Contracts 2 | 3 | Ethereum contracts for the Sequence Smart Wallet at [https://sequence.app](https://sequence.app). 4 | 5 | For more information, visit [https://sequence.build](https://sequence.build) 6 | 7 | ## Usage 8 | 9 | Please visit [https://sequence.app](https://sequence.app) to access the Sequence Wallet via your Web Browser, or 10 | download "Sequence Wallet" from the respective Apple/Google stores. 11 | 12 | You may also access, interface, or develop your own Sequence Wallet via [sequence.js](https://github.com/0xsequence/sequence.js). The 13 | sequence.js library offers a full open source library to interact, create, deploy and manage a Sequence Smart Wallet Account, 14 | as defined by the contracts in this repository. Also see [go-sequence](https://github.com/0xsequence/go-sequence) for an implementation 15 | in Go. 16 | 17 | ## Connecting your Dapp with Sequence Wallet 18 | 19 | If you wish to use Sequence Wallet in your Dapp, simply use [sequence.js](https://github.com/0xsequence/sequence.js). Sequence.js 20 | is an Ethereum client library built on [ethers.js](https://github.com/ethers-io/ethers.js), that provides an additional 21 | Sequence Smart Wallet Signer. 22 | 23 | Please refer to the [sequence.js](https://github.com/0xsequence/sequence.js) repository for usage instructions. 24 | 25 | # Developing a Custom Wallet UI for Sequence (Advanced!) 26 | 27 | If you wish to use the Sequence Wallet Contracts `@0xsequence/wallet-contracts` directly: 28 | 29 | 1. Install the contracts: `pnpm add @0xsequence/wallet-contracts` or `npm install @0xsequence/wallet-contracts` 30 | 2. Install the Sequence Wallet libraries: `pnpm add @0xsequence/wallet` or `npm install @0xsequence/wallet`. You can view the source, 31 | of the [wallet libraries](https://github.com/0xsequence/sequence.js/tree/master/packages/wallet), and review the 32 | [Sequence tests](https://github.com/0xsequence/sequence.js/tree/master/packages/0xsequence) for sample usage. 33 | 34 | **NOTE:** this integration is only needed if you want low-level access to the Sequence Wallet contracts, such as if you'd building 35 | your own custom wallet, or perhaps a CLI tool for managing your wallet. 36 | 37 | ## Security Review 38 | 39 | `@0xsequence/wallet-contracts` has been audited by independent parties. 40 | 41 | ### V2 Audits 42 | 43 | - [Consensys Diligence](https://github.com/0xsequence/wallet-contracts/blob/master/audits/v2/consensys-horizon-sequence-wallet-audit-2023-02.pdf) - February 2023 44 | - [Zellic](https://github.com/0xsequence/wallet-contracts/raw/master/audits/Quantstamp_Arcadeum_Report_Final.pdf) - March 2023 45 | 46 | ### V1 Audits 47 | 48 | - [Consensys Diligence](https://github.com/0xsequence/wallet-contracts/blob/master/audits/v1/Consensys_Diligence.md) - May 2020 49 | - [Quantstamp - initial audit](https://github.com/0xsequence/wallet-contracts/raw/master/audits/v1/Quantstamp_Arcadeum_Report_Final.pdf) - July 2020 50 | - [Quantstamp - audit of new capability, nested Sequence signers](https://github.com/0xsequence/wallet-contracts/raw/master/audits/v1/sequence_quantstamp_audit_feb_2021.pdf) - February 2021 51 | 52 | ## License 53 | 54 | Copyright (c) 2017-present [Horizon Blockchain Games Inc](https://horizon.io). 55 | 56 | Licensed under [Apache-2.0](https://github.com/0xsequence/erc-1155/blob/master/LICENSE) 57 | -------------------------------------------------------------------------------- /audits/v1/Consensys_Diligence.md: -------------------------------------------------------------------------------- 1 | # Consensys Diligence report May 2020 2 | 3 | [View full report](https://diligence.consensys.net/audits/private/cnhjwtpa-horizon-wallet/) 4 | 5 | Please note, Sequence Wallet was formerly known as "Arcadeum Wallet". Any references of "Arcadeum" 6 | are synonymous with "Sequence", and this repository. 7 | 8 | -------------------------------------------------------------------------------- /audits/v1/Quantstamp_Arcadeum_Report_Final.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xsequence/wallet-contracts/09c54e74c2803b55df32c0470f8b0e0ebe86f4c9/audits/v1/Quantstamp_Arcadeum_Report_Final.pdf -------------------------------------------------------------------------------- /audits/v1/sequence_quantstamp_audit_feb_2021.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xsequence/wallet-contracts/09c54e74c2803b55df32c0470f8b0e0ebe86f4c9/audits/v1/sequence_quantstamp_audit_feb_2021.pdf -------------------------------------------------------------------------------- /audits/v2/Sequence Wallet - Zellic Audit Report.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xsequence/wallet-contracts/09c54e74c2803b55df32c0470f8b0e0ebe86f4c9/audits/v2/Sequence Wallet - Zellic Audit Report.pdf -------------------------------------------------------------------------------- /audits/v2/consensys-horizon-sequence-wallet-audit-2023-02.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xsequence/wallet-contracts/09c54e74c2803b55df32c0470f8b0e0ebe86f4c9/audits/v2/consensys-horizon-sequence-wallet-audit-2023-02.pdf -------------------------------------------------------------------------------- /config/PROD.env.sample: -------------------------------------------------------------------------------- 1 | ETH_MNEMONIC="" 2 | INFURA_API_KEY="" 3 | ETHERSCAN="" 4 | -------------------------------------------------------------------------------- /contracts/Factory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | import "./Wallet.sol"; 5 | 6 | 7 | contract Factory { 8 | error DeployFailed(address _mainModule, bytes32 _salt); 9 | 10 | /** 11 | * @notice Will deploy a new wallet instance 12 | * @param _mainModule Address of the main module to be used by the wallet 13 | * @param _salt Salt used to generate the wallet, which is the imageHash 14 | * of the wallet's configuration. 15 | * @dev It is recommended to not have more than 200 signers as opcode repricing 16 | * could make transactions impossible to execute as all the signers must be 17 | * passed for each transaction. 18 | */ 19 | function deploy(address _mainModule, bytes32 _salt) public payable returns (address _contract) { 20 | bytes memory code = abi.encodePacked(Wallet.creationCode, uint256(uint160(_mainModule))); 21 | assembly { _contract := create2(callvalue(), add(code, 32), mload(code), _salt) } 22 | if (_contract == address(0)) revert DeployFailed(_mainModule, _salt); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /contracts/Wallet.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | /** 5 | Minimal upgradeable proxy implementation, delegates all calls to the address 6 | defined by the storage slot matching the wallet address. 7 | 8 | Inspired by EIP-1167 Implementation (https://eips.ethereum.org/EIPS/eip-1167) 9 | 10 | deployed code: 11 | 12 | 0x00 0x36 0x36 CALLDATASIZE cds 13 | 0x01 0x3d 0x3d RETURNDATASIZE 0 cds 14 | 0x02 0x3d 0x3d RETURNDATASIZE 0 0 cds 15 | 0x03 0x37 0x37 CALLDATACOPY 16 | 0x04 0x3d 0x3d RETURNDATASIZE 0 17 | 0x05 0x3d 0x3d RETURNDATASIZE 0 0 18 | 0x06 0x3d 0x3d RETURNDATASIZE 0 0 0 19 | 0x07 0x36 0x36 CALLDATASIZE cds 0 0 0 20 | 0x08 0x3d 0x3d RETURNDATASIZE 0 cds 0 0 0 21 | 0x09 0x30 0x30 ADDRESS addr 0 cds 0 0 0 22 | 0x0A 0x54 0x54 SLOAD imp 0 cds 0 0 0 23 | 0x0B 0x5a 0x5a GAS gas imp 0 cds 0 0 0 24 | 0x0C 0xf4 0xf4 DELEGATECALL suc 0 25 | 0x0D 0x3d 0x3d RETURNDATASIZE rds suc 0 26 | 0x0E 0x82 0x82 DUP3 0 rds suc 0 27 | 0x0F 0x80 0x80 DUP1 0 0 rds suc 0 28 | 0x10 0x3e 0x3e RETURNDATACOPY suc 0 29 | 0x11 0x90 0x90 SWAP1 0 suc 30 | 0x12 0x3d 0x3d RETURNDATASIZE rds 0 suc 31 | 0x13 0x91 0x91 SWAP2 suc 0 rds 32 | 0x14 0x60 0x18 0x6018 PUSH1 0x18 suc 0 rds 33 | /-- 0x16 0x57 0x57 JUMPI 0 rds 34 | | 0x17 0xfd 0xfd REVERT 35 | \-> 0x18 0x5b 0x5b JUMPDEST 0 rds 36 | 0x19 0xf3 0xf3 RETURN 37 | 38 | flat deployed code: 0x363d3d373d3d3d363d30545af43d82803e903d91601857fd5bf3 39 | 40 | deploy function: 41 | 42 | 0x00 0x60 0x3a 0x603a PUSH1 0x3a 43 | 0x02 0x60 0x0e 0x600e PUSH1 0x0e 0x3a 44 | 0x04 0x3d 0x3d RETURNDATASIZE 0 0x0e 0x3a 45 | 0x05 0x39 0x39 CODECOPY 46 | 0x06 0x60 0x1a 0x601a PUSH1 0x1a 47 | 0x08 0x80 0x80 DUP1 0x1a 0x1a 48 | 0x09 0x51 0x51 MLOAD imp 0x1a 49 | 0x0A 0x30 0x30 ADDRESS addr imp 0x1a 50 | 0x0B 0x55 0x55 SSTORE 0x1a 51 | 0x0C 0x3d 0x3d RETURNDATASIZE 0 0x1a 52 | 0x0D 0xf3 0xf3 RETURN 53 | [...deployed code] 54 | 55 | flat deploy function: 0x603a600e3d39601a805130553df3363d3d373d3d3d363d30545af43d82803e903d91601857fd5bf3 56 | */ 57 | library Wallet { 58 | bytes internal constant creationCode = hex"603a600e3d39601a805130553df3363d3d373d3d3d363d30545af43d82803e903d91601857fd5bf3"; 59 | } 60 | -------------------------------------------------------------------------------- /contracts/hooks/WalletProxyHook.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | import {IWalletProxy} from './interfaces/IWalletProxy.sol'; 5 | import {Implementation} from '../modules/commons/Implementation.sol'; 6 | 7 | contract WalletProxyHook is IWalletProxy, Implementation { 8 | /// @inheritdoc IWalletProxy 9 | function PROXY_getImplementation() public view returns (address) { 10 | return _getImplementation(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /contracts/hooks/interfaces/IWalletProxy.sol: -------------------------------------------------------------------------------- 1 | // Copyright Immutable Pty Ltd 2018 - 2023 2 | // SPDX-License-Identifier: Apache 2.0 3 | // https://github.com/immutable/contracts/blob/a04f7ecb8a79ad8f1b67f73f770e0545deb6cba2/contracts/allowlist/IWalletProxy.sol 4 | pragma solidity 0.8.18; 5 | 6 | // Interface to retrieve the implemention stored inside the Proxy contract 7 | /// Interface for Passport Wallet's proxy contract. 8 | interface IWalletProxy { 9 | // Returns the current implementation address used by the proxy contract 10 | // solhint-disable-next-line func-name-mixedcase 11 | function PROXY_getImplementation() external view returns (address); 12 | } 13 | -------------------------------------------------------------------------------- /contracts/interfaces/IERC1271Wallet.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | 5 | interface IERC1271Wallet { 6 | 7 | /** 8 | * @notice Verifies whether the provided signature is valid with respect to the provided data 9 | * @dev MUST return the correct magic value if the signature provided is valid for the provided data 10 | * > The bytes4 magic value to return when signature is valid is 0x20c13b0b : bytes4(keccak256("isValidSignature(bytes,bytes)") 11 | * > This function MAY modify Ethereum's state 12 | * @param _data Arbitrary length data signed on the behalf of address(this) 13 | * @param _signature Signature byte array associated with _data 14 | * @return magicValue Magic value 0x20c13b0b if the signature is valid and 0x0 otherwise 15 | */ 16 | function isValidSignature( 17 | bytes calldata _data, 18 | bytes calldata _signature) 19 | external 20 | view 21 | returns (bytes4 magicValue); 22 | 23 | /** 24 | * @notice Verifies whether the provided signature is valid with respect to the provided hash 25 | * @dev MUST return the correct magic value if the signature provided is valid for the provided hash 26 | * > The bytes4 magic value to return when signature is valid is 0x20c13b0b : bytes4(keccak256("isValidSignature(bytes,bytes)") 27 | * > This function MAY modify Ethereum's state 28 | * @param _hash keccak256 hash that was signed 29 | * @param _signature Signature byte array associated with _data 30 | * @return magicValue Magic value 0x20c13b0b if the signature is valid and 0x0 otherwise 31 | */ 32 | function isValidSignature( 33 | bytes32 _hash, 34 | bytes calldata _signature) 35 | external 36 | view 37 | returns (bytes4 magicValue); 38 | } -------------------------------------------------------------------------------- /contracts/interfaces/receivers/IERC1155Receiver.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | 5 | interface IERC1155Receiver { 6 | function onERC1155Received(address, address, uint256, uint256, bytes calldata) external returns (bytes4); 7 | function onERC1155BatchReceived(address, address, uint256[] calldata, uint256[] calldata, bytes calldata) external returns (bytes4); 8 | } 9 | -------------------------------------------------------------------------------- /contracts/interfaces/receivers/IERC223Receiver.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | 5 | interface IERC223Receiver { 6 | function tokenFallback(address, uint256, bytes calldata) external; 7 | } 8 | -------------------------------------------------------------------------------- /contracts/interfaces/receivers/IERC721Receiver.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | 5 | interface IERC721Receiver { 6 | function onERC721Received(address, address, uint256, bytes calldata) external returns (bytes4); 7 | } 8 | -------------------------------------------------------------------------------- /contracts/interfaces/receivers/IERC777Receiver.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | interface IERC777Receiver { 5 | function tokensReceived(address, address, address, uint256, bytes calldata, bytes calldata) external; 6 | } 7 | -------------------------------------------------------------------------------- /contracts/interfaces/tokens/IERC1155.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.18; 3 | 4 | interface IERC1155 { 5 | function balanceOf(address account, uint256 id) external view returns (uint256); 6 | function balanceOfBatch(address[] calldata accounts, uint256[] calldata ids) external view returns (uint256[] memory); 7 | function setApprovalForAll(address operator, bool approved) external; 8 | function isApprovedForAll(address account, address operator) external view returns (bool); 9 | function safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes calldata data) external; 10 | function safeBatchTransferFrom( 11 | address from, 12 | address to, 13 | uint256[] calldata ids, 14 | uint256[] calldata amounts, 15 | bytes calldata data 16 | ) external; 17 | 18 | event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value); 19 | event TransferBatch(address indexed operator, address indexed from, address indexed to, uint256[] ids, uint256[] values); 20 | event ApprovalForAll(address indexed account, address indexed operator, bool approved); 21 | event URI(string value, uint256 indexed id); 22 | } 23 | -------------------------------------------------------------------------------- /contracts/interfaces/tokens/IERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.18; 3 | 4 | interface IERC20 { 5 | function totalSupply() external view returns (uint256); 6 | function balanceOf(address account) external view returns (uint256); 7 | function transfer(address recipient, uint256 amount) external returns (bool); 8 | function allowance(address owner, address spender) external view returns (uint256); 9 | function approve(address spender, uint256 amount) external returns (bool); 10 | function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); 11 | 12 | event Transfer(address indexed from, address indexed to, uint256 value); 13 | event Approval(address indexed owner, address indexed spender, uint256 value); 14 | } 15 | -------------------------------------------------------------------------------- /contracts/interfaces/tokens/IERC721.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.18; 3 | 4 | interface IERC721 { 5 | function balanceOf(address owner) external view returns (uint256 balance); 6 | function ownerOf(uint256 tokenId) external view returns (address owner); 7 | function safeTransferFrom(address from, address to, uint256 tokenId) external; 8 | function transferFrom(address from, address to, uint256 tokenId) external; 9 | function approve(address to, uint256 tokenId) external; 10 | function getApproved(uint256 tokenId) external view returns (address operator); 11 | function setApprovalForAll(address operator, bool approved) external; 12 | function isApprovedForAll(address owner, address operator) external view returns (bool); 13 | function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external; 14 | 15 | event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); 16 | event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); 17 | event ApprovalForAll(address indexed owner, address indexed operator, bool approved); 18 | } 19 | -------------------------------------------------------------------------------- /contracts/mocks/AlwaysRevertMock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | 5 | contract AlwaysRevertMock { 6 | fallback() external payable { 7 | revert("AlwaysRevertMock#fallback: ALWAYS_REVERT"); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /contracts/mocks/CallReceiverMock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | 5 | contract CallReceiverMock { 6 | uint256 public lastValA; 7 | bytes public lastValB; 8 | 9 | bool revertFlag; 10 | 11 | constructor() payable { } 12 | 13 | function setRevertFlag(bool _revertFlag) external { 14 | revertFlag = _revertFlag; 15 | } 16 | 17 | function testCall(uint256 _valA, bytes calldata _valB) external payable { 18 | require(!revertFlag, "CallReceiverMock#testCall: REVERT_FLAG"); 19 | 20 | lastValA = _valA; 21 | lastValB = _valB; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /contracts/mocks/DelegateCallMock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | 5 | contract DelegateCallMock { 6 | event Readed(uint256 _val); 7 | 8 | uint256 private constant REVERT_SLOT = uint256(keccak256("revert-flag")); 9 | 10 | mapping(uint256 => uint256) private store; 11 | 12 | function setRevertFlag(bool _revertFlag) external { 13 | store[REVERT_SLOT] = _revertFlag ? 1 : 0; 14 | } 15 | 16 | function write(uint256 _key, uint256 _val) external { 17 | require(store[REVERT_SLOT] == 0, "DelegateCallMock#write: REVERT_FLAG"); 18 | store[_key] = _val; 19 | } 20 | 21 | function read(uint256 _key) external { 22 | emit Readed(store[_key]); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /contracts/mocks/ERC1155Mock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.18; 3 | 4 | contract ERC1155Mock { 5 | string public name = 'Mock ERC1155 Token'; 6 | string public symbol = 'MERC1155'; 7 | address public owner; 8 | 9 | mapping(uint256 => mapping(address => uint256)) public balances; 10 | mapping(address => mapping(address => bool)) public operatorApprovals; 11 | 12 | constructor() { 13 | owner = msg.sender; 14 | } 15 | 16 | modifier onlyOwner() { 17 | require(msg.sender == owner, 'Only owner can mint'); 18 | _; 19 | } 20 | 21 | function balanceOf(address account, uint256 id) public view returns (uint256) { 22 | return balances[id][account]; 23 | } 24 | 25 | function balanceOfBatch(address[] memory accounts, uint256[] memory ids) public view returns (uint256[] memory) { 26 | require(accounts.length == ids.length, 'Accounts and ids length mismatch'); 27 | 28 | uint256[] memory batchBalances = new uint256[](accounts.length); 29 | for (uint256 i = 0; i < accounts.length; ++i) { 30 | batchBalances[i] = balances[ids[i]][accounts[i]]; 31 | } 32 | return batchBalances; 33 | } 34 | 35 | function mint(address to, uint256 id, uint256 amount) public onlyOwner { 36 | require(to != address(0), 'Cannot mint to zero address'); 37 | 38 | balances[id][to] += amount; 39 | emit TransferSingle(msg.sender, address(0), to, id, amount); 40 | } 41 | 42 | function safeTransferFrom(address from, address to, uint256 id, uint256 amount) public { 43 | require(from == msg.sender || isApprovedForAll(from, msg.sender), 'Not approved to transfer'); 44 | 45 | require(balances[id][from] >= amount, 'Insufficient balance'); 46 | balances[id][from] -= amount; 47 | balances[id][to] += amount; 48 | 49 | emit TransferSingle(msg.sender, from, to, id, amount); 50 | } 51 | 52 | function setApprovalForAll(address operator, bool approved) public { 53 | operatorApprovals[msg.sender][operator] = approved; 54 | emit ApprovalForAll(msg.sender, operator, approved); 55 | } 56 | 57 | function isApprovedForAll(address account, address operator) public view returns (bool) { 58 | return operatorApprovals[account][operator]; 59 | } 60 | 61 | event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value); 62 | event ApprovalForAll(address indexed account, address indexed operator, bool approved); 63 | } 64 | -------------------------------------------------------------------------------- /contracts/mocks/ERC165CheckerMock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | 5 | contract ERC165CheckerMock { 6 | bytes4 constant InvalidID = 0xffffffff; 7 | bytes4 constant ERC165ID = 0x01ffc9a7; 8 | 9 | function doesContractImplementInterface(address _contract, bytes4 _interfaceId) external view returns (bool) { 10 | uint256 success; 11 | uint256 result; 12 | 13 | (success, result) = noThrowCall(_contract, ERC165ID); 14 | if (success == 0 || result == 0) { 15 | return false; 16 | } 17 | 18 | (success, result) = noThrowCall(_contract, InvalidID); 19 | if (success == 0 || result != 0) { 20 | return false; 21 | } 22 | 23 | (success, result) = noThrowCall(_contract, _interfaceId); 24 | if (success == 1 && result == 1) { 25 | return true; 26 | } 27 | return false; 28 | } 29 | 30 | function noThrowCall( 31 | address _contract, 32 | bytes4 _interfaceId 33 | ) private view returns ( 34 | uint256 success, 35 | uint256 result 36 | ) { 37 | bytes4 erc165ID = ERC165ID; 38 | 39 | assembly { 40 | let x := mload(0x40) // Find empty storage location using "free memory pointer" 41 | mstore(x, erc165ID) // Place signature at beginning of empty storage 42 | mstore(add(x, 0x04), _interfaceId) // Place first argument directly next to signature 43 | 44 | success := staticcall( 45 | 30000, // 30k gas 46 | _contract, // To addr 47 | x, // Inputs are stored at location x 48 | 0x24, // Inputs are 36 bytes long 49 | x, // Store output over input (saves space) 50 | 0x20 // Outputs are 32 bytes long 51 | ) 52 | 53 | result := mload(x) // Load the result 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /contracts/mocks/ERC20Mock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.18; 3 | 4 | contract ERC20Mock { 5 | string public name = 'Mock ERC20 Token'; 6 | string public symbol = 'MERC20'; 7 | uint8 public decimals = 18; 8 | uint256 public totalSupply; 9 | mapping(address => uint256) public balances; 10 | mapping(address => mapping(address => uint256)) public allowances; 11 | 12 | constructor(uint256 initialSupply) { 13 | totalSupply = initialSupply; 14 | balances[msg.sender] = initialSupply; 15 | } 16 | 17 | function balanceOf(address account) public view returns (uint256) { 18 | return balances[account]; 19 | } 20 | 21 | function transfer(address recipient, uint256 amount) public returns (bool) { 22 | require(balances[msg.sender] >= amount, 'Insufficient balance'); 23 | balances[msg.sender] -= amount; 24 | balances[recipient] += amount; 25 | emit Transfer(msg.sender, recipient, amount); 26 | return true; 27 | } 28 | 29 | function allowance(address owner, address spender) public view returns (uint256) { 30 | return allowances[owner][spender]; 31 | } 32 | 33 | function approve(address spender, uint256 amount) public returns (bool) { 34 | allowances[msg.sender][spender] = amount; 35 | emit Approval(msg.sender, spender, amount); 36 | return true; 37 | } 38 | 39 | function transferFrom(address sender, address recipient, uint256 amount) public returns (bool) { 40 | require(balances[sender] >= amount, 'Insufficient balance'); 41 | require(allowances[sender][msg.sender] >= amount, 'Allowance exceeded'); 42 | balances[sender] -= amount; 43 | balances[recipient] += amount; 44 | allowances[sender][msg.sender] -= amount; 45 | emit Transfer(sender, recipient, amount); 46 | return true; 47 | } 48 | 49 | event Transfer(address indexed from, address indexed to, uint256 value); 50 | event Approval(address indexed owner, address indexed spender, uint256 value); 51 | } 52 | -------------------------------------------------------------------------------- /contracts/mocks/ERC721Mock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.18; 3 | 4 | contract ERC721Mock { 5 | string public name = 'Mock ERC721 Token'; 6 | string public symbol = 'MERC721'; 7 | uint256 public totalSupply; 8 | address public owner; 9 | 10 | mapping(address => uint256) public balances; 11 | mapping(uint256 => address) public owners; 12 | mapping(address => mapping(address => bool)) public operatorApprovals; 13 | mapping(uint256 => address) public tokenApprovals; 14 | 15 | constructor() { 16 | owner = msg.sender; 17 | } 18 | 19 | modifier onlyOwner() { 20 | require(msg.sender == owner, 'Only owner can mint'); 21 | _; 22 | } 23 | 24 | function balanceOf(address _owner) public view returns (uint256) { 25 | return balances[_owner]; 26 | } 27 | 28 | function ownerOf(uint256 tokenId) public view returns (address) { 29 | address tokenOwner = owners[tokenId]; 30 | require(tokenOwner != address(0), 'Token does not exist'); 31 | return tokenOwner; 32 | } 33 | 34 | function mint(address to, uint256 tokenId) public onlyOwner { 35 | require(to != address(0), 'Cannot mint to zero address'); 36 | require(owners[tokenId] == address(0), 'Token already minted'); 37 | 38 | owners[tokenId] = to; 39 | balances[to] += 1; 40 | totalSupply += 1; 41 | 42 | emit Transfer(address(0), to, tokenId); 43 | } 44 | 45 | function transferFrom(address from, address to, uint256 tokenId) public { 46 | require(ownerOf(tokenId) == from, 'Not the owner of the token'); 47 | require(to != address(0), 'Cannot transfer to zero address'); 48 | 49 | require( 50 | msg.sender == from || getApproved(tokenId) == msg.sender || isApprovedForAll(from, msg.sender), 51 | 'Not approved to transfer' 52 | ); 53 | 54 | balances[from] -= 1; 55 | balances[to] += 1; 56 | owners[tokenId] = to; 57 | 58 | emit Transfer(from, to, tokenId); 59 | } 60 | 61 | function approve(address to, uint256 tokenId) public { 62 | address tokenOwner = ownerOf(tokenId); 63 | require(to != tokenOwner, 'Cannot approve current owner'); 64 | require(msg.sender == tokenOwner || isApprovedForAll(tokenOwner, msg.sender), 'Not approved'); 65 | 66 | tokenApprovals[tokenId] = to; 67 | emit Approval(tokenOwner, to, tokenId); 68 | } 69 | 70 | function getApproved(uint256 tokenId) public view returns (address) { 71 | return tokenApprovals[tokenId]; 72 | } 73 | 74 | function setApprovalForAll(address operator, bool approved) public { 75 | operatorApprovals[msg.sender][operator] = approved; 76 | emit ApprovalForAll(msg.sender, operator, approved); 77 | } 78 | 79 | function isApprovedForAll(address _owner, address operator) public view returns (bool) { 80 | return operatorApprovals[_owner][operator]; 81 | } 82 | 83 | event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); 84 | event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); 85 | event ApprovalForAll(address indexed owner, address indexed operator, bool approved); 86 | } 87 | -------------------------------------------------------------------------------- /contracts/mocks/GasBurnerMock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | 5 | contract GasBurnerMock { 6 | event ProvidedGas(uint256 _val); 7 | 8 | function burnGas(uint256 _burn) external { 9 | emit ProvidedGas(gasleft()); 10 | 11 | bytes32 stub; 12 | uint256 initial = gasleft(); 13 | 14 | while (initial - gasleft() < _burn) { 15 | stub = keccak256(abi.encode(stub)); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /contracts/mocks/HookCallerMock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | import "../interfaces/receivers/IERC1155Receiver.sol"; 5 | import "../interfaces/receivers/IERC721Receiver.sol"; 6 | import "../interfaces/receivers/IERC223Receiver.sol"; 7 | 8 | import "../interfaces/IERC1271Wallet.sol"; 9 | 10 | 11 | contract HookCallerMock { 12 | function callERC1155Received(address _addr) external { 13 | bytes4 result = IERC1155Receiver(_addr).onERC1155Received( 14 | address(this), 15 | msg.sender, 16 | 1, 17 | 2, 18 | msg.data 19 | ); 20 | 21 | require(result == 0xf23a6e61, "HookCallerMock#callERC1155Received: INVALID_RETURN"); 22 | } 23 | 24 | function callERC1155BatchReceived(address _addr) external { 25 | uint256[] memory ids = new uint256[](3); 26 | ids[0] = 1; 27 | ids[1] = 2; 28 | ids[2] = 3; 29 | 30 | uint256[] memory values = new uint256[](3); 31 | values[0] = 200; 32 | values[1] = 300; 33 | values[2] = 400; 34 | 35 | bytes4 result = IERC1155Receiver(_addr).onERC1155BatchReceived( 36 | address(this), 37 | msg.sender, 38 | ids, 39 | values, 40 | msg.data 41 | ); 42 | 43 | require(result == 0xbc197c81, "HookCallerMock#callERC1155BatchReceived: INVALID_RETURN"); 44 | } 45 | 46 | function callERC721Received(address _addr) external { 47 | bytes4 result = IERC721Receiver(_addr).onERC721Received( 48 | address(this), 49 | msg.sender, 50 | 1, 51 | msg.data 52 | ); 53 | 54 | require(result == 0x150b7a02, "HookCallerMock#callERC721Received: INVALID_RETURN"); 55 | } 56 | 57 | function callERC223Received(address _addr) external { 58 | IERC223Receiver(_addr).tokenFallback(msg.sender, 1, msg.data); 59 | } 60 | 61 | function callERC1271isValidSignatureData( 62 | address _addr, 63 | bytes calldata _data, 64 | bytes calldata _signature 65 | ) external view { 66 | bytes4 result = IERC1271Wallet(_addr).isValidSignature(_data, _signature); 67 | require(result == 0x20c13b0b, "HookCallerMock#callERC1271isValidSignatureData: INVALID_RETURN"); 68 | } 69 | 70 | function callERC1271isValidSignatureHash( 71 | address _addr, 72 | bytes32 _hash, 73 | bytes calldata _signature 74 | ) external view { 75 | bytes4 result = IERC1271Wallet(_addr).isValidSignature(_hash, _signature); 76 | require(result == 0x1626ba7e, "HookCallerMock#callERC1271isValidSignatureHash: INVALID_RETURN"); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /contracts/mocks/HookMock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | 5 | contract HookMock { 6 | function onHookMockCall(uint256 _num) external pure returns (uint256) { 7 | return _num * 2; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /contracts/mocks/LibBytesImpl.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | import "../utils/LibBytes.sol"; 5 | 6 | 7 | contract LibBytesImpl { 8 | using LibBytes for bytes; 9 | 10 | function readBytes32( 11 | bytes calldata data, 12 | uint256 index 13 | ) external pure returns ( 14 | bytes32 a 15 | ) { 16 | return LibBytes.readBytes32(data, index); 17 | } 18 | 19 | function readUint8( 20 | bytes calldata data, 21 | uint256 index 22 | ) external pure returns ( 23 | uint8 a 24 | ) { 25 | return LibBytes.readUint8(data, index); 26 | } 27 | 28 | function readUint32( 29 | bytes calldata data, 30 | uint256 index 31 | ) external pure returns ( 32 | uint32 a 33 | ) { 34 | return LibBytes.readUint32(data, index); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /contracts/mocks/LibBytesPointerImpl.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | import "../utils/LibBytesPointer.sol"; 5 | 6 | 7 | contract LibBytesPointerImpl { 8 | using LibBytesPointer for bytes; 9 | 10 | function readFirstUint16( 11 | bytes calldata data 12 | ) external pure returns ( 13 | uint16 a, 14 | uint256 newPointer 15 | ) { 16 | return LibBytesPointer.readFirstUint16(data); 17 | } 18 | 19 | function readUint16( 20 | bytes calldata data, 21 | uint256 index 22 | ) external pure returns ( 23 | uint16 a, 24 | uint256 newPointer 25 | ) { 26 | return LibBytesPointer.readUint16(data, index); 27 | } 28 | 29 | function readUint24( 30 | bytes calldata data, 31 | uint256 index 32 | ) external pure returns ( 33 | uint24 a, 34 | uint256 newPointer 35 | ) { 36 | return LibBytesPointer.readUint24(data, index); 37 | } 38 | 39 | function readUint64( 40 | bytes calldata data, 41 | uint256 index 42 | ) external pure returns ( 43 | uint64 a, 44 | uint256 newPointer 45 | ) { 46 | return LibBytesPointer.readUint64(data, index); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /contracts/mocks/LibStringImp.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | import "../utils/LibString.sol"; 5 | 6 | 7 | contract LibStringImp { 8 | using LibString for string; 9 | 10 | function prefixBase32(string calldata data) external pure returns (string memory) { 11 | return LibString.prefixBase32(data); 12 | } 13 | 14 | function prefixHexadecimal(string calldata data) external pure returns (string memory) { 15 | return LibString.prefixHexadecimal(data); 16 | } 17 | 18 | function bytesToBase32(bytes calldata data) external pure returns (string memory) { 19 | return LibString.bytesToBase32(data); 20 | } 21 | 22 | function bytesToHexadecimal(bytes calldata data) external pure returns (string memory) { 23 | return LibString.bytesToHexadecimal(data); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /contracts/mocks/ModuleMock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | 5 | contract ModuleMock { 6 | event Pong(); 7 | 8 | function ping() external { 9 | emit Pong(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /contracts/modules/GuestModule.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | import "../utils/LibOptim.sol"; 5 | 6 | import "./commons/submodules/auth/SequenceBaseSig.sol"; 7 | 8 | import "./commons/ModuleAuth.sol"; 9 | import "./commons/ModuleCalls.sol"; 10 | import "./commons/ModuleCreator.sol"; 11 | 12 | 13 | /** 14 | * GuestModule implements a Sequence wallet without signatures, nonce or replay protection. 15 | * executing transactions using this wallet is not an authenticated process, and can be done by any address. 16 | * 17 | * @notice This contract is completely public with no security, designed to execute pre-signed transactions 18 | * and use Sequence tools without using the wallets. 19 | */ 20 | contract GuestModule is 21 | ModuleAuth, 22 | ModuleCalls, 23 | ModuleCreator 24 | { 25 | error DelegateCallNotAllowed(uint256 _index); 26 | error NotSupported(); 27 | 28 | /** 29 | * @notice Allow any caller to execute an action 30 | * @param _txs Transactions to process 31 | */ 32 | function execute( 33 | Transaction[] calldata _txs, 34 | uint256, 35 | bytes calldata 36 | ) public override { 37 | // Hash transaction bundle 38 | bytes32 txHash = SequenceBaseSig.subdigest(keccak256(abi.encode('guest:', _txs))); 39 | 40 | // Execute the transactions 41 | _executeGuest(txHash, _txs); 42 | } 43 | 44 | /** 45 | * @notice Allow any caller to execute an action 46 | * @param _txs Transactions to process 47 | */ 48 | function selfExecute( 49 | Transaction[] calldata _txs 50 | ) public override { 51 | // Hash transaction bundle 52 | bytes32 txHash = SequenceBaseSig.subdigest(keccak256(abi.encode('self:', _txs))); 53 | 54 | // Execute the transactions 55 | _executeGuest(txHash, _txs); 56 | } 57 | 58 | /** 59 | * @notice Executes a list of transactions 60 | * @param _txHash Hash of the batch of transactions 61 | * @param _txs Transactions to execute 62 | */ 63 | function _executeGuest( 64 | bytes32 _txHash, 65 | Transaction[] calldata _txs 66 | ) private { 67 | // Execute transaction 68 | uint256 size = _txs.length; 69 | for (uint256 i = 0; i < size; i++) { 70 | Transaction calldata transaction = _txs[i]; 71 | 72 | if (transaction.delegateCall) revert DelegateCallNotAllowed(i); 73 | 74 | uint256 gasLimit = transaction.gasLimit; 75 | if (gasleft() < gasLimit) revert NotEnoughGas(i, gasLimit, gasleft()); 76 | 77 | bool success = LibOptim.call( 78 | transaction.target, 79 | transaction.value, 80 | gasLimit == 0 ? gasleft() : gasLimit, 81 | transaction.data 82 | ); 83 | 84 | if (success) { 85 | emit TxExecuted(_txHash, i); 86 | } else { 87 | _revertBytes( 88 | transaction.revertOnError, 89 | _txHash, 90 | i, 91 | LibOptim.returnData() 92 | ); 93 | } 94 | } 95 | } 96 | 97 | /** 98 | * @notice Validates any signature image, because the wallet is public and has no owner. 99 | * @return true, all signatures are valid. 100 | */ 101 | function _isValidImage(bytes32) internal override pure returns (bool) { 102 | return true; 103 | } 104 | 105 | /** 106 | * Not supported. 107 | */ 108 | function _updateImageHash(bytes32) internal override virtual { 109 | revert NotSupported(); 110 | } 111 | 112 | /** 113 | * @notice Query if a contract implements an interface 114 | * @param _interfaceID The interface identifier, as specified in ERC-165 115 | * @return `true` if the contract implements `_interfaceID` 116 | */ 117 | function supportsInterface( 118 | bytes4 _interfaceID 119 | ) public override ( 120 | ModuleAuth, 121 | ModuleCalls, 122 | ModuleCreator 123 | ) pure returns (bool) { 124 | return super.supportsInterface(_interfaceID); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /contracts/modules/MainModule.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | import "./commons/ModuleAuthFixed.sol"; 5 | import "./commons/ModuleHooks.sol"; 6 | import "./commons/ModuleCalls.sol"; 7 | import "./commons/ModuleCreator.sol"; 8 | import "./commons/ModuleExtraAuth.sol"; 9 | import "./commons/ModuleAuthConvenience.sol"; 10 | 11 | 12 | /** 13 | * @notice Contains the core functionality Sequence wallets will inherit. 14 | * @dev If using a new main module, developers must ensure that all inherited 15 | * contracts by the main module don't conflict and are accounted for to be 16 | * supported by the supportsInterface method. 17 | */ 18 | contract MainModule is 19 | ModuleAuthFixed, 20 | ModuleExtraAuth, 21 | ModuleCalls, 22 | ModuleHooks, 23 | ModuleCreator, 24 | ModuleAuthConvenience 25 | { 26 | constructor( 27 | address _factory, 28 | address _mainModuleUpgradable 29 | ) ModuleAuthFixed( 30 | _factory, 31 | _mainModuleUpgradable 32 | ) { } 33 | 34 | function _isValidImage( 35 | bytes32 _imageHash 36 | ) internal override( 37 | IModuleAuth, 38 | ModuleAuthFixed, 39 | ModuleExtraAuth 40 | ) view returns (bool) { 41 | return super._isValidImage(_imageHash); 42 | } 43 | 44 | /** 45 | * @notice Query if a contract implements an interface 46 | * @param _interfaceID The interface identifier, as specified in ERC-165 47 | * @return `true` if the contract implements `_interfaceID` 48 | */ 49 | function supportsInterface( 50 | bytes4 _interfaceID 51 | ) public override( 52 | ModuleAuthFixed, 53 | ModuleAuthConvenience, 54 | ModuleCalls, 55 | ModuleExtraAuth, 56 | ModuleHooks, 57 | ModuleCreator 58 | ) pure returns (bool) { 59 | return super.supportsInterface(_interfaceID); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /contracts/modules/MainModuleGasEstimation.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | import "./commons/gas-estimation/ModuleIgnoreAuthUpgradable.sol"; 5 | import "./commons/gas-estimation/ModuleIgnoreNonceCalls.sol"; 6 | import "./commons/ModuleHooks.sol"; 7 | import "./commons/ModuleUpdate.sol"; 8 | import "./commons/ModuleCreator.sol"; 9 | 10 | 11 | /** 12 | * @notice Contains an alternative implementation of the MainModules that skips validation of 13 | * signatures, this implementation SHOULD NOT be used directly on a wallet. 14 | * 15 | * Intended to be used only for gas estimation, using eth_call and overrides. 16 | */ 17 | contract MainModuleGasEstimation is 18 | ModuleIgnoreAuthUpgradable, 19 | ModuleIgnoreNonceCalls, 20 | ModuleUpdate, 21 | ModuleHooks, 22 | ModuleCreator 23 | { 24 | struct SimulateResult { 25 | bool executed; 26 | bool succeeded; 27 | bytes result; 28 | uint256 gasUsed; 29 | } 30 | 31 | /** 32 | * @notice Simulate each transaction in a bundle for gas usage and execution result 33 | * @param _txs Transactions to process 34 | * @return The gas used and execution result for each transaction in the bundle 35 | */ 36 | function simulateExecute(Transaction[] calldata _txs) public virtual returns (SimulateResult[] memory) { 37 | unchecked { 38 | SimulateResult[] memory results = new SimulateResult[](_txs.length); 39 | 40 | // Execute transaction 41 | uint256 size = _txs.length; 42 | for (uint256 i = 0; i < size; i++) { 43 | Transaction calldata transaction = _txs[i]; 44 | uint256 gasLimit = transaction.gasLimit; 45 | 46 | results[i].executed = true; 47 | 48 | if (gasleft() < gasLimit) { 49 | results[i].succeeded = false; 50 | results[i].result = abi.encodeWithSelector(IModuleCalls.NotEnoughGas.selector, i, gasLimit, gasleft()); 51 | break; 52 | } 53 | 54 | if (transaction.delegateCall) { 55 | uint256 initialGas = gasleft(); 56 | 57 | (results[i].succeeded, results[i].result) = transaction.target.delegatecall{ 58 | gas: gasLimit == 0 ? gasleft() : gasLimit 59 | }(transaction.data); 60 | 61 | results[i].gasUsed = initialGas - gasleft(); 62 | } else { 63 | uint256 initialGas = gasleft(); 64 | 65 | (results[i].succeeded, results[i].result) = transaction.target.call{ 66 | value: transaction.value, 67 | gas: gasLimit == 0 ? gasleft() : gasLimit 68 | }(transaction.data); 69 | 70 | results[i].gasUsed = initialGas - gasleft(); 71 | } 72 | 73 | if (!results[i].succeeded && transaction.revertOnError) { 74 | break; 75 | } 76 | } 77 | 78 | return results; 79 | } 80 | } 81 | 82 | function _isValidImage(bytes32 _imageHash) internal override( 83 | IModuleAuth, 84 | ModuleIgnoreAuthUpgradable 85 | ) view returns (bool) { 86 | return super._isValidImage(_imageHash); 87 | } 88 | 89 | /** 90 | * @notice Query if a contract implements an interface 91 | * @param _interfaceID The interface identifier, as specified in ERC-165 92 | * @dev If using a new main module, developers must ensure that all inherited 93 | * contracts by the main module don't conflict and are accounted for to be 94 | * supported by the supportsInterface method. 95 | * @return `true` if the contract implements `_interfaceID` 96 | */ 97 | function supportsInterface( 98 | bytes4 _interfaceID 99 | ) public override( 100 | ModuleAuthUpgradable, 101 | ModuleCalls, 102 | ModuleUpdate, 103 | ModuleHooks, 104 | ModuleCreator 105 | ) pure returns (bool) { 106 | return super.supportsInterface(_interfaceID); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /contracts/modules/MainModuleUpgradable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | import "./commons/ModuleAuthUpgradable.sol"; 5 | import "./commons/ModuleHooks.sol"; 6 | import "./commons/ModuleCalls.sol"; 7 | import "./commons/ModuleUpdate.sol"; 8 | import "./commons/ModuleCreator.sol"; 9 | import "./commons/ModuleExtraAuth.sol"; 10 | import "./commons/ModuleAuthConvenience.sol"; 11 | 12 | 13 | /** 14 | * @notice Contains the core functionality Sequence wallets will inherit with 15 | * the added functionality that the main module can be changed. 16 | * @dev If using a new main module, developers must ensure that all inherited 17 | * contracts by the main module don't conflict and are accounted for to be 18 | * supported by the supportsInterface method. 19 | */ 20 | contract MainModuleUpgradable is 21 | ModuleAuthUpgradable, 22 | ModuleExtraAuth, 23 | ModuleCalls, 24 | ModuleUpdate, 25 | ModuleHooks, 26 | ModuleCreator, 27 | ModuleAuthConvenience 28 | { 29 | function _isValidImage( 30 | bytes32 _imageHash 31 | ) internal override( 32 | IModuleAuth, 33 | ModuleAuthUpgradable, 34 | ModuleExtraAuth 35 | ) view returns (bool) { 36 | return super._isValidImage(_imageHash); 37 | } 38 | 39 | /** 40 | * @notice Query if a contract implements an interface 41 | * @param _interfaceID The interface identifier, as specified in ERC-165 42 | * @dev If using a new main module, developers must ensure that all inherited 43 | * contracts by the main module don't conflict and are accounted for to be 44 | * supported by the supportsInterface method. 45 | * @return `true` if the contract implements `_interfaceID` 46 | */ 47 | function supportsInterface( 48 | bytes4 _interfaceID 49 | ) public override( 50 | ModuleAuthUpgradable, 51 | ModuleAuthConvenience, 52 | ModuleCalls, 53 | ModuleExtraAuth, 54 | ModuleUpdate, 55 | ModuleHooks, 56 | ModuleCreator 57 | ) pure returns (bool) { 58 | return super.supportsInterface(_interfaceID); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /contracts/modules/commons/Implementation.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | /** 5 | * @dev Allows modules to access the implementation slot 6 | */ 7 | contract Implementation { 8 | /** 9 | * @notice Updates the Wallet implementation 10 | * @param _imp New implementation address 11 | * @dev The wallet implementation is stored on the storage slot 12 | * defined by the address of the wallet itself 13 | * WARNING updating this value may break the wallet and users 14 | * must be confident that the new implementation is safe. 15 | */ 16 | function _setImplementation(address _imp) internal { 17 | assembly { 18 | sstore(address(), _imp) 19 | } 20 | } 21 | 22 | /** 23 | * @notice Returns the Wallet implementation 24 | * @return _imp The address of the current Wallet implementation 25 | */ 26 | function _getImplementation() internal view returns (address _imp) { 27 | assembly { 28 | _imp := sload(address()) 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /contracts/modules/commons/ModuleAuthConvenience.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | import "./ModuleSelfAuth.sol"; 5 | import "./ModuleAuth.sol"; 6 | import "./ModuleIPFS.sol"; 7 | import "./ModuleERC165.sol"; 8 | 9 | import "../../utils/LibString.sol"; 10 | 11 | 12 | 13 | abstract contract ModuleAuthConvenience is ModuleERC165, ModuleSelfAuth, ModuleAuth, ModuleIPFS { 14 | 15 | /** 16 | * @notice Updates the image hash and the IPFS root in a single operation. 17 | * @dev These two operations are often performed together, so this function 18 | * allows to save some gas by performing them in a single step. 19 | * 20 | * @param _imageHash The new image hash to be set. 21 | * @param _ipfsRoot The new IPFS root to be set. 22 | */ 23 | function updateImageHashAndIPFS( 24 | bytes32 _imageHash, 25 | bytes32 _ipfsRoot 26 | ) external onlySelf { 27 | _updateImageHash(_imageHash); 28 | _updateIPFSRoot(_ipfsRoot); 29 | } 30 | 31 | /** 32 | * @notice Query if a contract implements an interface 33 | * @param _interfaceID The interface identifier, as specified in ERC-165 34 | * @return `true` if the contract implements `_interfaceID` 35 | */ 36 | function supportsInterface(bytes4 _interfaceID) public override ( 37 | ModuleERC165, 38 | ModuleAuth 39 | ) virtual pure returns (bool) { 40 | if (_interfaceID == type(ModuleAuthConvenience).interfaceId) { 41 | return true; 42 | } 43 | 44 | return super.supportsInterface(_interfaceID); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /contracts/modules/commons/ModuleAuthFixed.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | import "./ModuleAuth.sol"; 5 | import "./ModuleUpdate.sol"; 6 | import "./ModuleSelfAuth.sol"; 7 | import "./ModuleStorage.sol"; 8 | 9 | import "../../Wallet.sol"; 10 | 11 | /** 12 | * Implements ModuleAuth by validating the signature image against 13 | * the salt used to deploy the contract 14 | * 15 | * This module allows wallets to be deployed with a default configuration 16 | * without using any aditional contract storage 17 | */ 18 | abstract contract ModuleAuthFixed is ModuleSelfAuth, ModuleAuth, ModuleUpdate { 19 | bytes32 public immutable INIT_CODE_HASH; 20 | address public immutable FACTORY; 21 | address public immutable UPGRADEABLE_IMPLEMENTATION; 22 | 23 | constructor(address _factory, address _mainModuleUpgradeable) { 24 | // Build init code hash of the deployed wallets using that module 25 | bytes32 initCodeHash = keccak256(abi.encodePacked(Wallet.creationCode, uint256(uint160(address(this))))); 26 | 27 | INIT_CODE_HASH = initCodeHash; 28 | FACTORY = _factory; 29 | UPGRADEABLE_IMPLEMENTATION = _mainModuleUpgradeable; 30 | } 31 | 32 | /** 33 | * @notice Updates the configuration of the wallet 34 | * @dev In the process of updating the configuration, the wallet implementation 35 | * is updated to the mainModuleUpgradeable, this only happens once in the 36 | * lifetime of the wallet. 37 | * 38 | * @param _imageHash New required image hash of the signature 39 | */ 40 | function _updateImageHash(bytes32 _imageHash) internal override virtual { 41 | // Update imageHash in storage 42 | if (_imageHash == bytes32(0)) revert ImageHashIsZero(); 43 | ModuleStorage.writeBytes32(IMAGE_HASH_KEY, _imageHash); 44 | emit ImageHashUpdated(_imageHash); 45 | 46 | // Update wallet implementation to mainModuleUpgradeable 47 | _updateImplementation(UPGRADEABLE_IMPLEMENTATION); 48 | } 49 | 50 | /** 51 | * @notice Validates the signature image with the salt used to deploy the contract 52 | * @param _imageHash Hash image of signature 53 | * @return true if the signature image is valid 54 | */ 55 | function _isValidImage(bytes32 _imageHash) internal override virtual view returns (bool) { 56 | return address( 57 | uint160( 58 | uint256( 59 | keccak256( 60 | abi.encodePacked( 61 | hex"ff", 62 | FACTORY, 63 | _imageHash, 64 | INIT_CODE_HASH 65 | ) 66 | ) 67 | ) 68 | ) 69 | ) == address(this); 70 | } 71 | 72 | /** 73 | * @notice Query if a contract implements an interface 74 | * @param _interfaceID The interface identifier, as specified in ERC-165 75 | * @return `true` if the contract implements `_interfaceID` 76 | */ 77 | function supportsInterface(bytes4 _interfaceID) public override(ModuleAuth, ModuleUpdate) virtual pure returns (bool) { 78 | return super.supportsInterface(_interfaceID); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /contracts/modules/commons/ModuleAuthUpgradable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | import "./interfaces/IModuleAuthUpgradable.sol"; 5 | 6 | import "./ModuleSelfAuth.sol"; 7 | import "./ModuleAuth.sol"; 8 | import "./ModuleStorage.sol"; 9 | 10 | 11 | abstract contract ModuleAuthUpgradable is IModuleAuthUpgradable, ModuleSelfAuth, ModuleAuth { 12 | /** 13 | * @notice Updates the signers configuration of the wallet 14 | * @param _imageHash New required image hash of the signature 15 | */ 16 | function _updateImageHash(bytes32 _imageHash) internal override virtual { 17 | if (_imageHash == bytes32(0)) revert ImageHashIsZero(); 18 | ModuleStorage.writeBytes32(IMAGE_HASH_KEY, _imageHash); 19 | emit ImageHashUpdated(_imageHash); 20 | } 21 | 22 | /** 23 | * @notice Returns the current image hash of the wallet 24 | */ 25 | function imageHash() external override virtual view returns (bytes32) { 26 | return ModuleStorage.readBytes32(IMAGE_HASH_KEY); 27 | } 28 | 29 | /** 30 | * @notice Validates the signature image with a valid image hash defined 31 | * in the contract storage 32 | * @param _imageHash Hash image of signature 33 | * @return true if the signature image is valid 34 | */ 35 | function _isValidImage(bytes32 _imageHash) internal override virtual view returns (bool) { 36 | return _imageHash != bytes32(0) && _imageHash == ModuleStorage.readBytes32(IMAGE_HASH_KEY); 37 | } 38 | 39 | /** 40 | * @notice Query if a contract implements an interface 41 | * @param _interfaceID The interface identifier, as specified in ERC-165 42 | * @return `true` if the contract implements `_interfaceID` 43 | */ 44 | function supportsInterface(bytes4 _interfaceID) public override virtual pure returns (bool) { 45 | if (_interfaceID == type(IModuleAuthUpgradable).interfaceId) { 46 | return true; 47 | } 48 | 49 | return super.supportsInterface(_interfaceID); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /contracts/modules/commons/ModuleCalls.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | import "./ModuleSelfAuth.sol"; 5 | import "./ModuleStorage.sol"; 6 | import "./ModuleERC165.sol"; 7 | import "./ModuleNonce.sol"; 8 | import "./ModuleOnlyDelegatecall.sol"; 9 | 10 | import "./interfaces/IModuleCalls.sol"; 11 | import "./interfaces/IModuleAuth.sol"; 12 | 13 | import "./submodules/nonce/SubModuleNonce.sol"; 14 | import "./submodules/auth/SequenceBaseSig.sol"; 15 | 16 | import "../../utils/LibOptim.sol"; 17 | 18 | 19 | abstract contract ModuleCalls is IModuleCalls, IModuleAuth, ModuleERC165, ModuleOnlyDelegatecall, ModuleSelfAuth, ModuleNonce { 20 | /** 21 | * @notice Allow wallet owner to execute an action 22 | * @dev Relayers must ensure that the gasLimit specified for each transaction 23 | * is acceptable to them. A user could specify large enough that it could 24 | * consume all the gas available. 25 | * @param _txs Transactions to process 26 | * @param _nonce Signature nonce (may contain an encoded space) 27 | * @param _signature Encoded signature 28 | */ 29 | function execute( 30 | Transaction[] calldata _txs, 31 | uint256 _nonce, 32 | bytes calldata _signature 33 | ) external override virtual onlyDelegatecall { 34 | // Validate and update nonce 35 | _validateNonce(_nonce); 36 | 37 | // Hash and verify transaction bundle 38 | (bool isValid, bytes32 txHash) = _signatureValidation( 39 | keccak256( 40 | abi.encode( 41 | _nonce, 42 | _txs 43 | ) 44 | ), 45 | _signature 46 | ); 47 | 48 | if (!isValid) { 49 | revert InvalidSignature(txHash, _signature); 50 | } 51 | 52 | // Execute the transactions 53 | _execute(txHash, _txs); 54 | } 55 | 56 | /** 57 | * @notice Allow wallet to execute an action 58 | * without signing the message 59 | * @param _txs Transactions to execute 60 | */ 61 | function selfExecute( 62 | Transaction[] calldata _txs 63 | ) external override virtual onlySelf { 64 | // Hash transaction bundle 65 | bytes32 txHash = SequenceBaseSig.subdigest( 66 | keccak256( 67 | abi.encode('self:', _txs) 68 | ) 69 | ); 70 | 71 | // Execute the transactions 72 | _execute(txHash, _txs); 73 | } 74 | 75 | /** 76 | * @notice Executes a list of transactions 77 | * @param _txHash Hash of the batch of transactions 78 | * @param _txs Transactions to execute 79 | */ 80 | function _execute( 81 | bytes32 _txHash, 82 | Transaction[] calldata _txs 83 | ) private { 84 | unchecked { 85 | // Execute transaction 86 | uint256 size = _txs.length; 87 | for (uint256 i = 0; i < size; i++) { 88 | Transaction calldata transaction = _txs[i]; 89 | uint256 gasLimit = transaction.gasLimit; 90 | 91 | if (gasleft() < gasLimit) revert NotEnoughGas(i, gasLimit, gasleft()); 92 | 93 | bool success; 94 | if (transaction.delegateCall) { 95 | success = LibOptim.delegatecall( 96 | transaction.target, 97 | gasLimit == 0 ? gasleft() : gasLimit, 98 | transaction.data 99 | ); 100 | } else { 101 | success = LibOptim.call( 102 | transaction.target, 103 | transaction.value, 104 | gasLimit == 0 ? gasleft() : gasLimit, 105 | transaction.data 106 | ); 107 | } 108 | 109 | if (success) { 110 | emit TxExecuted(_txHash, i); 111 | } else { 112 | // Avoid copy of return data until neccesary 113 | _revertBytes( 114 | transaction.revertOnError, 115 | _txHash, 116 | i, 117 | LibOptim.returnData() 118 | ); 119 | } 120 | } 121 | } 122 | } 123 | 124 | /** 125 | * @notice Logs a failed transaction, reverts if the transaction is not optional 126 | * @param _revertOnError Signals if it should revert or just log 127 | * @param _txHash Hash of the transaction 128 | * @param _index Index of the transaction in the batch 129 | * @param _reason Encoded revert message 130 | */ 131 | function _revertBytes( 132 | bool _revertOnError, 133 | bytes32 _txHash, 134 | uint256 _index, 135 | bytes memory _reason 136 | ) internal { 137 | if (_revertOnError) { 138 | assembly { revert(add(_reason, 0x20), mload(_reason)) } 139 | } else { 140 | emit TxFailed(_txHash, _index, _reason); 141 | } 142 | } 143 | 144 | /** 145 | * @notice Query if a contract implements an interface 146 | * @param _interfaceID The interface identifier, as specified in ERC-165 147 | * @return `true` if the contract implements `_interfaceID` 148 | */ 149 | function supportsInterface(bytes4 _interfaceID) public override virtual pure returns (bool) { 150 | if (_interfaceID == type(IModuleCalls).interfaceId) { 151 | return true; 152 | } 153 | 154 | return super.supportsInterface(_interfaceID); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /contracts/modules/commons/ModuleCreator.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | import "./interfaces/IModuleCreator.sol"; 5 | 6 | import "./ModuleSelfAuth.sol"; 7 | import "./ModuleERC165.sol"; 8 | 9 | 10 | contract ModuleCreator is IModuleCreator, ModuleERC165, ModuleSelfAuth { 11 | event CreatedContract(address _contract); 12 | 13 | /** 14 | * @notice Creates a contract forwarding eth value 15 | * @param _code Creation code of the contract 16 | * @return addr The address of the created contract 17 | */ 18 | function createContract(bytes memory _code) public override virtual payable onlySelf returns (address addr) { 19 | assembly { addr := create(callvalue(), add(_code, 32), mload(_code)) } 20 | if (addr == address(0)) revert CreateFailed(_code); 21 | emit CreatedContract(addr); 22 | } 23 | 24 | /** 25 | * @notice Query if a contract implements an interface 26 | * @param _interfaceID The interface identifier, as specified in ERC-165 27 | * @return `true` if the contract implements `_interfaceID` 28 | */ 29 | function supportsInterface(bytes4 _interfaceID) public override virtual pure returns (bool) { 30 | if (_interfaceID == type(IModuleCreator).interfaceId) { 31 | return true; 32 | } 33 | 34 | return super.supportsInterface(_interfaceID); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /contracts/modules/commons/ModuleERC165.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | 5 | abstract contract ModuleERC165 { 6 | /** 7 | * @notice Query if a contract implements an interface 8 | * @param _interfaceID The interface identifier, as specified in ERC-165 9 | * @dev Adding new hooks will not lead to them being reported by this function 10 | * without upgrading the wallet. In addition, developers must ensure that 11 | * all inherited contracts by the main module don't conflict and are accounted 12 | * to be supported by the supportsInterface method. 13 | * @return `true` if the contract implements `_interfaceID` 14 | */ 15 | function supportsInterface(bytes4 _interfaceID) virtual public pure returns (bool) { 16 | return _interfaceID == this.supportsInterface.selector; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /contracts/modules/commons/ModuleERC5719.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | import "./ModuleIPFS.sol"; 5 | 6 | import "../../utils/LibString.sol"; 7 | 8 | 9 | contract ModuleERC5719 is ModuleIPFS { 10 | function getAlternativeSignature(bytes32 _digest) external view returns (string memory) { 11 | return string( 12 | abi.encodePacked( 13 | ipfsRoot(), 14 | "/ERC5719/", 15 | LibString.prefixHexadecimal( 16 | LibString.bytesToHexadecimal( 17 | abi.encodePacked(_digest) 18 | ) 19 | ) 20 | ) 21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /contracts/modules/commons/ModuleExtraAuth.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | import "./ModuleAuth.sol"; 5 | import "./ModuleStorage.sol"; 6 | import "./ModuleSelfAuth.sol"; 7 | import "./ModuleERC165.sol"; 8 | 9 | 10 | abstract contract ModuleExtraAuth is ModuleERC165, ModuleSelfAuth, ModuleAuth { 11 | // EXTRA_IMAGE_HASH_KEY = keccak256("org.sequence.module.static.auth.extra.image.hash"); 12 | bytes32 private constant EXTRA_IMAGE_HASH_KEY = bytes32(0x849e7bdc245db17e50b9f43086f1914d70eb4dab6dd89af4d541d53353ad97de); 13 | 14 | event SetExtraImageHash(bytes32 indexed _imageHash, uint256 _expiration); 15 | 16 | function _writeExpirationForImageHash(bytes32 _imageHash, uint256 _expiration) internal { 17 | ModuleStorage.writeBytes32Map(EXTRA_IMAGE_HASH_KEY, _imageHash, bytes32(_expiration)); 18 | } 19 | 20 | function _readExpirationForImageHash(bytes32 _imageHash) internal view returns (uint256 _expiration) { 21 | return uint256(ModuleStorage.readBytes32Map(EXTRA_IMAGE_HASH_KEY, _imageHash)); 22 | } 23 | 24 | function _isValidImage(bytes32 _imageHash) internal override virtual view returns (bool) { 25 | if (super._isValidImage(_imageHash)) { 26 | return true; 27 | } 28 | 29 | uint256 expiration = _readExpirationForImageHash(_imageHash); 30 | 31 | // solhint-disable-next-line not-rely-on-time 32 | return expiration != 0 && expiration > block.timestamp; 33 | } 34 | 35 | function extraImageHash(bytes32 _imageHash) public view returns (uint256) { 36 | return _readExpirationForImageHash(_imageHash); 37 | } 38 | 39 | function setExtraImageHash(bytes32 _imageHash, uint256 _expiration) external onlySelf { 40 | _writeExpirationForImageHash(_imageHash, _expiration); 41 | 42 | emit SetExtraImageHash(_imageHash, _expiration); 43 | } 44 | 45 | function clearExtraImageHashes(bytes32[] calldata _imageHashes) external onlySelf { 46 | unchecked { 47 | uint256 imageHashesLength = _imageHashes.length; 48 | for (uint256 i = 0; i < imageHashesLength; i++) { 49 | bytes32 imageHash = _imageHashes[i]; 50 | _writeExpirationForImageHash(imageHash, 0); 51 | 52 | emit SetExtraImageHash(imageHash, 0); 53 | } 54 | } 55 | } 56 | 57 | /** 58 | * @notice Query if a contract implements an interface 59 | * @param _interfaceID The interface identifier, as specified in ERC-165 60 | * @return `true` if the contract implements `_interfaceID` 61 | */ 62 | function supportsInterface(bytes4 _interfaceID) public override ( 63 | ModuleERC165, 64 | ModuleAuth 65 | ) virtual pure returns (bool) { 66 | if (_interfaceID == type(ModuleExtraAuth).interfaceId) { 67 | return true; 68 | } 69 | 70 | return super.supportsInterface(_interfaceID); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /contracts/modules/commons/ModuleIPFS.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | import "./ModuleSelfAuth.sol"; 5 | import "./ModuleStorage.sol"; 6 | 7 | import "../../utils/LibString.sol"; 8 | 9 | 10 | contract ModuleIPFS is ModuleSelfAuth { 11 | event IPFSRootUpdated(bytes32 _hash); 12 | 13 | // IPFS_ROOT_KEY = keccak256("sequence.ipfs.root") 14 | bytes32 private constant IPFS_ROOT_KEY = bytes32(0x0eecac93ced8722d209199364cda3bc33da3bc3a23daef6be49ebd780511d033); 15 | 16 | function ipfsRootBytes32() public view returns (bytes32) { 17 | return ModuleStorage.readBytes32(IPFS_ROOT_KEY); 18 | } 19 | 20 | function ipfsRoot() public view returns (string memory) { 21 | return string( 22 | abi.encodePacked( 23 | "ipfs://", 24 | LibString.prefixBase32( 25 | LibString.bytesToBase32( 26 | abi.encodePacked( 27 | hex'01701220', 28 | ipfsRootBytes32() 29 | ) 30 | ) 31 | ) 32 | ) 33 | ); 34 | } 35 | 36 | function updateIPFSRoot(bytes32 _hash) external onlySelf { 37 | _updateIPFSRoot(_hash); 38 | } 39 | 40 | function _updateIPFSRoot(bytes32 _hash) internal { 41 | ModuleStorage.writeBytes32(IPFS_ROOT_KEY, _hash); 42 | emit IPFSRootUpdated(_hash); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /contracts/modules/commons/ModuleNonce.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | import "./ModuleStorage.sol"; 5 | 6 | import "./submodules/nonce/SubModuleNonce.sol"; 7 | 8 | 9 | contract ModuleNonce { 10 | // Events 11 | event NonceChange(uint256 _space, uint256 _newNonce); 12 | 13 | // Errors 14 | error BadNonce(uint256 _space, uint256 _provided, uint256 _current); 15 | 16 | // NONCE_KEY = keccak256("org.arcadeum.module.calls.nonce"); 17 | bytes32 private constant NONCE_KEY = bytes32(0x8d0bf1fd623d628c741362c1289948e57b3e2905218c676d3e69abee36d6ae2e); 18 | 19 | /** 20 | * @notice Returns the next nonce of the default nonce space 21 | * @dev The default nonce space is 0x00 22 | * @return The next nonce 23 | */ 24 | function nonce() external virtual view returns (uint256) { 25 | return readNonce(0); 26 | } 27 | 28 | /** 29 | * @notice Returns the next nonce of the given nonce space 30 | * @param _space Nonce space, each space keeps an independent nonce count 31 | * @return The next nonce 32 | */ 33 | function readNonce(uint256 _space) public virtual view returns (uint256) { 34 | return uint256(ModuleStorage.readBytes32Map(NONCE_KEY, bytes32(_space))); 35 | } 36 | 37 | /** 38 | * @notice Changes the next nonce of the given nonce space 39 | * @param _space Nonce space, each space keeps an independent nonce count 40 | * @param _nonce Nonce to write on the space 41 | */ 42 | function _writeNonce(uint256 _space, uint256 _nonce) internal { 43 | ModuleStorage.writeBytes32Map(NONCE_KEY, bytes32(_space), bytes32(_nonce)); 44 | } 45 | 46 | /** 47 | * @notice Verify if a nonce is valid 48 | * @param _rawNonce Nonce to validate (may contain an encoded space) 49 | */ 50 | function _validateNonce(uint256 _rawNonce) internal virtual { 51 | // Retrieve current nonce for this wallet 52 | (uint256 space, uint256 providedNonce) = SubModuleNonce.decodeNonce(_rawNonce); 53 | 54 | uint256 currentNonce = readNonce(space); 55 | if (currentNonce != providedNonce) { 56 | revert BadNonce(space, providedNonce, currentNonce); 57 | } 58 | 59 | unchecked { 60 | uint256 newNonce = providedNonce + 1; 61 | 62 | _writeNonce(space, newNonce); 63 | emit NonceChange(space, newNonce); 64 | return; 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /contracts/modules/commons/ModuleOnlyDelegatecall.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | 5 | contract ModuleOnlyDelegatecall { 6 | address private immutable self; 7 | 8 | error OnlyDelegatecall(); 9 | 10 | constructor() { 11 | self = address(this); 12 | } 13 | 14 | /** 15 | * @notice Modifier that only allows functions to be called via delegatecall. 16 | */ 17 | modifier onlyDelegatecall() { 18 | if (address(this) == self) { 19 | revert OnlyDelegatecall(); 20 | } 21 | _; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /contracts/modules/commons/ModuleSelfAuth.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | 5 | contract ModuleSelfAuth { 6 | error OnlySelfAuth(address _sender, address _self); 7 | 8 | modifier onlySelf() { 9 | if (msg.sender != address(this)) { 10 | revert OnlySelfAuth(msg.sender, address(this)); 11 | } 12 | _; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /contracts/modules/commons/ModuleStorage.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | 5 | library ModuleStorage { 6 | function writeBytes32(bytes32 _key, bytes32 _val) internal { 7 | assembly { sstore(_key, _val) } 8 | } 9 | 10 | function readBytes32(bytes32 _key) internal view returns (bytes32 val) { 11 | assembly { val := sload(_key) } 12 | } 13 | 14 | function writeBytes32Map(bytes32 _key, bytes32 _subKey, bytes32 _val) internal { 15 | bytes32 key = keccak256(abi.encode(_key, _subKey)); 16 | assembly { sstore(key, _val) } 17 | } 18 | 19 | function readBytes32Map(bytes32 _key, bytes32 _subKey) internal view returns (bytes32 val) { 20 | bytes32 key = keccak256(abi.encode(_key, _subKey)); 21 | assembly { val := sload(key) } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /contracts/modules/commons/ModuleUpdate.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | import "./interfaces/IModuleUpdate.sol"; 5 | 6 | import "./Implementation.sol"; 7 | import "./ModuleSelfAuth.sol"; 8 | import "./ModuleERC165.sol"; 9 | 10 | import "../../utils/LibAddress.sol"; 11 | 12 | 13 | contract ModuleUpdate is IModuleUpdate, ModuleERC165, ModuleSelfAuth, Implementation { 14 | using LibAddress for address; 15 | 16 | event ImplementationUpdated(address newImplementation); 17 | 18 | /** 19 | * @notice Updates the implementation of the base wallet 20 | * @param _implementation New main module implementation 21 | * @dev WARNING Updating the implementation can brick the wallet 22 | */ 23 | function updateImplementation(address _implementation) external override virtual onlySelf { 24 | _updateImplementation(_implementation); 25 | } 26 | 27 | /** 28 | * @notice Updates the implementation of the base wallet, used internally. 29 | * @param _implementation New main module implementation 30 | * @dev WARNING Updating the implementation can brick the wallet 31 | */ 32 | function _updateImplementation(address _implementation) internal override virtual { 33 | if (!_implementation.isContract()) revert InvalidImplementation(_implementation); 34 | _setImplementation(_implementation); 35 | emit ImplementationUpdated(_implementation); 36 | } 37 | 38 | /** 39 | * @notice Query if a contract implements an interface 40 | * @param _interfaceID The interface identifier, as specified in ERC-165 41 | * @return `true` if the contract implements `_interfaceID` 42 | */ 43 | function supportsInterface(bytes4 _interfaceID) public override virtual pure returns (bool) { 44 | if (_interfaceID == type(IModuleUpdate).interfaceId) { 45 | return true; 46 | } 47 | 48 | return super.supportsInterface(_interfaceID); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /contracts/modules/commons/gas-estimation/ModuleIgnoreAuthUpgradable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | import "./../ModuleAuthUpgradable.sol"; 5 | 6 | 7 | /** 8 | @notice Implements ModuleAuthUpgradable but ignores the validity of the signature 9 | should only be used during gas estimation. 10 | */ 11 | abstract contract ModuleIgnoreAuthUpgradable is ModuleAuthUpgradable { 12 | /** 13 | * @notice Removes the signature validation from the module, by returning true for any _imageHash 14 | * @param _imageHash Hash image of signature 15 | * @return true always 16 | */ 17 | function _isValidImage(bytes32 _imageHash) internal override(ModuleAuthUpgradable) virtual view returns (bool) { 18 | // Still validates the imageHash using the original mechanism for a more acurate estimation 19 | return super._isValidImage(_imageHash) || true; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /contracts/modules/commons/gas-estimation/ModuleIgnoreNonceCalls.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | import "./../ModuleCalls.sol"; 5 | 6 | import "./../submodules/nonce/SubModuleNonce.sol"; 7 | 8 | 9 | /** 10 | @notice Implements ModuleCalls but ignores the validity of the nonce 11 | should only be used during gas estimation. 12 | */ 13 | abstract contract ModuleIgnoreNonceCalls is ModuleCalls { 14 | 15 | /** 16 | * @notice Verify if a nonce is valid 17 | * @param _rawNonce Nonce to validate (may contain an encoded space) 18 | */ 19 | function _validateNonce(uint256 _rawNonce) internal override virtual { 20 | // Retrieve current nonce for this wallet 21 | (uint256 space, uint256 providedNonce) = SubModuleNonce.decodeNonce(_rawNonce); 22 | 23 | uint256 currentNonce = readNonce(space); 24 | if (currentNonce != providedNonce && false) { 25 | revert BadNonce(space, providedNonce, currentNonce); 26 | } 27 | 28 | unchecked { 29 | uint256 newNonce = providedNonce + 1; 30 | 31 | _writeNonce(space, newNonce); 32 | emit NonceChange(space, newNonce); 33 | return; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /contracts/modules/commons/interfaces/IModuleAuth.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | 5 | abstract contract IModuleAuth { 6 | // IMAGE_HASH_KEY = keccak256("org.arcadeum.module.auth.upgradable.image.hash"); 7 | bytes32 internal constant IMAGE_HASH_KEY = bytes32(0xea7157fa25e3aa17d0ae2d5280fa4e24d421c61842aa85e45194e1145aa72bf8); 8 | 9 | event ImageHashUpdated(bytes32 newImageHash); 10 | 11 | // Errors 12 | error ImageHashIsZero(); 13 | error InvalidSignatureType(bytes1 _type); 14 | 15 | function _signatureValidation( 16 | bytes32 _digest, 17 | bytes calldata _signature 18 | ) internal virtual view returns ( 19 | bool isValid, 20 | bytes32 subdigest 21 | ); 22 | 23 | function signatureRecovery( 24 | bytes32 _digest, 25 | bytes calldata _signature 26 | ) public virtual view returns ( 27 | uint256 threshold, 28 | uint256 weight, 29 | bytes32 imageHash, 30 | bytes32 subdigest, 31 | uint256 checkpoint 32 | ); 33 | 34 | /** 35 | * @notice Validates the signature image 36 | * @return true if the signature image is valid 37 | */ 38 | function _isValidImage(bytes32) internal virtual view returns (bool) { 39 | return false; 40 | } 41 | 42 | /** 43 | * @notice Updates the signers configuration of the wallet 44 | * @param _imageHash New required image hash of the signature 45 | */ 46 | function updateImageHash(bytes32 _imageHash) external virtual; 47 | 48 | /** 49 | * @notice Updates the signers configuration of the wallet 50 | * @param _imageHash New required image hash of the signature 51 | */ 52 | function _updateImageHash(bytes32 _imageHash) internal virtual; 53 | } 54 | -------------------------------------------------------------------------------- /contracts/modules/commons/interfaces/IModuleAuthUpgradable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | 5 | interface IModuleAuthUpgradable { 6 | /** 7 | * @notice Returns the current image hash of the wallet 8 | */ 9 | function imageHash() external view returns (bytes32); 10 | } 11 | -------------------------------------------------------------------------------- /contracts/modules/commons/interfaces/IModuleCalls.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | 5 | interface IModuleCalls { 6 | // Events 7 | event TxFailed(bytes32 indexed _tx, uint256 _index, bytes _reason); 8 | event TxExecuted(bytes32 indexed _tx, uint256 _index); 9 | 10 | // Errors 11 | error NotEnoughGas(uint256 _index, uint256 _requested, uint256 _available); 12 | error InvalidSignature(bytes32 _hash, bytes _signature); 13 | 14 | // Transaction structure 15 | struct Transaction { 16 | bool delegateCall; // Performs delegatecall 17 | bool revertOnError; // Reverts transaction bundle if tx fails 18 | uint256 gasLimit; // Maximum gas to be forwarded 19 | address target; // Address of the contract to call 20 | uint256 value; // Amount of ETH to pass with the call 21 | bytes data; // calldata to pass 22 | } 23 | 24 | /** 25 | * @notice Allow wallet owner to execute an action 26 | * @param _txs Transactions to process 27 | * @param _nonce Signature nonce (may contain an encoded space) 28 | * @param _signature Encoded signature 29 | */ 30 | function execute( 31 | Transaction[] calldata _txs, 32 | uint256 _nonce, 33 | bytes calldata _signature 34 | ) external; 35 | 36 | /** 37 | * @notice Allow wallet to execute an action 38 | * without signing the message 39 | * @param _txs Transactions to execute 40 | */ 41 | function selfExecute( 42 | Transaction[] calldata _txs 43 | ) external; 44 | } 45 | -------------------------------------------------------------------------------- /contracts/modules/commons/interfaces/IModuleCreator.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | 5 | interface IModuleCreator { 6 | error CreateFailed(bytes _code); 7 | 8 | /** 9 | * @notice Creates a contract forwarding eth value 10 | * @param _code Creation code of the contract 11 | * @return addr The address of the created contract 12 | */ 13 | function createContract(bytes calldata _code) external payable returns (address addr); 14 | } 15 | -------------------------------------------------------------------------------- /contracts/modules/commons/interfaces/IModuleHooks.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | 5 | interface IModuleHooks { 6 | // Errors 7 | error HookAlreadyExists(bytes4 _signature); 8 | error HookDoesNotExist(bytes4 _signature); 9 | 10 | // Events 11 | event DefinedHook(bytes4 _signature, address _implementation); 12 | 13 | /** 14 | * @notice Reads the implementation hook of a signature 15 | * @param _signature Signature function 16 | * @return The address of the implementation hook, address(0) if none 17 | */ 18 | function readHook(bytes4 _signature) external view returns (address); 19 | 20 | /** 21 | * @notice Adds a new hook to handle a given function selector 22 | * @param _signature Signature function linked to the hook 23 | * @param _implementation Hook implementation contract 24 | */ 25 | function addHook(bytes4 _signature, address _implementation) external; 26 | 27 | /** 28 | * @notice Removes a registered hook 29 | * @param _signature Signature function linked to the hook 30 | */ 31 | function removeHook(bytes4 _signature) external; 32 | } 33 | -------------------------------------------------------------------------------- /contracts/modules/commons/interfaces/IModuleUpdate.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | 5 | abstract contract IModuleUpdate { 6 | // Errors 7 | error InvalidImplementation(address _implementation); 8 | 9 | /** 10 | * @notice Updates the implementation of the base wallet 11 | * @param _implementation New main module implementation 12 | * @dev WARNING Updating the implementation can brick the wallet 13 | */ 14 | function updateImplementation(address _implementation) external virtual; 15 | 16 | /** 17 | * @notice Updates the implementation of the base wallet, used internally. 18 | * @param _implementation New main module implementation 19 | * @dev WARNING Updating the implementation can brick the wallet 20 | */ 21 | function _updateImplementation(address _implementation) internal virtual; 22 | } 23 | -------------------------------------------------------------------------------- /contracts/modules/commons/submodules/auth/SequenceChainedSig.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | import "./SequenceBaseSig.sol"; 5 | 6 | import "../../interfaces/IModuleAuth.sol"; 7 | 8 | import "../../ModuleSelfAuth.sol"; 9 | import "../../ModuleStorage.sol"; 10 | 11 | import "../../../../utils/LibBytesPointer.sol"; 12 | import "../../../../utils/LibOptim.sol"; 13 | 14 | /** 15 | * @title Sequence chained auth recovery submodule 16 | * @author Agustin Aguilar (aa@horizon.io) 17 | * @notice Defines Sequence signatures that work by delegating control to new configurations. 18 | * @dev The delegations can be chained together, the first signature is the one that is used to validate 19 | * the message, the last signature must match the current on-chain configuration of the wallet. 20 | */ 21 | abstract contract SequenceChainedSig is IModuleAuth, ModuleSelfAuth { 22 | using LibBytesPointer for bytes; 23 | 24 | bytes32 public constant SET_IMAGE_HASH_TYPE_HASH = keccak256("SetImageHash(bytes32 imageHash)"); 25 | 26 | error LowWeightChainedSignature(bytes _signature, uint256 threshold, uint256 _weight); 27 | error WrongChainedCheckpointOrder(uint256 _current, uint256 _prev); 28 | 29 | /** 30 | * @notice Defined the special token that must be signed to delegate control to a new configuration. 31 | * @param _imageHash The hash of the new configuration. 32 | * @return bytes32 The message hash to be signed. 33 | */ 34 | function _hashSetImageHashStruct(bytes32 _imageHash) internal pure returns (bytes32) { 35 | return LibOptim.fkeccak256(SET_IMAGE_HASH_TYPE_HASH, _imageHash); 36 | } 37 | 38 | /** 39 | * @notice Returns the threshold, weight, root, and checkpoint of a (chained) signature. 40 | * 41 | * @dev This method return the `threshold`, `weight` and `imageHash` of the last signature in the chain. 42 | * Intermediate signatures are validated directly in this method. The `subdigest` is the one of the 43 | * first signature in the chain (since that's the one that is used to validate the message). 44 | * 45 | * @param _digest The digest to recover the signature from. 46 | * @param _signature The signature to recover. 47 | * @return threshold The threshold of the (last) signature. 48 | * @return weight The weight of the (last) signature. 49 | * @return imageHash The image hash of the (last) signature. 50 | * @return subdigest The subdigest of the (first) signature in the chain. 51 | * @return checkpoint The checkpoint of the (last) signature. 52 | */ 53 | function chainedRecover( 54 | bytes32 _digest, 55 | bytes calldata _signature 56 | ) internal view returns ( 57 | uint256 threshold, 58 | uint256 weight, 59 | bytes32 imageHash, 60 | bytes32 subdigest, 61 | uint256 checkpoint 62 | ) { 63 | uint256 rindex = 1; 64 | uint256 sigSize; 65 | 66 | // 67 | // First signature out of the loop 68 | // 69 | 70 | // First uint24 is the size of the signature 71 | (sigSize, rindex) = _signature.readUint24(rindex); 72 | uint256 nrindex = sigSize + rindex; 73 | 74 | ( 75 | threshold, 76 | weight, 77 | imageHash, 78 | subdigest, 79 | checkpoint 80 | ) = signatureRecovery( 81 | _digest, 82 | _signature[rindex:nrindex] 83 | ); 84 | 85 | if (weight < threshold) { 86 | revert LowWeightChainedSignature(_signature[rindex:nrindex], threshold, weight); 87 | } 88 | 89 | rindex = nrindex; 90 | 91 | // The following signatures are handled by this loop. 92 | // This is done this way because the first signature does not have a 93 | // checkpoint to be validated against. 94 | while (rindex < _signature.length) { 95 | // First uint24 is the size of the signature 96 | (sigSize, rindex) = _signature.readUint24(rindex); 97 | nrindex = sigSize + rindex; 98 | 99 | uint256 nextCheckpoint; 100 | 101 | ( 102 | threshold, 103 | weight, 104 | imageHash,, 105 | // Do not change the subdigest; 106 | // it should remain that of the first signature. 107 | nextCheckpoint 108 | ) = signatureRecovery( 109 | _hashSetImageHashStruct(imageHash), 110 | _signature[rindex:nrindex] 111 | ); 112 | 113 | // Validate signature 114 | if (weight < threshold) { 115 | revert LowWeightChainedSignature(_signature[rindex:nrindex], threshold, weight); 116 | } 117 | 118 | // Checkpoints must be provided in descending order 119 | // since the first signature is the one that is used to validate the message 120 | // and the last signature is the one that is used to validate the current configuration 121 | if (nextCheckpoint >= checkpoint) { 122 | revert WrongChainedCheckpointOrder(nextCheckpoint, checkpoint); 123 | } 124 | 125 | checkpoint = nextCheckpoint; 126 | rindex = nrindex; 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /contracts/modules/commons/submodules/auth/SequenceDynamicSig.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | import "./SequenceBaseSig.sol"; 5 | 6 | 7 | library SequenceDynamicSig { 8 | 9 | /** 10 | * @notice Recover a "dynamically encoded" Sequence signature. 11 | * @dev The Signature is stripped of the first byte, which is the encoding flag. 12 | * 13 | * @param _subdigest The digest of the signature. 14 | * @param _signature The Sequence signature. 15 | * @return threshold The threshold weight required to validate the signature. 16 | * @return weight The weight of the signature. 17 | * @return imageHash The hash of the recovered configuration. 18 | * @return checkpoint The checkpoint of the configuration. 19 | */ 20 | function recover( 21 | bytes32 _subdigest, 22 | bytes calldata _signature 23 | ) internal view returns ( 24 | uint256 threshold, 25 | uint256 weight, 26 | bytes32 imageHash, 27 | uint256 checkpoint 28 | ) { 29 | return SequenceBaseSig.recover(_subdigest, _signature[1:]); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /contracts/modules/commons/submodules/auth/SequenceNoChainIdSig.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | 5 | library SequenceNoChainIdSig { 6 | 7 | /** 8 | * @notice Computes a subdigest for a Sequence signature that works on all chains. 9 | * @dev The subdigest is computed by removing the chain ID from the digest (using 0 instead). 10 | * @param _digest The digest of the chain of signatures. 11 | * @return bytes32 The subdigest with no chain ID. 12 | */ 13 | function subdigest(bytes32 _digest) internal view returns (bytes32) { 14 | return keccak256( 15 | abi.encodePacked( 16 | "\x19\x01", 17 | uint256(0), 18 | address(this), 19 | _digest 20 | ) 21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /contracts/modules/commons/submodules/nonce/SubModuleNonce.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | 5 | library SubModuleNonce { 6 | // Nonce schema 7 | // 8 | // - space[160]:nonce[96] 9 | // 10 | uint256 internal constant NONCE_BITS = 96; 11 | bytes32 internal constant NONCE_MASK = bytes32(uint256(type(uint96).max)); 12 | 13 | /** 14 | * @notice Decodes a raw nonce 15 | * @dev Schema: space[160]:type[96] 16 | * @param _rawNonce Nonce to be decoded 17 | * @return _space The nonce space of the raw nonce 18 | * @return _nonce The nonce of the raw nonce 19 | */ 20 | function decodeNonce(uint256 _rawNonce) internal pure returns ( 21 | uint256 _space, 22 | uint256 _nonce 23 | ) { 24 | unchecked { 25 | // Decode nonce 26 | _space = _rawNonce >> NONCE_BITS; 27 | _nonce = uint256(bytes32(_rawNonce) & NONCE_MASK); 28 | } 29 | } 30 | 31 | function encodeNonce(uint256 _space, uint256 _nonce) internal pure returns (uint256) { 32 | unchecked { 33 | // Combine space and nonce 34 | return (_space << NONCE_BITS) | _nonce; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /contracts/modules/utils/GasEstimator.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | 5 | contract GasEstimator { 6 | function estimate( 7 | address _to, 8 | bytes calldata _data 9 | ) external returns (bool success, bytes memory result, uint256 gas) { 10 | // solhint-disable 11 | uint256 initialGas = gasleft(); 12 | (success, result) = _to.call(_data); 13 | gas = initialGas - gasleft(); 14 | // solhint-enable 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /contracts/modules/utils/MultiCallUtils.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | import "../commons/interfaces/IModuleCalls.sol"; 5 | 6 | 7 | contract MultiCallUtils { 8 | // Errors 9 | error DelegateCallNotAllowed(uint256 _index); 10 | error CallReverted(uint256 _index, bytes _result); 11 | 12 | function multiCall( 13 | IModuleCalls.Transaction[] memory _txs 14 | ) public payable returns ( 15 | bool[] memory _successes, 16 | bytes[] memory _results 17 | ) { 18 | _successes = new bool[](_txs.length); 19 | _results = new bytes[](_txs.length); 20 | 21 | for (uint256 i = 0; i < _txs.length; i++) { 22 | IModuleCalls.Transaction memory transaction = _txs[i]; 23 | 24 | if (transaction.delegateCall) revert DelegateCallNotAllowed(i); 25 | if (gasleft() < transaction.gasLimit) revert IModuleCalls.NotEnoughGas(i, transaction.gasLimit, gasleft()); 26 | 27 | // solhint-disable 28 | (_successes[i], _results[i]) = transaction.target.call{ 29 | value: transaction.value, 30 | gas: transaction.gasLimit == 0 ? gasleft() : transaction.gasLimit 31 | }(transaction.data); 32 | // solhint-enable 33 | 34 | if (!_successes[i] && _txs[i].revertOnError) revert CallReverted(i, _results[i]); 35 | } 36 | } 37 | 38 | // /// 39 | // Globals 40 | // /// 41 | 42 | function callBlockhash(uint256 _i) external view returns (bytes32) { 43 | return blockhash(_i); 44 | } 45 | 46 | function callCoinbase() external view returns (address) { 47 | return block.coinbase; 48 | } 49 | 50 | function callDifficulty() external view returns (uint256) { 51 | return block.prevrandao; // old block.difficulty 52 | } 53 | 54 | function callPrevrandao() external view returns (uint256) { 55 | return block.prevrandao; 56 | } 57 | 58 | function callGasLimit() external view returns (uint256) { 59 | return block.gaslimit; 60 | } 61 | 62 | function callBlockNumber() external view returns (uint256) { 63 | return block.number; 64 | } 65 | 66 | function callTimestamp() external view returns (uint256) { 67 | return block.timestamp; 68 | } 69 | 70 | function callGasLeft() external view returns (uint256) { 71 | return gasleft(); 72 | } 73 | 74 | function callGasPrice() external view returns (uint256) { 75 | return tx.gasprice; 76 | } 77 | 78 | function callOrigin() external view returns (address) { 79 | return tx.origin; 80 | } 81 | 82 | function callBalanceOf(address _addr) external view returns (uint256) { 83 | return _addr.balance; 84 | } 85 | 86 | function callCodeSize(address _addr) external view returns (uint256 size) { 87 | assembly { size := extcodesize(_addr) } 88 | } 89 | 90 | function callCode(address _addr) external view returns (bytes memory code) { 91 | assembly { 92 | let size := extcodesize(_addr) 93 | code := mload(0x40) 94 | mstore(0x40, add(code, and(add(add(size, 0x20), 0x1f), not(0x1f)))) 95 | mstore(code, size) 96 | extcodecopy(_addr, add(code, 0x20), 0, size) 97 | } 98 | } 99 | 100 | function callCodeHash(address _addr) external view returns (bytes32 codeHash) { 101 | assembly { codeHash := extcodehash(_addr) } 102 | } 103 | 104 | function callChainId() external view returns (uint256 id) { 105 | assembly { id := chainid() } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /contracts/modules/utils/RequireUtils.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | import "../commons/ModuleNonce.sol"; 5 | import "../commons/submodules/nonce/SubModuleNonce.sol"; 6 | 7 | import "../../interfaces/tokens/IERC20.sol"; 8 | import "../../interfaces/tokens/IERC721.sol"; 9 | import "../../interfaces/tokens/IERC1155.sol"; 10 | 11 | contract RequireUtils { 12 | /** 13 | * @notice Validates that a given expiration hasn't expired 14 | * @dev Used as an optional transaction on a Sequence batch, to create expirable transactions. 15 | * 16 | * @param _expiration Expiration to check 17 | */ 18 | function requireNonExpired(uint256 _expiration) external view { 19 | require(block.timestamp < _expiration, "RequireUtils#requireNonExpired: EXPIRED"); 20 | } 21 | 22 | /** 23 | * @notice Validates that a given wallet has reached a given nonce 24 | * @dev Used as an optional transaction on a Sequence batch, to define transaction execution order 25 | * 26 | * @param _wallet Sequence wallet 27 | * @param _nonce Required nonce 28 | */ 29 | function requireMinNonce(address _wallet, uint256 _nonce) external view { 30 | (uint256 space, uint256 nonce) = SubModuleNonce.decodeNonce(_nonce); 31 | uint256 currentNonce = ModuleNonce(_wallet).readNonce(space); 32 | require(currentNonce >= nonce, "RequireUtils#requireMinNonce: NONCE_BELOW_REQUIRED"); 33 | } 34 | 35 | /** 36 | * @notice Validates that a wallet has a minimum ERC20 token balance 37 | * @param _token ERC20 token address 38 | * @param _wallet Sequence wallet 39 | * @param _minBalance Minimum required balance 40 | */ 41 | function requireMinERC20Balance(address _token, address _wallet, uint256 _minBalance) external view { 42 | uint256 balance = IERC20(_token).balanceOf(_wallet); 43 | require(balance >= _minBalance, 'RequireUtils#requireMinERC20Balance: BALANCE_TOO_LOW'); 44 | } 45 | 46 | /** 47 | * @notice Validates that a wallet has a minimum ERC20 allowance for a spender 48 | * @param _token ERC20 token address 49 | * @param _owner Sequence wallet 50 | * @param _spender Address allowed to spend the tokens 51 | * @param _minAllowance Minimum required allowance 52 | */ 53 | function requireMinERC20Allowance(address _token, address _owner, address _spender, uint256 _minAllowance) external view { 54 | uint256 allowance = IERC20(_token).allowance(_owner, _spender); 55 | require(allowance >= _minAllowance, 'RequireUtils#requireMinERC20Allowance: ALLOWANCE_TOO_LOW'); 56 | } 57 | 58 | /** 59 | * @notice Validates that a wallet owns a specific ERC721 token 60 | * @param _token ERC721 token address 61 | * @param _wallet Sequence wallet 62 | * @param _tokenId Token ID to check for ownership 63 | */ 64 | function requireERC721Ownership(address _token, address _wallet, uint256 _tokenId) external view { 65 | address owner = IERC721(_token).ownerOf(_tokenId); 66 | require(owner == _wallet, 'RequireUtils#requireERC721Ownership: NOT_OWNER'); 67 | } 68 | 69 | /** 70 | * @notice Validates that an ERC721 token is approved for a specific spender 71 | * @param _token ERC721 token address 72 | * @param _owner Sequence wallet 73 | * @param _spender Address that should have approval 74 | * @param _tokenId Token ID to check for approval 75 | */ 76 | function requireERC721Approval(address _token, address _owner, address _spender, uint256 _tokenId) external view { 77 | address approved = IERC721(_token).getApproved(_tokenId); 78 | require( 79 | approved == _spender || IERC721(_token).isApprovedForAll(_owner, _spender), 80 | 'RequireUtils#requireERC721Approval: NOT_APPROVED' 81 | ); 82 | } 83 | 84 | /** 85 | * @notice Validates that a wallet has a minimum balance of an ERC1155 token 86 | * @param _token ERC1155 token address 87 | * @param _wallet Sequence wallet 88 | * @param _tokenId Token ID to check 89 | * @param _minBalance Minimum required balance 90 | */ 91 | function requireMinERC1155Balance(address _token, address _wallet, uint256 _tokenId, uint256 _minBalance) external view { 92 | uint256 balance = IERC1155(_token).balanceOf(_wallet, _tokenId); 93 | require(balance >= _minBalance, 'RequireUtils#requireMinERC1155Balance: BALANCE_TOO_LOW'); 94 | } 95 | 96 | /** 97 | * @notice Validates that an ERC1155 token is approved for a specific operator 98 | * @param _token ERC1155 token address 99 | * @param _owner Sequence wallet 100 | * @param _operator Address that should have operator approval 101 | */ 102 | function requireERC1155Approval(address _token, address _owner, address _operator) external view { 103 | bool isApproved = IERC1155(_token).isApprovedForAll(_owner, _operator); 104 | require(isApproved, 'RequireUtils#requireERC1155Approval: NOT_APPROVED'); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /contracts/modules/utils/SequenceUtils.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | import "./MultiCallUtils.sol"; 5 | import "./RequireUtils.sol"; 6 | 7 | 8 | contract SequenceUtils is 9 | MultiCallUtils, 10 | RequireUtils 11 | { } 12 | -------------------------------------------------------------------------------- /contracts/trust/TrustFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | import "./Trust.sol"; 5 | 6 | 7 | contract TrustFactory { 8 | function trustCreationCode() external pure returns (bytes memory) { 9 | return type(Trust).creationCode; 10 | } 11 | 12 | function addressOf( 13 | address _owner, 14 | address _beneficiary, 15 | uint256 _duration 16 | ) external view returns (address) { 17 | return address(uint160(uint(keccak256(abi.encodePacked( 18 | bytes1(0xff), 19 | address(this), 20 | bytes32(0), 21 | keccak256(abi.encodePacked( 22 | type(Trust).creationCode, 23 | abi.encode(_owner, _beneficiary, _duration) 24 | )) 25 | ))))); 26 | } 27 | 28 | function deploy( 29 | address _owner, 30 | address _beneficiary, 31 | uint256 _duration 32 | ) external returns (Trust) { 33 | return new Trust{ salt: bytes32(0) }( _owner, _beneficiary, _duration); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /contracts/utils/LibAddress.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | 5 | library LibAddress { 6 | /** 7 | * @notice Will return true if provided address is a contract 8 | * @param account Address to verify if contract or not 9 | * @dev This contract will return false if called within the constructor of 10 | * a contract's deployment, as the code is not yet stored on-chain. 11 | */ 12 | function isContract(address account) internal view returns (bool) { 13 | uint256 csize; 14 | // solhint-disable-next-line no-inline-assembly 15 | assembly { csize := extcodesize(account) } 16 | return csize != 0; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /contracts/utils/LibBytes.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | 5 | /** 6 | * @title Library for reading data from bytes arrays 7 | * @author Agustin Aguilar (aa@horizon.io) 8 | * @notice This library contains functions for reading data from bytes arrays. 9 | * 10 | * @dev These functions do not check if the input index is within the bounds of the data array. 11 | * Reading out of bounds may return dirty values. 12 | */ 13 | library LibBytes { 14 | 15 | /** 16 | * @notice Returns the bytes32 value at the given index in the input data. 17 | * @param data The input data. 18 | * @param index The index of the value to retrieve. 19 | * @return a The bytes32 value at the given index. 20 | */ 21 | function readBytes32( 22 | bytes calldata data, 23 | uint256 index 24 | ) internal pure returns ( 25 | bytes32 a 26 | ) { 27 | assembly { 28 | a := calldataload(add(data.offset, index)) 29 | } 30 | } 31 | 32 | /** 33 | * @notice Returns the uint8 value at the given index in the input data. 34 | * @param data The input data. 35 | * @param index The index of the value to retrieve. 36 | * @return a The uint8 value at the given index. 37 | */ 38 | function readUint8( 39 | bytes calldata data, 40 | uint256 index 41 | ) internal pure returns ( 42 | uint8 a 43 | ) { 44 | assembly { 45 | let word := calldataload(add(index, data.offset)) 46 | a := shr(248, word) 47 | } 48 | } 49 | 50 | /** 51 | * @notice Returns the first uint16 value in the input data. 52 | * @param data The input data. 53 | * @return a The first uint16 value in the input data. 54 | */ 55 | function readFirstUint16( 56 | bytes calldata data 57 | ) internal pure returns ( 58 | uint16 a 59 | ) { 60 | assembly { 61 | let word := calldataload(data.offset) 62 | a := shr(240, word) 63 | } 64 | } 65 | 66 | /** 67 | * @notice Returns the uint32 value at the given index in the input data. 68 | * @param data The input data. 69 | * @param index The index of the value to retrieve. 70 | * @return a The uint32 value at the given index. 71 | */ 72 | function readUint32( 73 | bytes calldata data, 74 | uint256 index 75 | ) internal pure returns ( 76 | uint32 a 77 | ) { 78 | assembly { 79 | let word := calldataload(add(index, data.offset)) 80 | a := shr(224, word) 81 | } 82 | } 83 | 84 | function readMBytes4( 85 | bytes memory data, 86 | uint256 index 87 | ) internal pure returns ( 88 | bytes4 a 89 | ) { 90 | assembly { 91 | let word := mload(add(add(data, 0x20), index)) 92 | a := and(word, 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000) 93 | } 94 | } 95 | 96 | function readMBytes32( 97 | bytes memory data, 98 | uint256 index 99 | ) internal pure returns ( 100 | bytes32 a 101 | ) { 102 | assembly { 103 | a := mload(add(add(data, 0x20), index)) 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /contracts/utils/LibOptim.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | /** 5 | * @title Library for optimized EVM operations 6 | * @author Agustin Aguilar (aa@horizon.io) 7 | * @notice This library contains functions for optimizing certain EVM operations. 8 | */ 9 | library LibOptim { 10 | 11 | /** 12 | * @notice Computes the keccak256 hash of two 32-byte inputs. 13 | * @dev It uses only scratch memory space. 14 | * @param _a The first 32 bytes of the hash. 15 | * @param _b The second 32 bytes of the hash. 16 | * @return c The keccak256 hash of the two 32-byte inputs. 17 | */ 18 | function fkeccak256( 19 | bytes32 _a, 20 | bytes32 _b 21 | ) internal pure returns (bytes32 c) { 22 | assembly { 23 | mstore(0, _a) 24 | mstore(32, _b) 25 | c := keccak256(0, 64) 26 | } 27 | } 28 | 29 | /** 30 | * @notice Returns the return data from the last call. 31 | * @return r The return data from the last call. 32 | */ 33 | function returnData() internal pure returns (bytes memory r) { 34 | assembly { 35 | let size := returndatasize() 36 | r := mload(0x40) 37 | let start := add(r, 32) 38 | mstore(0x40, add(start, size)) 39 | mstore(r, size) 40 | returndatacopy(start, 0, size) 41 | } 42 | } 43 | 44 | /** 45 | * @notice Calls another contract with the given parameters. 46 | * @dev This method doesn't increase the memory pointer. 47 | * @param _to The address of the contract to call. 48 | * @param _val The value to send to the contract. 49 | * @param _gas The amount of gas to provide for the call. 50 | * @param _data The data to send to the contract. 51 | * @return r The success status of the call. 52 | */ 53 | function call( 54 | address _to, 55 | uint256 _val, 56 | uint256 _gas, 57 | bytes calldata _data 58 | ) internal returns (bool r) { 59 | assembly { 60 | let tmp := mload(0x40) 61 | calldatacopy(tmp, _data.offset, _data.length) 62 | 63 | r := call( 64 | _gas, 65 | _to, 66 | _val, 67 | tmp, 68 | _data.length, 69 | 0, 70 | 0 71 | ) 72 | } 73 | } 74 | 75 | /** 76 | * @notice Calls another contract with the given parameters, using delegatecall. 77 | * @dev This method doesn't increase the memory pointer. 78 | * @param _to The address of the contract to call. 79 | * @param _gas The amount of gas to provide for the call. 80 | * @param _data The data to send to the contract. 81 | * @return r The success status of the call. 82 | */ 83 | function delegatecall( 84 | address _to, 85 | uint256 _gas, 86 | bytes calldata _data 87 | ) internal returns (bool r) { 88 | assembly { 89 | let tmp := mload(0x40) 90 | calldatacopy(tmp, _data.offset, _data.length) 91 | 92 | r := delegatecall( 93 | _gas, 94 | _to, 95 | tmp, 96 | _data.length, 97 | 0, 98 | 0 99 | ) 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /contracts/utils/LibString.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | /** 5 | * @title Library for string manipulation operations 6 | * @notice This library contains functions for manipulating strings in Solidity. 7 | */ 8 | library LibString { 9 | bytes private constant ALPHABET_HEX_16 = '0123456789abcdef'; 10 | bytes private constant ALPHABET_32 = 'abcdefghijklmnopqrstuvwxyz234567'; 11 | 12 | /** 13 | * @notice Prefixes a hexadecimal string with "0x". 14 | * @param _hex The hexadecimal string to prefix. 15 | * @return The prefixed hexadecimal string. 16 | */ 17 | function prefixHexadecimal(string memory _hex) internal pure returns (string memory) { 18 | return string(abi.encodePacked('0x', _hex)); 19 | } 20 | 21 | /** 22 | * @notice Prefixes a base32 string with "b". 23 | * @param _base32 The base32 string to prefix. 24 | * @return The prefixed base32 string. 25 | */ 26 | function prefixBase32(string memory _base32) internal pure returns (string memory) { 27 | return string(abi.encodePacked('b', _base32)); 28 | } 29 | 30 | /** 31 | * @notice Converts a byte array to a hexadecimal string. 32 | * @param _bytes The byte array to convert. 33 | * @return The resulting hexadecimal string. 34 | */ 35 | function bytesToHexadecimal(bytes memory _bytes) internal pure returns (string memory) { 36 | uint256 bytesLength = _bytes.length; 37 | bytes memory bytesArray = new bytes(bytesLength << 1); 38 | 39 | unchecked { 40 | for (uint256 i = 0; i < bytesLength; i++) { 41 | uint256 word = uint8(_bytes[i]); 42 | uint256 ib = i << 1; 43 | bytesArray[ib] = bytes1(ALPHABET_HEX_16[word >> 4]); 44 | bytesArray[ib + 1] = bytes1(ALPHABET_HEX_16[word & 0xf]); 45 | } 46 | } 47 | 48 | return string(bytesArray); 49 | } 50 | 51 | /** 52 | * @notice Converts a byte array to a base32 string. 53 | * @param _bytes The byte array to convert. 54 | * @return The resulting base32 string. 55 | */ 56 | function bytesToBase32(bytes memory _bytes) internal pure returns (string memory) { 57 | uint256 bytesLength = _bytes.length; 58 | 59 | uint256 t1 = bytesLength << 3; 60 | 61 | unchecked { 62 | // base32-encoded length = ceil(# of bits / 5) 63 | bytes memory bytesArray = new bytes((t1 + 4) / 5); 64 | 65 | uint256 bits = 0; 66 | uint256 buffer = 0; 67 | uint256 pointer = 0; 68 | 69 | for (uint256 i = 0; i < bytesLength; i++) { 70 | buffer = (buffer << 8) | uint8(_bytes[i]); 71 | bits += 8; 72 | 73 | while (bits >= 5) { 74 | bits -= 5; 75 | bytesArray[pointer] = bytes1(ALPHABET_32[(buffer >> bits) & 0x1f]); 76 | pointer++; 77 | } 78 | } 79 | 80 | if (bits > 0) { 81 | bytesArray[pointer] = bytes1(ALPHABET_32[(buffer << (5 - bits)) & 0x1f]); 82 | } 83 | 84 | return string(bytesArray); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = 'contracts' 3 | out = 'foundry_artifacts' 4 | libs = ["node_modules", "lib"] 5 | test = 'foundry_test' 6 | 7 | ffi = true 8 | max_test_rejects = 2048000 9 | -------------------------------------------------------------------------------- /foundry_test/hooks/WalletProxyHook.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | import 'contracts/hooks/WalletProxyHook.sol'; 5 | import 'contracts/hooks/interfaces/IWalletProxy.sol'; 6 | import 'contracts/modules/commons/ModuleHooks.sol'; 7 | import 'contracts/Factory.sol'; 8 | 9 | import 'foundry_test/base/AdvTest.sol'; 10 | 11 | contract WalletProxyHookTest is AdvTest { 12 | ModuleHooks private template; 13 | ModuleHooks private walletMod; 14 | WalletProxyHook private wallet; 15 | 16 | function setUp() external { 17 | Factory factory = new Factory(); 18 | template = new ModuleHooks(); 19 | walletMod = ModuleHooks(payable(factory.deploy(address(template), bytes32(0)))); 20 | WalletProxyHook hook = new WalletProxyHook(); 21 | 22 | // Add hook 23 | vm.prank(address(walletMod)); 24 | walletMod.addHook(IWalletProxy.PROXY_getImplementation.selector, address(hook)); 25 | 26 | wallet = WalletProxyHook(address(walletMod)); 27 | vm.label(address(wallet), 'wallet'); 28 | } 29 | 30 | // 31 | // Get Implementation 32 | // 33 | 34 | function test_PROXY_getImplementation() external { 35 | assertEq(wallet.PROXY_getImplementation(), address(template)); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /foundry_test/modules/commons/Implementation.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | import "contracts/modules/commons/Implementation.sol"; 5 | import "contracts/Factory.sol"; 6 | 7 | import "foundry_test/base/AdvTest.sol"; 8 | 9 | 10 | contract ImplementationImp is Implementation { 11 | function setImplementation(address _imp) external { 12 | _setImplementation(_imp); 13 | } 14 | 15 | function getImplementation() external view returns (address) { 16 | return _getImplementation(); 17 | } 18 | 19 | function stub() external virtual pure returns (uint256) { 20 | return 1; 21 | } 22 | } 23 | 24 | contract NextImplementation is ImplementationImp { 25 | function stub() external override pure returns (uint256) { 26 | return 2; 27 | } 28 | } 29 | 30 | contract ImplementationTest is AdvTest { 31 | function test_setImplementation(address _imp, address _next) external { 32 | boundNoContract(boundNoSys(_imp)); 33 | 34 | vm.etch(_imp, address(new ImplementationImp()).code); 35 | 36 | assertEq(ImplementationImp(_imp).getImplementation(), address(0)); 37 | 38 | ImplementationImp(_imp).setImplementation(_next); 39 | 40 | assertEq(ImplementationImp(_imp).getImplementation(), _next); 41 | assertEq(vm.load(_imp, addrToBytes32(_imp)), addrToBytes32(_next)); 42 | } 43 | 44 | function test_setImplementation_matchWallet(bytes32 _salt, address _imp, address _imp2) external { 45 | Factory factory = new Factory(); 46 | 47 | ImplementationImp imp = new ImplementationImp(); 48 | ImplementationImp imp2 = new NextImplementation(); 49 | 50 | boundNoContract(boundDiff(boundNoSys(_imp), address(factory))); 51 | boundNoContract(boundDiff(boundNoSys(_imp2), address(factory))); 52 | 53 | vm.etch(_imp, address(imp).code); 54 | address wallet = factory.deploy(_imp, _salt); 55 | 56 | assertEq(ImplementationImp(wallet).getImplementation(), _imp); 57 | assertEq(ImplementationImp(wallet).stub(), 1); 58 | 59 | vm.etch(_imp2, address(imp2).code); 60 | 61 | ImplementationImp(wallet).setImplementation(_imp2); 62 | 63 | assertEq(ImplementationImp(wallet).getImplementation(), _imp2); 64 | assertEq(vm.load(wallet, addrToBytes32(wallet)), addrToBytes32(_imp2)); 65 | assertEq(ImplementationImp(wallet).stub(), 2); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /foundry_test/modules/commons/ModuleERC5719.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | import "contracts/modules/commons/ModuleERC5719.sol"; 5 | 6 | import "foundry_test/base/AdvTest.sol"; 7 | 8 | contract ModuleERC5719Test is AdvTest { 9 | ModuleERC5719 private module; 10 | 11 | function setUp() public { 12 | module = new ModuleERC5719(); 13 | } 14 | 15 | function test_getAlternativeSignature() external { 16 | // 0xba5a3cbb592813d90eae65a3aac33e9b6dfc7be50623aa25e151fe3da06c8443 17 | // == 18 | // ipfs://bafybeif2li6lwwjicpmq5ltfuovmgpu3nx6hxzigeovclykr7y62a3eeim 19 | 20 | // 0xb6f77d000c8791676d96aedac165dd1bb2da4a5baf78198b9f391fc76b893f46 21 | // == 22 | // ipfs://bafybeifw656qadehsftw3fvo3lawlxi3wlneuw5ppamyxhzzd7dwxcj7iy 23 | 24 | vm.startPrank(address(module)); 25 | 26 | bytes32 root = 0xba5a3cbb592813d90eae65a3aac33e9b6dfc7be50623aa25e151fe3da06c8443; 27 | module.updateIPFSRoot(root); 28 | 29 | assertEq( 30 | module.getAlternativeSignature(0x64f16a2f2c80ab7f3b0e0edc67c0bf1399402dc389cfb4271ba58abe3f61ea16), 31 | 'ipfs://bafybeif2li6lwwjicpmq5ltfuovmgpu3nx6hxzigeovclykr7y62a3eeim/ERC5719/0x64f16a2f2c80ab7f3b0e0edc67c0bf1399402dc389cfb4271ba58abe3f61ea16' 32 | ); 33 | 34 | root = 0xb6f77d000c8791676d96aedac165dd1bb2da4a5baf78198b9f391fc76b893f46; 35 | module.updateIPFSRoot(root); 36 | 37 | assertEq( 38 | module.getAlternativeSignature(0x11b858b3b52eb84e900639ebb082aeacb10bd9d239f58ae3f7092885cc3593b6), 39 | 'ipfs://bafybeifw656qadehsftw3fvo3lawlxi3wlneuw5ppamyxhzzd7dwxcj7iy/ERC5719/0x11b858b3b52eb84e900639ebb082aeacb10bd9d239f58ae3f7092885cc3593b6' 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /foundry_test/modules/commons/ModuleIPFS.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | import "contracts/modules/commons/ModuleIPFS.sol"; 5 | 6 | import "foundry_test/base/AdvTest.sol"; 7 | 8 | contract ModuleIPFSTest is AdvTest { 9 | ModuleIPFS private module; 10 | 11 | function setUp() public { 12 | module = new ModuleIPFS(); 13 | } 14 | 15 | function test_exposeRoot() external { 16 | // 0xba5a3cbb592813d90eae65a3aac33e9b6dfc7be50623aa25e151fe3da06c8443 17 | // == 18 | // ipfs://bafybeif2li6lwwjicpmq5ltfuovmgpu3nx6hxzigeovclykr7y62a3eeim 19 | 20 | // 0xb6f77d000c8791676d96aedac165dd1bb2da4a5baf78198b9f391fc76b893f46 21 | // == 22 | // ipfs://bafybeifw656qadehsftw3fvo3lawlxi3wlneuw5ppamyxhzzd7dwxcj7iy 23 | 24 | vm.startPrank(address(module)); 25 | 26 | bytes32 root = 0xba5a3cbb592813d90eae65a3aac33e9b6dfc7be50623aa25e151fe3da06c8443; 27 | module.updateIPFSRoot(root); 28 | 29 | assertEq(module.ipfsRootBytes32(), root); 30 | assertEq( 31 | module.ipfsRoot(), 32 | 'ipfs://bafybeif2li6lwwjicpmq5ltfuovmgpu3nx6hxzigeovclykr7y62a3eeim' 33 | ); 34 | 35 | root = 0xb6f77d000c8791676d96aedac165dd1bb2da4a5baf78198b9f391fc76b893f46; 36 | module.updateIPFSRoot(root); 37 | 38 | assertEq(module.ipfsRootBytes32(), root); 39 | assertEq( 40 | module.ipfsRoot(), 41 | 'ipfs://bafybeifw656qadehsftw3fvo3lawlxi3wlneuw5ppamyxhzzd7dwxcj7iy' 42 | ); 43 | } 44 | 45 | function test_fail_updateIPFSRoot_notSelf(address _notSelf) external { 46 | boundDiff(_notSelf, address(module)); 47 | 48 | vm.prank(address(_notSelf)); 49 | vm.expectRevert(abi.encodeWithSignature('OnlySelfAuth(address,address)', _notSelf, address(module))); 50 | module.updateIPFSRoot(0x0); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /foundry_test/modules/commons/ModuleStorage.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | import "contracts/modules/commons/ModuleStorage.sol"; 5 | 6 | import "foundry_test/base/AdvTest.sol"; 7 | 8 | 9 | contract ModuleStorageImp { 10 | function writeBytes32(bytes32 _key, bytes32 _val) external { 11 | ModuleStorage.writeBytes32(_key, _val); 12 | } 13 | 14 | function readBytes32(bytes32 _key) external view returns (bytes32) { 15 | return ModuleStorage.readBytes32(_key); 16 | } 17 | 18 | function writeBytes32Map(bytes32 _key, bytes32 _subKey, bytes32 _val) external { 19 | ModuleStorage.writeBytes32Map(_key, _subKey, _val); 20 | } 21 | 22 | function readBytes32Map(bytes32 _key, bytes32 _subKey) external view returns (bytes32) { 23 | return ModuleStorage.readBytes32Map(_key, _subKey); 24 | } 25 | } 26 | 27 | 28 | contract ModuleStorageTest is AdvTest { 29 | ModuleStorageImp private imp; 30 | 31 | function setUp() external { 32 | imp = new ModuleStorageImp(); 33 | } 34 | 35 | function test_writeBytes32( 36 | bytes32 _key1, 37 | bytes32 _key2, 38 | bytes32 _val1, 39 | bytes32 _val2 40 | ) external { 41 | assertEq(imp.readBytes32(_key1), bytes32(0)); 42 | assertEq(imp.readBytes32(_key2), bytes32(0)); 43 | 44 | bool equal = _key1 == _key2; 45 | 46 | imp.writeBytes32(_key1, _val1); 47 | 48 | bytes32 res1 = imp.readBytes32(_key1); 49 | assertEq(res1, _val1); 50 | assertEq(vm.load(address(imp), _key1), res1); 51 | 52 | imp.writeBytes32(_key2, _val2); 53 | 54 | bytes32 res2 = imp.readBytes32(_key2); 55 | res1 = imp.readBytes32(_key1); 56 | 57 | assertEq(res1, equal ? _val2 : _val1); 58 | assertEq(res2, _val2); 59 | assertEq(vm.load(address(imp), _key1), res1); 60 | assertEq(vm.load(address(imp), _key2), res2); 61 | } 62 | 63 | function test_writeBytes32Map( 64 | bytes32 _key1, 65 | bytes32 _subkey1, 66 | bytes32 _val1, 67 | bytes32 _key2, 68 | bytes32 _subkey2, 69 | bytes32 _val2 70 | ) external { 71 | bool equal = _key1 == _key2 && _subkey1 == _subkey2; 72 | bytes32 slot1 = keccak256(abi.encode(_key1, _subkey1)); 73 | bytes32 slot2 = keccak256(abi.encode(_key2, _subkey2)); 74 | assertEq(slot1 == slot2, equal); 75 | 76 | imp.writeBytes32Map(_key1, _subkey1, _val1); 77 | bytes32 res1 = imp.readBytes32Map(_key1, _subkey1); 78 | assertEq(res1, _val1); 79 | assertEq(vm.load(address(imp), slot1), res1); 80 | 81 | imp.writeBytes32Map(_key2, _subkey2, _val2); 82 | 83 | bytes32 res2 = imp.readBytes32Map(_key2, _subkey2); 84 | res1 = imp.readBytes32Map(_key1, _subkey1); 85 | 86 | assertEq(res1, equal ? _val2 : _val1); 87 | assertEq(res2, _val2); 88 | assertEq(vm.load(address(imp), slot1), res1); 89 | assertEq(vm.load(address(imp), slot2), res2); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /foundry_test/modules/commons/submodules/auth/SequenceDynamicSig.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | import "contracts/modules/commons/submodules/auth/SequenceBaseSig.sol"; 5 | import "contracts/modules/commons/submodules/auth/SequenceDynamicSig.sol"; 6 | 7 | import "foundry_test/base/AdvTest.sol"; 8 | 9 | 10 | contract SequenceDynamicSigImp { 11 | function recover(bytes32 _subdigest, bytes calldata _signature) external view returns (uint256, uint256, bytes32, uint256) { 12 | return SequenceDynamicSig.recover(_subdigest, _signature); 13 | } 14 | 15 | function recoverBase(bytes32 _subdigest, bytes calldata _signature) external view returns (uint256 threshold, uint256 weight, bytes32 imageHash, uint256) { 16 | return SequenceBaseSig.recover(_subdigest, _signature); 17 | } 18 | } 19 | 20 | contract SequenceDynamicSigTest is AdvTest { 21 | SequenceDynamicSigImp private lib; 22 | 23 | function setUp() public { 24 | lib = new SequenceDynamicSigImp(); 25 | } 26 | 27 | function test_recover_ignoreFirstByte(uint8 _first, bytes32 _subdigest, uint256 _pk, uint16 _threshold, uint32 _checkpoint, uint8 _weight) external { 28 | _pk = boundPk(_pk); 29 | 30 | bytes memory encoded = abi.encodePacked(_threshold, _checkpoint, uint8(0), _weight, signAndPack(_pk, _subdigest, 1)); 31 | 32 | (uint256 threshold1, uint256 weight1, bytes32 imageHash1, uint256 checkpoint1) = lib.recover(_subdigest, abi.encodePacked(_first, encoded)); 33 | (uint256 threshold2, uint256 weight2, bytes32 imageHash2, uint256 checkpoint2) = lib.recoverBase(_subdigest, encoded); 34 | 35 | assertEq(threshold1, threshold2); 36 | assertEq(weight1, weight2); 37 | assertEq(imageHash1, imageHash2); 38 | assertEq(checkpoint1, checkpoint2); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /foundry_test/modules/commons/submodules/auth/SequenceNoChainIdSig.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | import "contracts/modules/commons/submodules/auth/SequenceNoChainIdSig.sol"; 5 | 6 | import "foundry_test/base/AdvTest.sol"; 7 | 8 | 9 | contract SequenceNoChainIdSigImp { 10 | function subdigest(bytes32 _digest) external view returns (bytes32) { 11 | return SequenceNoChainIdSig.subdigest(_digest); 12 | } 13 | } 14 | 15 | contract SequenceNoChainIdSigTest is AdvTest { 16 | SequenceNoChainIdSigImp private lib; 17 | 18 | function setUp() public { 19 | lib = new SequenceNoChainIdSigImp(); 20 | } 21 | 22 | function test_subdigest_DiffDigest(bytes32 _digest1, bytes32 _digest2) external { 23 | bytes32 res1 = lib.subdigest(_digest1); 24 | bytes32 res2 = lib.subdigest(_digest2); 25 | assertTrue(res1 != res2 || _digest1 == _digest2); 26 | } 27 | 28 | function test_subdigest_DiffAddress(bytes32 _digest, address _addr1, address _addr2) external { 29 | boundNoContract(boundNoSys(_addr1)); 30 | boundNoContract(boundNoSys(_addr2)); 31 | 32 | vm.etch(_addr1, address(lib).code); 33 | vm.etch(_addr2, address(lib).code); 34 | 35 | bytes32 res1 = SequenceNoChainIdSigImp(_addr1).subdigest(_digest); 36 | bytes32 res2 = SequenceNoChainIdSigImp(_addr2).subdigest(_digest); 37 | 38 | assertTrue(res1 != res2 || _addr1 == _addr2); 39 | } 40 | 41 | function test_subdigest_DiffChainId(bytes32 _digest, uint256 _chainId1, uint256 _chainId2) external { 42 | _chainId1 = bound(_chainId1, 0, type(uint64).max); 43 | _chainId2 = bound(_chainId2, 0, type(uint64).max); 44 | 45 | vm.chainId(_chainId1); 46 | bytes32 res1 = lib.subdigest(_digest); 47 | vm.chainId(_chainId2); 48 | bytes32 res2 = lib.subdigest(_digest); 49 | 50 | assertTrue(res1 == res2); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /foundry_test/modules/commons/submodules/nonce/SubModuleNonce.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | import "contracts/modules/commons/submodules/nonce/SubModuleNonce.sol"; 5 | 6 | import "foundry_test/base/AdvTest.sol"; 7 | 8 | 9 | contract SubModuleNonceTest is AdvTest { 10 | function test_decodeNonce(uint160 _space, uint96 _nonce) external { 11 | uint256 encoded = abi.decode(abi.encodePacked(_space, _nonce), (uint256)); 12 | (uint256 space2, uint256 nonce2) = SubModuleNonce.decodeNonce(encoded); 13 | 14 | assertEq(space2, _space); 15 | assertEq(nonce2, _nonce); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /foundry_test/modules/utils/L2CompressorHuffReadNonce.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | import "foundry_test/base/AdvTest.sol"; 5 | 6 | import "forge-std/console.sol"; 7 | import "forge-std/console2.sol"; 8 | 9 | import { HuffConfig } from "foundry-huff/HuffConfig.sol"; 10 | import { HuffDeployer } from "foundry-huff/HuffDeployer.sol"; 11 | 12 | import "contracts/modules/commons/interfaces/IModuleCalls.sol"; 13 | 14 | uint256 constant FMS = 0xa0; 15 | 16 | import "./L2CompressorEncoder.sol"; 17 | 18 | contract L2CompressorHuffReadNonceTest is AdvTest { 19 | address public imp; 20 | 21 | function setUp() public { 22 | imp = address( 23 | HuffDeployer 24 | .config() 25 | .with_evm_version("paris") 26 | .deploy("imps/L2CompressorReadNonce") 27 | ); 28 | } 29 | 30 | function test_read_simple_nonce() external { 31 | uint256 space = 76518466025766696338879503773554426820412884125; 32 | uint256 nonce = 29095922913147819529123945996; 33 | 34 | bytes32 compact = 0x0d6734e95e00251b768924d47d52db3270fcc49d5e039555a5312d84eb305e0c; 35 | 36 | bytes memory encoded = abi.encodePacked( 37 | encodeWord(space), encodeWord(nonce) 38 | ); 39 | 40 | (bool s, bytes memory r) = imp.staticcall(encoded); 41 | 42 | assertTrue(s); 43 | (uint256 rindex, uint256 windex, bytes memory res) = abi.decode(r, (uint256, uint256, bytes)); 44 | assertEq(rindex, encoded.length); 45 | assertEq(windex, res.length + FMS); 46 | 47 | assertEq(compact, abi.decode(res, (bytes32))); 48 | } 49 | 50 | function test_read_nonce(uint160 _space, uint96 _nonce) external { 51 | bytes memory encoded = abi.encodePacked( 52 | encodeWord(_space), encodeWord(_nonce) 53 | ); 54 | 55 | (bool s, bytes memory r) = imp.staticcall(encoded); 56 | 57 | assertTrue(s); 58 | (uint256 rindex, uint256 windex, bytes memory res) = abi.decode(r, (uint256, uint256, bytes)); 59 | assertEq(rindex, encoded.length); 60 | assertEq(windex, res.length + FMS); 61 | 62 | assertEq(abi.encodePacked(_space, _nonce), res); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /foundry_test/modules/utils/L2CompressorHuffReadTx.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | import "foundry_test/base/AdvTest.sol"; 5 | 6 | import "forge-std/console.sol"; 7 | import "forge-std/console2.sol"; 8 | 9 | import { HuffConfig } from "foundry-huff/HuffConfig.sol"; 10 | import { HuffDeployer } from "foundry-huff/HuffDeployer.sol"; 11 | 12 | import "contracts/modules/commons/interfaces/IModuleCalls.sol"; 13 | 14 | import "./L2CompressorEncoder.sol"; 15 | 16 | uint256 constant FMS = 0xa0; 17 | 18 | contract L2CompressorHuffReadTxTests is AdvTest { 19 | address public imp; 20 | 21 | function setUp() public { 22 | imp = address( 23 | HuffDeployer 24 | .config() 25 | .with_evm_version("paris") 26 | .deploy("imps/L2CompressorReadTx") 27 | ); 28 | } 29 | 30 | function test_read_simple_transaction(address _addr) external { 31 | bytes memory encoded = abi.encodePacked(build_flag(true, true, false, false, false), encode_raw_address(_addr)); 32 | 33 | (bool s, bytes memory r) = imp.staticcall(encoded); 34 | 35 | assertTrue(s); 36 | (uint256 rindex, uint256 windex, bytes memory res) = abi.decode(r, (uint256, uint256, bytes)); 37 | 38 | IModuleCalls.Transaction memory t; 39 | t.delegateCall = true; 40 | t.revertOnError = true; 41 | t.target = _addr; 42 | 43 | assertEq(rindex, encoded.length); 44 | assertEq(windex, res.length + FMS); 45 | 46 | // Abi encode prefixes with the point on which the data starts 47 | // we don't do it on the compressor, so we need to append 32 48 | assertEq(abi.encodePacked(abi.encode(32), res), abi.encode(t)); 49 | } 50 | 51 | function test_read_simple_transaction_with_data(address _addr, bytes memory _data) external { 52 | bytes memory encoded = abi.encodePacked( 53 | build_flag(true, true, false, false, true), 54 | encode_raw_address(_addr), 55 | encode_bytes_n(_data) 56 | ); 57 | 58 | (bool s, bytes memory r) = imp.staticcall(encoded); 59 | 60 | assertTrue(s); 61 | (uint256 rindex, uint256 windex, bytes memory res) = abi.decode(r, (uint256, uint256, bytes)); 62 | 63 | IModuleCalls.Transaction memory t; 64 | t.delegateCall = true; 65 | t.revertOnError = true; 66 | t.target = _addr; 67 | t.data = _data; 68 | 69 | assertEq(rindex, encoded.length); 70 | assertEq(windex, res.length + FMS); 71 | 72 | // Abi encode prefixes with the point on which the data starts 73 | // we don't do it on the compressor, so we need to append 32 74 | assertEq(abi.encodePacked(abi.encode(32), res), abi.encode(t)); 75 | } 76 | 77 | function test_read_transaction(IModuleCalls.Transaction memory _tx) external { 78 | bytes memory encoded = abi.encodePacked( 79 | build_flag(_tx.delegateCall, _tx.revertOnError, _tx.gasLimit != 0, _tx.value != 0, _tx.data.length != 0), 80 | _tx.gasLimit != 0 ? encodeWord(_tx.gasLimit) : bytes(""), 81 | encode_raw_address(_tx.target), 82 | _tx.value != 0 ? encodeWord(_tx.value) : bytes(""), 83 | _tx.data.length != 0 ? encode_bytes_n(_tx.data) : bytes("") 84 | ); 85 | 86 | console.logBytes(encoded); 87 | 88 | (bool s, bytes memory r) = imp.staticcall(encoded); 89 | 90 | assertTrue(s); 91 | (uint256 rindex, uint256 windex, bytes memory res) = abi.decode(r, (uint256, uint256, bytes)); 92 | 93 | assertEq(rindex, encoded.length); 94 | assertEq(windex, res.length + FMS); 95 | 96 | // Abi encode prefixes with the point on which the data starts 97 | // we don't do it on the compressor, so we need to append 32 98 | assertEq(abi.encodePacked(abi.encode(32), res), abi.encode(_tx)); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /foundry_test/modules/utils/L2CompressorHuffReadTxs.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | import "foundry_test/base/AdvTest.sol"; 5 | 6 | import "forge-std/console.sol"; 7 | import "forge-std/console2.sol"; 8 | 9 | import { HuffConfig } from "foundry-huff/HuffConfig.sol"; 10 | import { HuffDeployer } from "foundry-huff/HuffDeployer.sol"; 11 | 12 | import "contracts/modules/commons/interfaces/IModuleCalls.sol"; 13 | 14 | uint256 constant FMS = 0xa0; 15 | 16 | import "./L2CompressorEncoder.sol"; 17 | 18 | contract L2CompressorHuffReadTxTests is AdvTest { 19 | address public imp; 20 | 21 | function setUp() public { 22 | imp = address( 23 | HuffDeployer 24 | .config() 25 | .with_evm_version("paris") 26 | .deploy("imps/L2CompressorReadTxs") 27 | ); 28 | } 29 | 30 | function test_read_simple_2_transactions() external { 31 | address _addr = address(0x1234567890123456789012345678901234567890); 32 | address _addr2 = address(this); 33 | bytes memory encoded = abi.encodePacked( 34 | uint8(0x02), 35 | build_flag(false, true, false, false, false), 36 | encode_raw_address(_addr), 37 | build_flag(true, true, false, false, false), 38 | encode_raw_address(_addr2) 39 | ); 40 | 41 | (bool s, bytes memory r) = imp.staticcall{ gas: 10000 }(encoded); 42 | 43 | assertTrue(s); 44 | (uint256 rindex, uint256 windex, bytes memory res) = abi.decode(r, (uint256, uint256, bytes)); 45 | 46 | IModuleCalls.Transaction[] memory t = new IModuleCalls.Transaction[](2); 47 | t[0].delegateCall = false; 48 | t[0].revertOnError = true; 49 | t[0].target = _addr; 50 | t[1].delegateCall = true; 51 | t[1].revertOnError = true; 52 | t[1].target = _addr2; 53 | 54 | assertEq(rindex, encoded.length); 55 | assertEq(windex, res.length + FMS); 56 | 57 | // Abi encode prefixes with the point on which the data starts 58 | // we don't do it on the compressor, so we need to append 32 59 | assertEq(abi.encodePacked(abi.encode(32), res), abi.encode(t)); 60 | } 61 | 62 | function test_read_simple_2_transactions_asymetric() external { 63 | address _addr = address(0x1234567890123456789012345678901234567890); 64 | address _addr2 = address(this); 65 | bytes memory _data = hex"123456789012345678901234567890123456789012345678901234567890123456789011222211"; 66 | 67 | bytes memory encoded = abi.encodePacked( 68 | uint8(0x02), 69 | build_flag(false, true, false, false, true), 70 | encode_raw_address(_addr), 71 | encode_bytes_n(_data), 72 | build_flag(true, true, false, false, false), 73 | encode_raw_address(_addr2) 74 | ); 75 | 76 | (bool s, bytes memory r) = imp.staticcall{ gas: 10000 }(encoded); 77 | 78 | assertTrue(s); 79 | (uint256 rindex, uint256 windex, bytes memory res) = abi.decode(r, (uint256, uint256, bytes)); 80 | 81 | IModuleCalls.Transaction[] memory t = new IModuleCalls.Transaction[](2); 82 | t[0].delegateCall = false; 83 | t[0].revertOnError = true; 84 | t[0].target = _addr; 85 | t[0].data = _data; 86 | t[1].delegateCall = true; 87 | t[1].revertOnError = true; 88 | t[1].target = _addr2; 89 | 90 | assertEq(rindex, encoded.length); 91 | assertEq(windex, res.length + FMS); 92 | 93 | // Abi encode prefixes with the point on which the data starts 94 | // we don't do it on the compressor, so we need to append 32 95 | assertEq(abi.encodePacked(abi.encode(32), res), abi.encode(t)); 96 | } 97 | 98 | function test_read_transactions(IModuleCalls.Transaction[] memory _txs) external { 99 | vm.assume(_txs.length != 0 && _txs.length <= type(uint8).max); 100 | 101 | bytes memory encoded = abi.encodePacked( 102 | uint8(_txs.length) 103 | ); 104 | 105 | for (uint256 i = 0; i < _txs.length; i++) { 106 | IModuleCalls.Transaction memory t = _txs[i]; 107 | 108 | encoded = abi.encodePacked( 109 | encoded, 110 | build_flag(t.delegateCall, t.revertOnError, t.gasLimit != 0, t.value != 0, t.data.length != 0), 111 | t.gasLimit != 0 ? encodeWord(t.gasLimit) : bytes(""), 112 | encode_raw_address(t.target), 113 | t.value != 0 ? encodeWord(t.value) : bytes(""), 114 | t.data.length != 0 ? encode_bytes_n(t.data) : bytes("") 115 | ); 116 | } 117 | 118 | (bool s, bytes memory r) = imp.staticcall(encoded); 119 | 120 | assertTrue(s); 121 | (uint256 rindex, uint256 windex, bytes memory res) = abi.decode(r, (uint256, uint256, bytes)); 122 | 123 | assertEq(rindex, encoded.length); 124 | assertEq(windex, res.length + FMS); 125 | 126 | // Abi encode prefixes with the point on which the data starts 127 | // we don't do it on the compressor, so we need to append 32 128 | assertEq(abi.encodePacked(abi.encode(32), res), abi.encode(_txs)); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /foundry_test/trust/TrustFactory.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | import "contracts/trust/TrustFactory.sol"; 5 | 6 | import "foundry_test/base/AdvTest.sol"; 7 | 8 | contract TrustFactoryTest is AdvTest { 9 | TrustFactory private factory; 10 | 11 | function setUp() external { 12 | factory = new TrustFactory(); 13 | } 14 | 15 | function test_create_trust(address _owner, address _beneficiary, uint256 _duration) external { 16 | Trust trust = factory.deploy(_owner, _beneficiary, _duration); 17 | address trustAddress = address(trust); 18 | 19 | assertEq(trust.owner(), _owner); 20 | assertEq(trust.beneficiary(), _beneficiary); 21 | assertEq(trust.duration(), _duration); 22 | 23 | uint256 codeSize; assembly { codeSize := extcodesize(trustAddress) } 24 | assertGt(codeSize, 0); 25 | } 26 | 27 | function test_predict_address(address _owner, address _beneficiary, uint256 _duration) external { 28 | address expected = factory.addressOf(_owner, _beneficiary, _duration); 29 | address actual = address(factory.deploy(_owner, _beneficiary, _duration)); 30 | assertEq(actual, expected); 31 | } 32 | 33 | function test_fail_deploy_twice(address _owner, address _beneficiary, uint256 _duration) external { 34 | factory.deploy(_owner, _beneficiary, _duration); 35 | vm.expectRevert(); 36 | factory.deploy(_owner, _beneficiary, _duration); 37 | } 38 | 39 | function test_fail_deploy_low_gas(address _owner, address _beneficiary, uint256 _duration, uint256 _gas) external { 40 | _gas = bound(_gas, 21000, block.gaslimit); 41 | try factory.deploy{ gas: _gas }(_owner, _beneficiary, _duration) returns (Trust trust) { 42 | address trustAddress = address(trust); 43 | // The address should have code, and never be the zero address 44 | assertNotEq(trustAddress, address(0)); 45 | uint256 codeSize; assembly { codeSize := extcodesize(trustAddress) } 46 | assertGt(codeSize, 0); 47 | } catch { 48 | // Ignore errors from low gas 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /foundry_test/utils/LibAddress.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | import "contracts/utils/LibAddress.sol"; 5 | 6 | import "foundry_test/base/AdvTest.sol"; 7 | 8 | 9 | contract LibAddressTest is AdvTest { 10 | function test_isContract(address _addr, bytes calldata _code) external { 11 | boundNoSys(_addr); 12 | 13 | vm.etch(_addr, _code); 14 | assertEq(LibAddress.isContract(_addr), _code.length > 0); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /foundry_test/utils/LibBytes.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | import "contracts/utils/LibBytes.sol"; 5 | 6 | import "foundry_test/base/AdvTest.sol"; 7 | 8 | 9 | contract LibBytesImp { 10 | using LibBytes for bytes; 11 | 12 | function readBytes32(bytes calldata _data, uint256 _index) external pure returns (bytes32) { 13 | return _data.readBytes32(_index); 14 | } 15 | 16 | function readUint8(bytes calldata _data, uint256 _index) external pure returns (uint8) { 17 | return _data.readUint8(_index); 18 | } 19 | 20 | function readFirstUint16(bytes calldata _data) external pure returns (uint16) { 21 | return _data.readFirstUint16(); 22 | } 23 | 24 | function readUint32(bytes calldata _data, uint256 _index) external pure returns (uint32) { 25 | return _data.readUint32(_index); 26 | } 27 | } 28 | 29 | contract LibBytesTest is AdvTest { 30 | LibBytesImp private lib; 31 | 32 | function setUp() external { 33 | lib = new LibBytesImp(); 34 | } 35 | 36 | function test_readBytes32(bytes calldata _prefix, bytes32 _data, bytes calldata _sufix) external { 37 | bytes memory combined = abi.encodePacked(_prefix, _data, _sufix); 38 | bytes32 actual = lib.readBytes32(combined, _prefix.length); 39 | assertEq(actual, _data); 40 | } 41 | 42 | function test_readBytes32_OutOfBounds(bytes calldata _data, uint256 _index) external view { 43 | lib.readBytes32(_data, _index); 44 | } 45 | 46 | function test_readBytes32_Fuzz_AbiDecode(bytes calldata _data, uint256 _index) external { 47 | _index = bound(_index, 0, _data.length > 32 ? _data.length - 32 : 0); 48 | bytes32 expected = abi.decode(abi.encodePacked(_data[_index:], bytes32(0)), (bytes32)); 49 | bytes32 actual = lib.readBytes32(_data, _index); 50 | assertEq(expected, actual); 51 | } 52 | 53 | function test_readUint8(bytes calldata _prefix, uint8 _data, bytes calldata _sufix) external { 54 | bytes memory combined = abi.encodePacked(_prefix, _data, _sufix); 55 | uint8 expected = lib.readUint8(combined, _prefix.length); 56 | assertEq(expected, _data); 57 | } 58 | 59 | function test_readUint8_OutOfBounds(bytes calldata _data, uint256 _index) external view { 60 | lib.readUint8(_data, _index); 61 | } 62 | 63 | function test_readUint8_Fuzz_ReadByte(bytes calldata _data, uint256 _index) external { 64 | vm.assume(_data.length >= 1); 65 | 66 | _index = bound(_index, 0, _data.length - 1); 67 | uint8 expected = uint8(uint256(bytes32(_data[_index])) >> 248); 68 | uint8 actual = lib.readUint8(_data, _index); 69 | 70 | assertEq(expected, actual); 71 | } 72 | 73 | function test_readFirstUint16(uint16 _data, bytes calldata _sufix) external { 74 | bytes memory combined = abi.encodePacked(_data, _sufix); 75 | uint16 expected = lib.readFirstUint16(combined); 76 | assertEq(expected, _data); 77 | } 78 | 79 | function test_readFirstUint16_OutOfBounds(uint8 _data) external { 80 | bytes memory encoded = abi.encodePacked(_data); 81 | 82 | uint16 actual = lib.readFirstUint16(bytes('')); 83 | assertEq(actual, uint16(0)); 84 | 85 | actual = lib.readFirstUint16(encoded); 86 | assertEq(actual, uint256(_data) << 8); 87 | } 88 | 89 | function test_readFirstUint16_Fuzz_AbiDecode(bytes calldata _data) external { 90 | uint256 expected = abi.decode(abi.encodePacked(_data, bytes32(0)), (uint256)); 91 | uint16 actual = lib.readFirstUint16(_data); 92 | 93 | assertEq(expected >> 240, actual); 94 | } 95 | 96 | function test_readUint32(bytes calldata _prefix, uint32 _data, bytes calldata _sufix) external { 97 | bytes memory combined = abi.encodePacked(_prefix, _data, _sufix); 98 | uint32 expected = lib.readUint32(combined, _prefix.length); 99 | assertEq(expected, _data); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /foundry_test/utils/LibOptim.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity 0.8.18; 3 | 4 | import "contracts/utils/LibOptim.sol"; 5 | 6 | import "foundry_test/base/AdvTest.sol"; 7 | 8 | contract WillReturn { 9 | bytes private r; 10 | 11 | constructor (bytes memory _r) { 12 | r = _r; 13 | } 14 | 15 | fallback() external { 16 | bytes memory res = r; 17 | assembly { 18 | return(add(res, 32), mload(res)) 19 | } 20 | } 21 | } 22 | 23 | contract LibOptimTest is AdvTest { 24 | function test_fkeccak256_Bytes32_Bytes32_Fuzz(bytes32 _a, bytes32 _b) external { 25 | bytes32 expected = keccak256(abi.encodePacked(_a, _b)); 26 | bytes32 actual = LibOptim.fkeccak256(_a, _b); 27 | assertEq(expected, actual); 28 | } 29 | 30 | function test_returnData_Fuzz(bytes memory _data) external { 31 | WillReturn r = new WillReturn(_data); 32 | 33 | (bool suc, bytes memory res1) = address(r).call(bytes('')); 34 | assertEq(suc, true); 35 | assertEq(res1, _data); 36 | 37 | uint256 pointer1; assembly { pointer1 := mload(0x40) } 38 | assertTrue(pointer1 != 0); 39 | 40 | bytes memory optres = LibOptim.returnData(); 41 | assertEq(res1, optres); 42 | 43 | uint256 pointer2; assembly { pointer2 := mload(0x40) } 44 | assertEq(pointer2 - pointer1, res1.length + 32); 45 | 46 | uint256 positionArr; assembly { positionArr := optres } 47 | assertEq(positionArr, pointer1); 48 | } 49 | 50 | function test_call( 51 | address _to, 52 | uint256 _val, 53 | bytes calldata _data 54 | ) external { 55 | _to = boundNoSys(_to); 56 | _to = boundDiff(_to, address(0x004e59b44847b379578588920ca78fbf26c0b4956c)); 57 | 58 | vm.expectCall(_to, _data); 59 | vm.deal(_to, 0); 60 | vm.deal(address(this), _val); 61 | LibOptim.call(_to, _val, gasleft(), _data); 62 | assertEq(_to.balance, _val); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /funding.json: -------------------------------------------------------------------------------- 1 | { 2 | "opRetro": { 3 | "projectId": "0x62408999652f3bfa1be746d256bf5a4eb4719b993d40f07d2d60aaebee015018" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /hardhat.config.ts: -------------------------------------------------------------------------------- 1 | import { HardhatUserConfig, task } from 'hardhat/config' 2 | import { networkConfig } from './utils/config-loader' 3 | 4 | import '@nomicfoundation/hardhat-ethers' 5 | import '@nomicfoundation/hardhat-verify' 6 | import '@nomiclabs/hardhat-truffle5' 7 | import '@nomiclabs/hardhat-web3' 8 | import '@tenderly/hardhat-tenderly' 9 | 10 | import 'hardhat-gas-reporter' 11 | import 'solidity-coverage' 12 | 13 | import './utils/benchmarker' 14 | 15 | const ganacheNetwork = { 16 | url: 'http://127.0.0.1:8545', 17 | blockGasLimit: 6000000000 18 | } 19 | 20 | const config: HardhatUserConfig = { 21 | solidity: { 22 | version: '0.8.18', 23 | settings: { 24 | optimizer: { 25 | enabled: true, 26 | runs: 500000 27 | } 28 | } 29 | }, 30 | networks: { 31 | mainnet: networkConfig('mainnet'), 32 | ropsten: networkConfig('ropsten'), 33 | kovan: networkConfig('kovan'), 34 | goerli: networkConfig('goerli'), 35 | polygon: networkConfig('polygon'), 36 | polygonZkevm: networkConfig('polygon-zkevm'), 37 | mumbai: networkConfig('mumbai'), 38 | arbitrum: networkConfig('arbitrum'), 39 | arbitrumGoerli: networkConfig('arbitrum-goerli'), 40 | arbitrumNova: networkConfig('arbitrum-nova'), 41 | optimism: networkConfig('optimism'), 42 | bnb: networkConfig('bnb'), 43 | bnbTestnet: networkConfig('bnb-testnet'), 44 | gnosis: networkConfig('gnosis'), 45 | avalanche: networkConfig('avalanche'), 46 | avalancheFuji: networkConfig('avalanche-fuji'), 47 | ganache: ganacheNetwork, 48 | hardhat: { 49 | blockGasLimit: 60000000 50 | } 51 | }, 52 | etherscan: { 53 | // Your API key for Etherscan 54 | // Obtain one at https://etherscan.io/ 55 | apiKey: networkConfig('mainnet').etherscan 56 | }, 57 | mocha: { 58 | timeout: process.env.COVERAGE ? 15 * 60 * 1000 : 30 * 1000 59 | }, 60 | gasReporter: { 61 | enabled: !!process.env.REPORT_GAS === true, 62 | currency: 'USD', 63 | gasPrice: 21, 64 | showTimeSpent: true 65 | }, 66 | tenderly: { 67 | project: 'horizon/sequence-dev-1', 68 | username: 'Agusx1211-horizon' 69 | } 70 | } 71 | 72 | export default config 73 | -------------------------------------------------------------------------------- /networks/arbitrum.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "contractName": "WalletFactory", 4 | "address": "0xFaA5c0b14d1bED5C888Ca655B9a8A5911F78eF4A" 5 | }, 6 | { 7 | "contractName": "MainModule", 8 | "address": "0xfBf8f1A5E00034762D928f46d438B947f5d4065d" 9 | }, 10 | { 11 | "contractName": "MainModuleUpgradable", 12 | "address": "0x4222dcA3974E39A8b41c411FeDDE9b09Ae14b911" 13 | }, 14 | { 15 | "contractName": "GuestModule", 16 | "address": "0xfea230Ee243f88BC698dD8f1aE93F8301B6cdfaE" 17 | }, 18 | { 19 | "contractName": "SequenceUtils", 20 | "address": "0xdbbFa3cB3B087B64F4ef5E3D20Dda2488AA244e6" 21 | } 22 | ] -------------------------------------------------------------------------------- /networks/arbitrumGoerli.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "contractName": "WalletFactory", 4 | "address": "0xFaA5c0b14d1bED5C888Ca655B9a8A5911F78eF4A" 5 | }, 6 | { 7 | "contractName": "MainModule", 8 | "address": "0xfBf8f1A5E00034762D928f46d438B947f5d4065d" 9 | }, 10 | { 11 | "contractName": "MainModuleUpgradable", 12 | "address": "0x4222dcA3974E39A8b41c411FeDDE9b09Ae14b911" 13 | }, 14 | { 15 | "contractName": "GuestModule", 16 | "address": "0xfea230Ee243f88BC698dD8f1aE93F8301B6cdfaE" 17 | }, 18 | { 19 | "contractName": "SequenceUtils", 20 | "address": "0xdbbFa3cB3B087B64F4ef5E3D20Dda2488AA244e6" 21 | } 22 | ] -------------------------------------------------------------------------------- /networks/arbitrumNova.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "contractName": "WalletFactory", 4 | "address": "0xFaA5c0b14d1bED5C888Ca655B9a8A5911F78eF4A" 5 | }, 6 | { 7 | "contractName": "MainModule", 8 | "address": "0xfBf8f1A5E00034762D928f46d438B947f5d4065d" 9 | }, 10 | { 11 | "contractName": "MainModuleUpgradable", 12 | "address": "0x4222dcA3974E39A8b41c411FeDDE9b09Ae14b911" 13 | }, 14 | { 15 | "contractName": "GuestModule", 16 | "address": "0xfea230Ee243f88BC698dD8f1aE93F8301B6cdfaE" 17 | }, 18 | { 19 | "contractName": "SequenceUtils", 20 | "address": "0xdbbFa3cB3B087B64F4ef5E3D20Dda2488AA244e6" 21 | } 22 | ] -------------------------------------------------------------------------------- /networks/avalanche.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "contractName": "WalletFactory", 4 | "address": "0xFaA5c0b14d1bED5C888Ca655B9a8A5911F78eF4A" 5 | }, 6 | { 7 | "contractName": "MainModule", 8 | "address": "0xfBf8f1A5E00034762D928f46d438B947f5d4065d" 9 | }, 10 | { 11 | "contractName": "MainModuleUpgradable", 12 | "address": "0x4222dcA3974E39A8b41c411FeDDE9b09Ae14b911" 13 | }, 14 | { 15 | "contractName": "GuestModule", 16 | "address": "0xfea230Ee243f88BC698dD8f1aE93F8301B6cdfaE" 17 | }, 18 | { 19 | "contractName": "SequenceUtils", 20 | "address": "0xdbbFa3cB3B087B64F4ef5E3D20Dda2488AA244e6" 21 | } 22 | ] -------------------------------------------------------------------------------- /networks/avalancheFuji.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "contractName": "WalletFactory", 4 | "address": "0xFaA5c0b14d1bED5C888Ca655B9a8A5911F78eF4A" 5 | }, 6 | { 7 | "contractName": "MainModule", 8 | "address": "0xfBf8f1A5E00034762D928f46d438B947f5d4065d" 9 | }, 10 | { 11 | "contractName": "MainModuleUpgradable", 12 | "address": "0x4222dcA3974E39A8b41c411FeDDE9b09Ae14b911" 13 | }, 14 | { 15 | "contractName": "GuestModule", 16 | "address": "0xfea230Ee243f88BC698dD8f1aE93F8301B6cdfaE" 17 | }, 18 | { 19 | "contractName": "SequenceUtils", 20 | "address": "0xdbbFa3cB3B087B64F4ef5E3D20Dda2488AA244e6" 21 | } 22 | ] -------------------------------------------------------------------------------- /networks/bnb.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "contractName": "WalletFactory", 4 | "address": "0xFaA5c0b14d1bED5C888Ca655B9a8A5911F78eF4A" 5 | }, 6 | { 7 | "contractName": "MainModule", 8 | "address": "0xfBf8f1A5E00034762D928f46d438B947f5d4065d" 9 | }, 10 | { 11 | "contractName": "MainModuleUpgradable", 12 | "address": "0x4222dcA3974E39A8b41c411FeDDE9b09Ae14b911" 13 | }, 14 | { 15 | "contractName": "GuestModule", 16 | "address": "0xfea230Ee243f88BC698dD8f1aE93F8301B6cdfaE" 17 | }, 18 | { 19 | "contractName": "SequenceUtils", 20 | "address": "0xdbbFa3cB3B087B64F4ef5E3D20Dda2488AA244e6" 21 | } 22 | ] -------------------------------------------------------------------------------- /networks/bnbTestnet.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "contractName": "WalletFactory", 4 | "address": "0xFaA5c0b14d1bED5C888Ca655B9a8A5911F78eF4A" 5 | }, 6 | { 7 | "contractName": "MainModule", 8 | "address": "0xfBf8f1A5E00034762D928f46d438B947f5d4065d" 9 | }, 10 | { 11 | "contractName": "MainModuleUpgradable", 12 | "address": "0x4222dcA3974E39A8b41c411FeDDE9b09Ae14b911" 13 | }, 14 | { 15 | "contractName": "GuestModule", 16 | "address": "0xfea230Ee243f88BC698dD8f1aE93F8301B6cdfaE" 17 | }, 18 | { 19 | "contractName": "SequenceUtils", 20 | "address": "0xdbbFa3cB3B087B64F4ef5E3D20Dda2488AA244e6" 21 | } 22 | ] -------------------------------------------------------------------------------- /networks/gnosis.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "contractName": "WalletFactory", 4 | "address": "0xFaA5c0b14d1bED5C888Ca655B9a8A5911F78eF4A" 5 | }, 6 | { 7 | "contractName": "MainModule", 8 | "address": "0xfBf8f1A5E00034762D928f46d438B947f5d4065d" 9 | }, 10 | { 11 | "contractName": "MainModuleUpgradable", 12 | "address": "0x4222dcA3974E39A8b41c411FeDDE9b09Ae14b911" 13 | }, 14 | { 15 | "contractName": "GuestModule", 16 | "address": "0xfea230Ee243f88BC698dD8f1aE93F8301B6cdfaE" 17 | }, 18 | { 19 | "contractName": "SequenceUtils", 20 | "address": "0xdbbFa3cB3B087B64F4ef5E3D20Dda2488AA244e6" 21 | } 22 | ] -------------------------------------------------------------------------------- /networks/goerli.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "contractName": "WalletFactory", 4 | "address": "0xFaA5c0b14d1bED5C888Ca655B9a8A5911F78eF4A" 5 | }, 6 | { 7 | "contractName": "MainModule", 8 | "address": "0xfBf8f1A5E00034762D928f46d438B947f5d4065d" 9 | }, 10 | { 11 | "contractName": "MainModuleUpgradable", 12 | "address": "0x4222dcA3974E39A8b41c411FeDDE9b09Ae14b911" 13 | }, 14 | { 15 | "contractName": "GuestModule", 16 | "address": "0xfea230Ee243f88BC698dD8f1aE93F8301B6cdfaE" 17 | }, 18 | { 19 | "contractName": "SequenceUtils", 20 | "address": "0xdbbFa3cB3B087B64F4ef5E3D20Dda2488AA244e6" 21 | } 22 | ] -------------------------------------------------------------------------------- /networks/hardhat.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "contractName": "WalletFactory", 4 | "address": "0xFaA5c0b14d1bED5C888Ca655B9a8A5911F78eF4A" 5 | }, 6 | { 7 | "contractName": "MainModule", 8 | "address": "0xE1846F966D116B86d65481Fe81886219D698b60D" 9 | }, 10 | { 11 | "contractName": "MainModuleUpgradable", 12 | "address": "0xB39E1ed61caC9E8eE8CDD3218765cF6a7fE390db" 13 | }, 14 | { 15 | "contractName": "GuestModule", 16 | "address": "0x28Ec0675C7b40ed78EBcBBbDF39e5652c0787B18" 17 | }, 18 | { 19 | "contractName": "SequenceUtils", 20 | "address": "0x4817Fe9f2352E88991A7c84E4385708Ccff05704" 21 | } 22 | ] -------------------------------------------------------------------------------- /networks/mainnet.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "contractName": "WalletFactory", 4 | "address": "0xFaA5c0b14d1bED5C888Ca655B9a8A5911F78eF4A" 5 | }, 6 | { 7 | "contractName": "MainModule", 8 | "address": "0xfBf8f1A5E00034762D928f46d438B947f5d4065d" 9 | }, 10 | { 11 | "contractName": "MainModuleUpgradable", 12 | "address": "0x4222dcA3974E39A8b41c411FeDDE9b09Ae14b911" 13 | }, 14 | { 15 | "contractName": "GuestModule", 16 | "address": "0xfea230Ee243f88BC698dD8f1aE93F8301B6cdfaE" 17 | }, 18 | { 19 | "contractName": "SequenceUtils", 20 | "address": "0xdbbFa3cB3B087B64F4ef5E3D20Dda2488AA244e6" 21 | } 22 | ] -------------------------------------------------------------------------------- /networks/mumbai.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "contractName": "WalletFactory", 4 | "address": "0xFaA5c0b14d1bED5C888Ca655B9a8A5911F78eF4A" 5 | }, 6 | { 7 | "contractName": "MainModule", 8 | "address": "0xfBf8f1A5E00034762D928f46d438B947f5d4065d" 9 | }, 10 | { 11 | "contractName": "MainModuleUpgradable", 12 | "address": "0x4222dcA3974E39A8b41c411FeDDE9b09Ae14b911" 13 | }, 14 | { 15 | "contractName": "GuestModule", 16 | "address": "0xfea230Ee243f88BC698dD8f1aE93F8301B6cdfaE" 17 | }, 18 | { 19 | "contractName": "SequenceUtils", 20 | "address": "0xdbbFa3cB3B087B64F4ef5E3D20Dda2488AA244e6" 21 | } 22 | ] -------------------------------------------------------------------------------- /networks/optimism.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "contractName": "WalletFactory", 4 | "address": "0xFaA5c0b14d1bED5C888Ca655B9a8A5911F78eF4A" 5 | }, 6 | { 7 | "contractName": "MainModule", 8 | "address": "0xfBf8f1A5E00034762D928f46d438B947f5d4065d" 9 | }, 10 | { 11 | "contractName": "MainModuleUpgradable", 12 | "address": "0x4222dcA3974E39A8b41c411FeDDE9b09Ae14b911" 13 | }, 14 | { 15 | "contractName": "GuestModule", 16 | "address": "0xfea230Ee243f88BC698dD8f1aE93F8301B6cdfaE" 17 | }, 18 | { 19 | "contractName": "SequenceUtils", 20 | "address": "0xdbbFa3cB3B087B64F4ef5E3D20Dda2488AA244e6" 21 | } 22 | ] -------------------------------------------------------------------------------- /networks/polygon.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "contractName": "WalletFactory", 4 | "address": "0xFaA5c0b14d1bED5C888Ca655B9a8A5911F78eF4A" 5 | }, 6 | { 7 | "contractName": "MainModule", 8 | "address": "0xfBf8f1A5E00034762D928f46d438B947f5d4065d" 9 | }, 10 | { 11 | "contractName": "MainModuleUpgradable", 12 | "address": "0x4222dcA3974E39A8b41c411FeDDE9b09Ae14b911" 13 | }, 14 | { 15 | "contractName": "GuestModule", 16 | "address": "0xfea230Ee243f88BC698dD8f1aE93F8301B6cdfaE" 17 | }, 18 | { 19 | "contractName": "SequenceUtils", 20 | "address": "0xdbbFa3cB3B087B64F4ef5E3D20Dda2488AA244e6" 21 | }, 22 | { 23 | "contractName": "TrustFactory", 24 | "address": "0x4483FaA9dEEDd6D6FaCFee9c686f1E394A1280f9" 25 | } 26 | ] -------------------------------------------------------------------------------- /networks/polygonZkevm.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "contractName": "WalletFactory", 4 | "address": "0xFaA5c0b14d1bED5C888Ca655B9a8A5911F78eF4A" 5 | }, 6 | { 7 | "contractName": "MainModule", 8 | "address": "0xfBf8f1A5E00034762D928f46d438B947f5d4065d" 9 | }, 10 | { 11 | "contractName": "MainModuleUpgradable", 12 | "address": "0x4222dcA3974E39A8b41c411FeDDE9b09Ae14b911" 13 | }, 14 | { 15 | "contractName": "GuestModule", 16 | "address": "0xfea230Ee243f88BC698dD8f1aE93F8301B6cdfaE" 17 | }, 18 | { 19 | "contractName": "SequenceUtils", 20 | "address": "0xdbbFa3cB3B087B64F4ef5E3D20Dda2488AA244e6" 21 | } 22 | ] -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@0xsequence/wallet-contracts", 3 | "version": "3.0.1", 4 | "private": true, 5 | "license": "Apache-2.0", 6 | "scripts": { 7 | "build": "pnpm compile && pnpm adapter", 8 | "compile": "hardhat --max-memory 4096 compile", 9 | "clean": "rimraf artifacts && rimraf cache", 10 | "typecheck": "tsc --noEmit", 11 | "test": "hardhat test", 12 | "benchmark": "BENCHMARK=true pnpm test", 13 | "coverage": "COVERAGE=true NET_ID=1 hardhat coverage", 14 | "deploy": "hardhat run utils/deploy-contracts.ts --network hardhat", 15 | "verify": "hardhat verify --network", 16 | "release": "pnpm publish src", 17 | "lint": "pnpm lint:ts && pnpm lint:sol", 18 | "lint:fix": "pnpm lint:ts:fix && pnpm lint:sol:fix", 19 | "lint:sol": "solhint \"./contracts/**/*.sol\"", 20 | "lint:sol:fix": "solhint \"./contracts/**/*.sol\" --fix", 21 | "lint:ts": "eslint -c .eslintrc.js \"./**/*.ts\"", 22 | "lint:ts:fix": "eslint -c .eslintrc.js --fix \"./**/*.ts\"", 23 | "format": "prettier --write ./**/*.ts", 24 | "adapter": "typechain --target ethers-v6 --out-dir gen/typechain \"./artifacts/contracts/**/*[^dbg].json\"", 25 | "prepare": "husky" 26 | }, 27 | "types": "gen/typechain/index.ts", 28 | "files": [ 29 | "./LICENSE", 30 | "./artifacts/contracts/**/*.json", 31 | "./contracts/**/*.sol", 32 | "./gen/typechain", 33 | "./networks" 34 | ], 35 | "husky": { 36 | "hooks": { 37 | "pre-commit": "pnpm lint", 38 | "pre-push": "pnpm lint && pnpm test" 39 | } 40 | }, 41 | "devDependencies": { 42 | "@nomicfoundation/hardhat-ethers": "^3.0.5", 43 | "@nomicfoundation/hardhat-verify": "^2.0.4", 44 | "@nomiclabs/hardhat-truffle5": "^2.0.7", 45 | "@nomiclabs/hardhat-web3": "^2.0.0", 46 | "@tenderly/hardhat-tenderly": "^1.0.11", 47 | "@types/chai": "^4.3.16", 48 | "@types/chai-as-promised": "^7.1.0", 49 | "@types/chai-string": "^1.4.1", 50 | "@types/mocha": "^8.2.1", 51 | "@typescript-eslint/eslint-plugin": "^4.18.0", 52 | "@typescript-eslint/parser": "^4.18.0", 53 | "bn-chai": "^1.0.1", 54 | "chai": "^4.3.4", 55 | "chai-as-promised": "^7.1.1", 56 | "chai-bignumber": "^3.0.0", 57 | "chai-shallow-deep-equal": "^1.4.6", 58 | "chai-string": "^1.5.0", 59 | "child_process": "^1.0.2", 60 | "dotenv": "^8.2.0", 61 | "eslint": "^7.22.0", 62 | "eslint-config-prettier": "^8.1.0", 63 | "eslint-plugin-import": "^2.22.0", 64 | "eslint-plugin-prettier": "^3.3.1", 65 | "ethereum-waffle": "^3.4.4", 66 | "ganache-cli": "6.12.2", 67 | "hardhat": "^2.20.1", 68 | "hardhat-gas-reporter": "1.0.10", 69 | "husky": "^9.0.11", 70 | "ora": "^5.4.1", 71 | "rimraf": "^3.0.2", 72 | "scrypt": "github:barrysteyn/node-scrypt#fb60a8d3c158fe115a624b5ffa7480f3a24b03fb", 73 | "solhint": "^3.4.1", 74 | "solidity-coverage": "0.8.3", 75 | "threads": "^1.7.0", 76 | "ts-node": "^10.9.1", 77 | "typechain": "^8.3.2", 78 | "typescript": "^4.7.4", 79 | "yesno": "^0.3.1" 80 | }, 81 | "config": { 82 | "mnemonic": "test test test test test test test test test test test junk", 83 | "ganacheChainID": 127001, 84 | "ganachePort": 8545, 85 | "ganacheGasLimit": "0xfffffffffff", 86 | "ganacheGasPrice": "2", 87 | "etherBalance": "100000", 88 | "extra": "" 89 | }, 90 | "dependencies": { 91 | "@typechain/ethers-v6": "^0.5.1", 92 | "ethers": "^6.13.0", 93 | "keccak256": "^1.0.6" 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /remappings.txt: -------------------------------------------------------------------------------- 1 | ds-test/=lib/forge-std/lib/ds-test/src/ 2 | forge-std/=lib/forge-std/src/ -------------------------------------------------------------------------------- /run_huff_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | for file in $(find ./src -type f -name '*.huff'); do 4 | echo "Testing $file..." 5 | output=$(huffc "$file" test 2>&1) 6 | echo "$output" 7 | 8 | # Check for the presence of [FAIL] that is not part of another word like [PASS] 9 | if echo "$output" | grep -E 'FAIL' > /dev/null; then 10 | echo "Failure detected in $file" 11 | exit 255 12 | else 13 | echo "Processed $file successfully" 14 | fi 15 | done 16 | 17 | echo "All tests completed" -------------------------------------------------------------------------------- /src/L2Compressor.huff: -------------------------------------------------------------------------------- 1 | #include "./L2CompressorLib.huff" 2 | 3 | 4 | #define jumptable SELECTORS_TABLE { 5 | execute_transaction // 0x00 6 | execute_many_transactions // 0x01 7 | read_address // 0x02 8 | read_bytes32 // 0x03 9 | sizes // 0x04 10 | read_storage_slots // 0x05 11 | decompress_transaction // 0x06 12 | decompress_many_transactions // 0x07 13 | } 14 | 15 | #define macro MAIN() = takes (0) returns (0) { 16 | // Write the jump table to 0x20 17 | // or else the flags jumptable won't get written 18 | // all this memory will be reused anyway 19 | __tablesize(SELECTORS_TABLE) // [table_size] 20 | __tablestart(SELECTORS_TABLE) // [table_start, table_size] 21 | 0x20 // [0x20, table_start, table_size] 22 | codecopy // [] 23 | 24 | callvalue // [0x00] 25 | calldataload // [data[0x00]] 26 | callvalue // [0x00, data[0x00]] 27 | byte // [method] 28 | 29 | 0x05 // [0x05, method] 30 | shl // [(method << 0x05)] 31 | 0x20 add // [method + 0x20] 32 | mload // [mload[method]] 33 | jump // [] 34 | 35 | execute_transaction: 36 | 0x01 // [rindex] 37 | PERFORM_EXECUTE(nrfs) // [rindex] 38 | callvalue callvalue return 39 | 40 | execute_many_transactions: 41 | 0x01 // [rindex] 42 | PERFORM_MANY_EXECUTES(nrfs) // [rindex, size, i] 43 | callvalue callvalue return 44 | 45 | read_address: 46 | PERFORM_READ_ADDRESS() 47 | 0x20 callvalue return 48 | 49 | read_bytes32: 50 | PERFORM_READ_BYTES32() 51 | 0x20 callvalue return 52 | 53 | sizes: 54 | PERFORM_READ_SIZES() 55 | 0x20 callvalue return 56 | 57 | read_storage_slots: 58 | PERFORM_READ_SLOTS() // [size] 59 | callvalue // [0x00, size] 60 | return 61 | 62 | decompress_transaction: 63 | 0x01 // [rindex] 64 | [FMS] // [windex, rindex] 65 | 66 | READ_FULL_EXECUTE(nrfs) // [windex, rindex] 67 | 68 | [FMS] // [FMS, windex, rindex] 69 | swap1 // [windex, FMS, rindex] 70 | sub // [(windex - FMS), rindex] 71 | 72 | [FMS] // [FMS, (windex - FMS), rindex] 73 | return // [rindex] 74 | 75 | decompress_many_transactions: 76 | 0x01 // [rindex] 77 | [FMS] // [windex, rindex] 78 | 79 | READ_MANY_FULL_EXECUTES(nrfs) // [windex, rindex] 80 | 81 | [FMS] // [FMS, windex, rindex] 82 | swap1 // [windex, FMS, rindex] 83 | sub // [(windex - FMS), rindex] 84 | 85 | [FMS] // [FMS, (windex - FMS), rindex] 86 | return // [rindex] 87 | 88 | // This will be appended at the end 89 | // unreachable code as all the method return first 90 | 91 | nrfs: 92 | FN_READ_FLAG(nrfs) 93 | } 94 | 95 | 96 | 97 | #define macro READ_FULL_EXECUTE(nrfs) = takes (2) returns (2) { 98 | // input stack: [windex, rindex] 99 | 100 | READ_EXECUTE() // [windex, rindex] 101 | PERFORM_NESTED_READ_FLAG() // [windex, rindex] 102 | 103 | // output stack: [windex, rindex] 104 | } 105 | 106 | #define macro READ_MANY_FULL_EXECUTES(nrfs) = takes (2) returns (2) { 107 | // input stack: [windex, rindex] 108 | 109 | swap1 // [rindex, windex] 110 | 111 | LOAD_1_BYTE() // [size, rindex, windex] 112 | callvalue // [i, size, rindex, windex] 113 | swap2 // [rindex, size, i, windex] 114 | 115 | do_another: // [rindex, size, i, windex] 116 | swap1 // [size, rindex, i, windex] 117 | swap2 // [i, rindex, size, windex] 118 | swap3 // [windex, rindex, size, i] 119 | 120 | READ_FULL_EXECUTE() // [windex, rindex, size, i] 121 | 122 | swap3 // [i, rindex, size, windex] 123 | swap2 // [size, rindex, i, windex] 124 | swap1 // [rindex, size, i, windex] 125 | 126 | swap2 // [i, size, rindex, windex] 127 | 0x01 // [0x01, i, size, rindex, windex] 128 | add // [(0x01 + i), size, rindex, windex] 129 | swap2 // [rindex, size, (0x01 + i), windex] 130 | 131 | dup2 // [size, rindex, size, (0x01 + i), windex] 132 | dup4 // [(0x01 + i), size, rindex, size, (0x01 + i), windex] 133 | lt // [((0x01 + i) < size), rindex, size, (0x01 + i), windex] 134 | do_another jumpi // [rindex, size, (0x01 + i), windex] 135 | 136 | swap1 // [size, rindex, (0x01 + i), windex] 137 | swap3 // [windex, rindex, (0x01 + i), size] 138 | 139 | // output stack: [windex, rindex, (0x01 + i), size] 140 | } -------------------------------------------------------------------------------- /src/imps/L2CompressorImps.huff: -------------------------------------------------------------------------------- 1 | #include "../L2CompressorLib.huff" 2 | 3 | #define function testLoadDynamicSize(bytes32 _a, bytes32 _b, uint256, uint256) view returns (uint256, uint256, bytes32) 4 | #define function testReadBytes32(bytes32 _a, bytes32 _b, uint256, uint256, uint256) view returns (uint256, uint256) 5 | 6 | // Function Dispatching 7 | #define macro MAIN() = takes (1) returns (1) { 8 | // Identify which function is being called. 9 | 0x00 calldataload 0xE0 shr // [func_sig] 10 | 11 | dup1 __FUNC_SIG(testLoadDynamicSize) eq testLoadDynamicSize jumpi 12 | dup1 __FUNC_SIG(testReadBytes32) eq testReadBytes32 jumpi 13 | 14 | // Revert if no match is found. 15 | 0x00 dup1 revert 16 | 17 | testLoadDynamicSize: 18 | IMP_LOAD_DYNAMIC_SIZE() 19 | 20 | testReadBytes32: 21 | IMP_READ_BYTES32() 22 | } 23 | 24 | #define macro IMP_LOAD_DYNAMIC_SIZE() = takes (2) returns (0) { 25 | 0x04 0x40 add calldataload // [rindex] 26 | 0x04 0x60 add calldataload // [size, rindex] 27 | 28 | LOAD_DYNAMIC_SIZE() // [size bits, rindex + size] 29 | 30 | 0x00 mstore // [rindex + size] 31 | 0x20 mstore // [] 32 | 33 | 0x40 0x00 return 34 | } 35 | 36 | #define macro IMP_READ_BYTES32() = takes (3) returns (2) { 37 | 0x04 0x40 add calldataload // [rindex] 38 | 0x04 0x60 add calldataload // [windex, rindex] 39 | 0x04 0x80 add calldataload // [flag, windex, rindex] 40 | 41 | READ_BYTES32() // [windex, rindex] 42 | 43 | 0x00 mstore // [rindex] 44 | 0x20 mstore // [] 45 | 46 | 0x04 0x60 add calldataload // [windex] 47 | mload // [written] 48 | 49 | 0x40 mstore // [] 50 | 51 | 0x60 0x00 return 52 | } -------------------------------------------------------------------------------- /src/imps/L2CompressorReadExecute.huff: -------------------------------------------------------------------------------- 1 | #include "../L2CompressorLib.huff" 2 | 3 | #define constant FMS = 0xa0 4 | 5 | // Function Dispatching 6 | #define macro MAIN() = takes (1) returns (1) { 7 | // readAdvanced with whatever calldata is passed 8 | // first 32 bytes returns the new rindex and the next 32 bytes returns the new windex 9 | 10 | 0x00 // [rindex] 11 | [FMS] // [windex, rindex] 12 | 13 | READ_EXECUTE_STANDALONE() // [windex, rindex] 14 | 15 | [FMS] // [0xa0, windex, rindex] 16 | dup2 // [windex, 0xa0, windex, rindex] 17 | sub // [len, windex, rindex] 18 | 19 | swap2 // [rindex, windex, len] 20 | 21 | 0x80 [FMS] sub mstore // [windex, len] 22 | 0x60 [FMS] sub mstore // [len] 23 | 24 | 0x60 0x40 [FMS] sub mstore // [len] 25 | dup1 0x20 [FMS] sub mstore // [len] 26 | 27 | 0x80 add // [len + 0x80] 28 | 29 | 0x80 [FMS] sub return 30 | } 31 | -------------------------------------------------------------------------------- /src/imps/L2CompressorReadFlag.huff: -------------------------------------------------------------------------------- 1 | #include "../L2CompressorLib.huff" 2 | 3 | #define constant FMS = 0xa0 4 | 5 | // Function Dispatching 6 | #define macro MAIN() = takes (1) returns (1) { 7 | // readAdvanced with whatever calldata is passed 8 | // first 32 bytes returns the new rindex and the next 32 bytes returns the new windex 9 | 10 | 0x00 // [rindex] 11 | [FMS] // [windex, rindex] 12 | 13 | READ_FLAG() // [windex, rindex] 14 | 15 | [FMS] // [0xa0, windex, rindex] 16 | dup2 // [windex, 0xa0, windex, rindex] 17 | sub // [len, windex, rindex] 18 | 19 | swap2 // [rindex, windex, len] 20 | 21 | 0x80 [FMS] sub mstore // [windex, len] 22 | 0x60 [FMS] sub mstore // [len] 23 | 24 | 0x60 0x40 [FMS] sub mstore // [len] 25 | dup1 0x20 [FMS] sub mstore // [len] 26 | 27 | 0x80 add // [len + 0x80] 28 | 29 | 0x80 [FMS] sub return 30 | } 31 | -------------------------------------------------------------------------------- /src/imps/L2CompressorReadNonce.huff: -------------------------------------------------------------------------------- 1 | #include "../L2CompressorLib.huff" 2 | 3 | #define constant FMS = 0xa0 4 | 5 | // Function Dispatching 6 | #define macro MAIN() = takes (1) returns (1) { 7 | // readAdvanced with whatever calldata is passed 8 | // first 32 bytes returns the new rindex and the next 32 bytes returns the new windex 9 | 10 | 0x00 // [rindex] 11 | [FMS] // [windex, rindex] 12 | 13 | READ_NONCE_STANDALONE() // [windex, rindex] 14 | 15 | [FMS] // [0xa0, windex, rindex] 16 | dup2 // [windex, 0xa0, windex, rindex] 17 | sub // [len, windex, rindex] 18 | 19 | swap2 // [rindex, windex, len] 20 | 21 | 0x80 [FMS] sub mstore // [windex, len] 22 | 0x60 [FMS] sub mstore // [len] 23 | 24 | 0x60 0x40 [FMS] sub mstore // [len] 25 | dup1 0x20 [FMS] sub mstore // [len] 26 | 27 | 0x80 add // [len + 0x80] 28 | 29 | 0x80 [FMS] sub return 30 | } 31 | -------------------------------------------------------------------------------- /src/imps/L2CompressorReadTx.huff: -------------------------------------------------------------------------------- 1 | #include "../L2CompressorLib.huff" 2 | 3 | #define constant FMS = 0xa0 4 | 5 | // Function Dispatching 6 | #define macro MAIN() = takes (1) returns (1) { 7 | // readAdvanced with whatever calldata is passed 8 | // first 32 bytes returns the new rindex and the next 32 bytes returns the new windex 9 | 10 | 0x00 // [rindex] 11 | [FMS] // [windex, rindex] 12 | 13 | READ_TRANSACTION_STANDALONE() // [windex, rindex] 14 | 15 | [FMS] // [0xa0, windex, rindex] 16 | dup2 // [windex, 0xa0, windex, rindex] 17 | sub // [len, windex, rindex] 18 | 19 | swap2 // [rindex, windex, len] 20 | 21 | 0x80 [FMS] sub mstore // [windex, len] 22 | 0x60 [FMS] sub mstore // [len] 23 | 24 | 0x60 0x40 [FMS] sub mstore // [len] 25 | dup1 0x20 [FMS] sub mstore // [len] 26 | 27 | 0x80 add // [len + 0x80] 28 | 29 | 0x80 [FMS] sub return 30 | } 31 | -------------------------------------------------------------------------------- /src/imps/L2CompressorReadTxs.huff: -------------------------------------------------------------------------------- 1 | #include "../L2CompressorLib.huff" 2 | 3 | #define constant FMS = 0xa0 4 | 5 | // Function Dispatching 6 | #define macro MAIN() = takes (1) returns (1) { 7 | // readAdvanced with whatever calldata is passed 8 | // first 32 bytes returns the new rindex and the next 32 bytes returns the new windex 9 | 10 | 0x00 // [rindex] 11 | [FMS] // [windex, rindex] 12 | 13 | READ_TRANSACTIONS_STANDALONE() // [windex, rindex] 14 | 15 | [FMS] // [0xa0, windex, rindex] 16 | dup2 // [windex, 0xa0, windex, rindex] 17 | sub // [len, windex, rindex] 18 | 19 | swap2 // [rindex, windex, len] 20 | 21 | 0x80 [FMS] sub mstore // [windex, len] 22 | 0x60 [FMS] sub mstore // [len] 23 | 24 | 0x60 0x40 [FMS] sub mstore // [len] 25 | dup1 0x20 [FMS] sub mstore // [len] 26 | 27 | 0x80 add // [len + 0x80] 28 | 29 | 0x80 [FMS] sub return 30 | } 31 | -------------------------------------------------------------------------------- /test/ERC165.spec.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from 'ethers' 2 | import { ContractType, deploySequenceContext, ERC165CheckerMock, SequenceContext } from './utils/contracts' 3 | import { SequenceWallet } from './utils/wallet' 4 | import { expect, interfaceIdOf, randomHex } from './utils' 5 | 6 | const interfaceIds = [ 7 | 'IERC223Receiver', 8 | 'IERC721Receiver', 9 | 'IERC1155Receiver', 10 | 'IERC1271Wallet', 11 | 'IModuleAuth', 12 | 'IModuleCalls', 13 | 'IModuleCreator', 14 | 'IModuleHooks', 15 | 'IModuleUpdate' 16 | ] 17 | 18 | contract('ERC165', () => { 19 | let context: SequenceContext 20 | let erc165checker: ContractType 21 | let wallet: SequenceWallet 22 | 23 | before(async () => { 24 | context = await deploySequenceContext() 25 | erc165checker = await ERC165CheckerMock.deploy() 26 | }) 27 | 28 | beforeEach(async () => { 29 | wallet = SequenceWallet.basicWallet(context) 30 | await wallet.deploy() 31 | }) 32 | 33 | describe('Implement all interfaces for ERC165 on MainModule', () => { 34 | interfaceIds.forEach(element => { 35 | it(`Should return implements ${element} interfaceId`, async () => { 36 | const interfaceId = interfaceIdOf(new ethers.Interface(artifacts.require(element).abi)) 37 | expect(BigInt(interfaceId) === 0n).to.be.false 38 | 39 | const erc165result = await erc165checker.doesContractImplementInterface(wallet.address, interfaceId) 40 | expect(erc165result).to.be.true 41 | }) 42 | }) 43 | }) 44 | describe('Implement all interfaces for ERC165 on MainModuleUpgradable', () => { 45 | beforeEach(async () => { 46 | await wallet.updateImageHash(randomHex(32)) 47 | }) 48 | 49 | interfaceIds.concat('IModuleAuthUpgradable').forEach(element => { 50 | it(`Should return implements ${element} interfaceId`, async () => { 51 | const interfaceId = interfaceIdOf(new ethers.Interface(artifacts.require(element).abi)) 52 | expect(BigInt(interfaceId) === 0n).to.be.false 53 | 54 | const erc165result = await erc165checker.doesContractImplementInterface(wallet.address, interfaceId) 55 | expect(erc165result).to.be.true 56 | }) 57 | }) 58 | }) 59 | describe('Manually defined interfaces', () => { 60 | const interfaces = [ 61 | ['ERC165', '0x01ffc9a7'], 62 | ['ERC721', '0x150b7a02'], 63 | ['ERC1155', '0x4e2312e0'] 64 | ] 65 | 66 | interfaces.forEach(i => { 67 | it(`Should implement ${i[0]} interface`, async () => { 68 | const erc165result = await erc165checker.doesContractImplementInterface(wallet.address, i[1]) 69 | expect(erc165result).to.be.true 70 | }) 71 | }) 72 | }) 73 | }) 74 | -------------------------------------------------------------------------------- /test/Factory.spec.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from 'ethers' 2 | 3 | import { ethers as hethers } from 'hardhat' 4 | import { addressOf } from './utils/sequence' 5 | import { expect, expectToBeRejected } from './utils' 6 | import { ContractType, Factory, ModuleMock } from './utils/contracts' 7 | 8 | contract('Factory', () => { 9 | let module: ContractType 10 | let factory: ContractType 11 | 12 | beforeEach(async () => { 13 | module = await ModuleMock.deploy() 14 | factory = await Factory.deploy() 15 | 16 | await module.waitForDeployment() 17 | await factory.waitForDeployment() 18 | }) 19 | 20 | describe('Deploy wallets', () => { 21 | it('Should deploy wallet', async () => { 22 | await factory.deploy( 23 | await module.getAddress(), 24 | ethers.AbiCoder.defaultAbiCoder().encode(['address'], [ethers.Wallet.createRandom().address]) 25 | ) 26 | await factory.waitForDeployment() 27 | }) 28 | 29 | it('Should predict wallet address', async () => { 30 | const hash = ethers.hexlify(ethers.randomBytes(32)) 31 | const predict = addressOf(await factory.getAddress(), await module.getAddress(), hash) 32 | await factory.deploy(await module.getAddress(), hash) 33 | await factory.waitForDeployment() 34 | expect(await hethers.provider.getCode(predict)).to.not.equal('0x') 35 | }) 36 | 37 | it('Should initialize with main module', async () => { 38 | const hash = ethers.hexlify(ethers.randomBytes(32)) 39 | await factory.deploy(await module.getAddress(), hash) 40 | const address = addressOf(await factory.getAddress(), await module.getAddress(), hash) 41 | const wallet = await ModuleMock.attach(address) 42 | const receipt = await (await wallet.ping()).wait() 43 | 44 | if (!receipt) { 45 | throw new Error('No receipt') 46 | } 47 | 48 | expect(wallet.interface.parseLog(receipt.logs[0])?.name).to.equal('Pong') 49 | }) 50 | 51 | it('Should fail to deploy twice', async () => { 52 | const hash = ethers.hexlify(ethers.randomBytes(32)) 53 | await factory.deploy(await module.getAddress(), hash) 54 | 55 | const tx2 = factory.deploy(await module.getAddress(), hash) 56 | await expectToBeRejected(tx2, `DeployFailed("${await module.getAddress()}", "${hash}")`) 57 | }) 58 | 59 | it('Should fail to deploy with not enough gas', async () => { 60 | const hash = ethers.hexlify(ethers.randomBytes(32)) 61 | const tx = factory.deploy(await module.getAddress(), hash, { gasLimit: 80000 }) 62 | await expectToBeRejected(tx, `DeployFailed("${await module.getAddress()}", "${hash}")`) 63 | }) 64 | }) 65 | }) 66 | -------------------------------------------------------------------------------- /test/GuestModule.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect, expectToBeRejected } from './utils' 2 | import { ethers as hethers } from 'hardhat' 3 | import { ethers } from 'ethers' 4 | import { CallReceiverMock, ContractType, GuestModule, HookCallerMock, MainModule } from './utils/contracts' 5 | import { applyTxDefaults, Transaction } from './utils/sequence' 6 | 7 | contract('GuestModule', () => { 8 | let guestModule: ContractType 9 | let callReceiver: ContractType 10 | let hookCallerMock: ContractType 11 | 12 | describe('GuestModule wallet', () => { 13 | before(async () => { 14 | guestModule = await GuestModule.deploy() 15 | callReceiver = await CallReceiverMock.deploy() 16 | hookCallerMock = await HookCallerMock.deploy() 17 | }) 18 | 19 | let valA: bigint 20 | let valB: string 21 | 22 | let transactions: Transaction[] 23 | 24 | beforeEach(async () => { 25 | valA = ethers.toBigInt(ethers.randomBytes(3)) 26 | valB = ethers.hexlify(ethers.randomBytes(120)) 27 | 28 | transactions = applyTxDefaults([ 29 | { 30 | target: await callReceiver.getAddress(), 31 | data: callReceiver.interface.encodeFunctionData('testCall', [valA, valB]) 32 | } 33 | ]) 34 | }) 35 | 36 | it('Should accept transactions without signature', async () => { 37 | await guestModule.execute(transactions, 0, new Uint8Array([])) 38 | 39 | expect(await callReceiver.lastValA()).to.equal(valA) 40 | expect(await callReceiver.lastValB()).to.equal(valB) 41 | }) 42 | it('Should accept transactions on selfExecute', async () => { 43 | await guestModule.selfExecute(transactions) 44 | 45 | expect(await callReceiver.lastValA()).to.equal(valA) 46 | expect(await callReceiver.lastValB()).to.equal(valB) 47 | }) 48 | it('Should accept transactions with random signature', async () => { 49 | const signature = ethers.randomBytes(96) 50 | 51 | await guestModule.execute(transactions, 0, signature) 52 | 53 | expect(await callReceiver.lastValA()).to.equal(valA) 54 | expect(await callReceiver.lastValB()).to.equal(valB) 55 | }) 56 | it('Should accept transactions with random nonce', async () => { 57 | const nonce = 9123891 58 | 59 | await guestModule.execute(transactions, nonce, new Uint8Array([])) 60 | 61 | expect(await callReceiver.lastValA()).to.equal(valA) 62 | expect(await callReceiver.lastValB()).to.equal(valB) 63 | }) 64 | it('Should revert on delegateCall transactions', async () => { 65 | const transactions = applyTxDefaults([ 66 | { 67 | delegateCall: true 68 | } 69 | ]) 70 | 71 | const tx = guestModule.selfExecute(transactions) 72 | await expectToBeRejected(tx, 'DelegateCallNotAllowed(0)') 73 | }) 74 | it('Should not accept ETH', async () => { 75 | const signer = await hethers.provider.getSigner() 76 | const tx = signer.sendTransaction({ value: 1, to: await guestModule.getAddress() }) 77 | await expect(tx).to.be.rejected 78 | }) 79 | it('Should not implement hooks', async () => { 80 | const tx = hookCallerMock.callERC1155Received(await guestModule.getAddress()) 81 | await expect(tx).to.be.rejected 82 | }) 83 | it('Should not be upgradeable', async () => { 84 | const mainModule = await MainModule.attach(await guestModule.getAddress()) 85 | const newImageHash = ethers.hexlify(ethers.randomBytes(32)) 86 | 87 | const migrateBundle = applyTxDefaults([ 88 | { 89 | target: await mainModule.getAddress(), 90 | data: mainModule.interface.encodeFunctionData('updateImageHash', [newImageHash]) 91 | } 92 | ]) 93 | 94 | const tx = guestModule.selfExecute(migrateBundle) 95 | await expect(tx).to.be.rejected 96 | }) 97 | }) 98 | }) 99 | -------------------------------------------------------------------------------- /test/LibString.spec.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from 'ethers' 2 | 3 | import { expect, expectStaticToBeRejected, randomHex } from './utils' 4 | import { ContractType, LibStringImp } from './utils/contracts' 5 | 6 | contract('LibString', () => { 7 | let libString: ContractType 8 | 9 | before(async () => { 10 | libString = await LibStringImp.deploy() 11 | }) 12 | 13 | describe('bytesToHexadecimal', () => { 14 | new Array(99).fill(0).map((_, i) => { 15 | it(`Should convert ${i} bytes to hexadecimal`, async () => { 16 | const bytes = randomHex(i) 17 | const expected = ethers.hexlify(bytes) 18 | const result = await libString.bytesToHexadecimal(bytes) 19 | expect(result).to.eq(expected.slice(2)) 20 | 21 | const prefixed = await libString.prefixHexadecimal(result) 22 | expect(prefixed).to.eq(expected) 23 | }) 24 | }) 25 | }) 26 | 27 | describe('bytesToBase32', () => { 28 | ;[ 29 | ['0x', 'b'], 30 | ['0x69', 'bne'], 31 | ['0x8775', 'bq52q'], 32 | ['0x91e0f3', 'bshqpg'], 33 | ['0x4867e789', 'bjbt6pci'], 34 | ['0x456fe65b8d', 'bivx6mw4n'], 35 | ['0xb20b0f525320', 'bwifq6ustea'], 36 | ['0xd292f6c85f4e5a', 'b2kjpnsc7jzna'], 37 | ['0xf78beb02b622adc7', 'b66f6wavwekw4o'], 38 | ['0xfad9c9851352f0ae0e', 'b7lm4tbitklyk4dq'], 39 | ['0x30b4bca5f67cc95312be', 'bgc2lzjpwptevgev6'], 40 | ['0xebc9fd66e4ae84644d502b', 'b5pe72zxev2cgitkqfm'], 41 | ['0x0f198dc28836e24782e8d182', 'bb4my3quig3repaxi2gba'], 42 | ['0x50059d23099f062187e9d52d8a', 'bkacz2iyjt4dcdb7j2uwyu'], 43 | ['0x4c0e454dc43d303c896ef5f9c449', 'bjqhektoehuydzclo6x44isi'], 44 | ['0x1ee41f4e940e4bf14df57a4fc06dd9', 'bd3sb6tuubzf7ctpvpjh4a3oz'], 45 | ['0x04a8d211d53b45d29fed9a624bb2d5e0', 'basuneeovhnc5fh7ntjrexmwv4a'], 46 | ['0x0a8b878364cdd555bda61db7c67503bb5e', 'bbkfypa3ezxkvlpngdw34m5idxnpa'], 47 | ['0x313b3c6e446ee6eb6477c0b0b01b70799cb6', 'bge5ty3sen3towzdxycylag3qpgolm'], 48 | ['0xe8be4a44970232babf052c3e75f611df8099c5', 'b5c7eurexaizlvpyffq7hl5qr36ajtri'], 49 | ['0x461eb18d034475a35d4084a89e0e3a47980f3408', 'biyplddidir22gxkaqsuj4dr2i6ma6nai'], 50 | ['0x519e139092a60229fe1a4db06d26fd199af9d39680', 'bkgpbheesuybct7q2jwyg2jx5dgnptu4wqa'], 51 | ['0x0fdd1a5696148daf6986505e2b3dc9af16ed91452720', 'bb7oruvuwcsg262mgkbpcwpojv4lo3ekfe4qa'], 52 | ['0x63a77d886c3f6593b6c6d37c7ab1f16ca3e34bd67e1ae1', 'bmotx3cdmh5szhnwg2n6hvmprnsr6gs6wpynoc'], 53 | ['0x1198cc96b431176fc2d5bc8a8c49ce041e184649ec3b9cbb', 'bcgmmzfvugelw7qwvxsfiysooaqpbqrsj5q5zzoy'], 54 | ['0x6b8d9af44a31c799ef835a522f8b630435c8e77e844360ad3d', 'bnogzv5ckghdzt34dljjc7c3daq24rz36qrbwblj5'], 55 | ['0xbb2cb7057a24b8588bafd87907ee7579b159027dc7224a3f6540', 'bxmwlobl2es4frc5p3b4qp3tvpgyvsat5y4reup3fia'], 56 | ['0x1f64b537d0e4b0158142dfa7cbfd9af76af0931862e48df02ba35b', 'bd5slkn6q4syblakc36t4x7m265vpbeyymlsi34blunnq'], 57 | ['0x11c5655f9d5496039fe6b1a9651014d4a19e62331d61e73b48fec776', 'bchcwkx45kslahh7gwguwkeau2sqz4yrtdvq6oo2i73dxm'], 58 | ['0x752c3c5cb4a3f06e4feff3f543278e97c7626569994b12b0d9ba9c7c83', 'bouwdyxfuupyg4t7p6p2ugj4os7dwezljtffrfmgzxkohzay'], 59 | ['0xb6bd83f8bba80bc8aca3706f7090cd5a0f0cc6adf4e89614bff58c149880', 'bw26yh6f3vaf4rlfdobxxbegnlihqzrvn6tujmff76wgbjgea'], 60 | ['0x90f4b198a1a1b2d3e01c36ba3460f46249f180dfa67b152f81552266a18708', 'bsd2ldgfbugznhya4g25diyhumje7dag7uz5rkl4bkurgnimhba'], 61 | [ 62 | '0x24cda83efac2802425b1c35a2d21b01576e71d0e512bcc80840f85f9e0af2faa', 63 | 'betg2qpx2ykacijnrynnc2inqcv3oohiokev4zaeeb6c7tyfpf6va' 64 | ], 65 | [ 66 | '0x169dc221f4cec8756de592c3205a23177867f18c383d2251bd1a5c03cc346c3fe8', 67 | 'bc2o4eipuz3ehk3pfslbsawrdc54gp4mmha6seun5djoahtbunq76q' 68 | ], 69 | [ 70 | '0x65b13b1aef3e44180314108b7f0434ddc23ad740a0df4df7984ad0bf1fdd84edcbaf', 71 | 'bmwytwgxphzcbqayuccfx6bbu3xbdvv2audpu354yjlil6h65qtw4xly' 72 | ] 73 | ].map(([bytes, base32Encoded]) => { 74 | it(`Should convert ${ethers.getBytes(bytes).length} bytes to base32`, async () => { 75 | const result = await libString.bytesToBase32(bytes) 76 | expect(result).to.equal(base32Encoded.slice(1)) 77 | 78 | const prefixed = await libString.prefixBase32(result) 79 | expect(prefixed).to.equal(base32Encoded) 80 | }) 81 | }) 82 | }) 83 | }) 84 | -------------------------------------------------------------------------------- /test/MerkleSignatures.spec.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from 'ethers' 2 | import { deploySequenceContext, SequenceContext } from './utils/contracts' 3 | import { legacyTopology, merkleTopology, printTopology, toSimplifiedConfig } from './utils/sequence' 4 | import { SequenceWallet } from './utils/wallet' 5 | 6 | contract('MerkleSignatures', () => { 7 | let context: SequenceContext 8 | 9 | before(async () => { 10 | context = await deploySequenceContext() 11 | }) 12 | 13 | it('Should display config topology', async () => { 14 | const wallet = SequenceWallet.basicWallet(context, { signing: 10 }) 15 | const simplifiedConfig = toSimplifiedConfig(wallet.config) 16 | const topology1 = legacyTopology(simplifiedConfig) 17 | const topology2 = merkleTopology(simplifiedConfig) 18 | 19 | console.log(`Legacy topology:`) 20 | const t = printTopology(topology1, undefined, true) 21 | for (const line of t) { 22 | console.log(line) 23 | } 24 | 25 | const t2 = printTopology(topology2) 26 | for (const line of t2) { 27 | console.log(line) 28 | } 29 | }) 30 | }) 31 | -------------------------------------------------------------------------------- /test/utils/contracts.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from 'ethers' 2 | import { ethers as hethers } from 'hardhat' 3 | import * as t from '../../gen/typechain' 4 | 5 | const cachedFactories: { [name: string]: ethers.ContractFactory } = {} 6 | 7 | async function deploy(name: string, ...args: any[]) { 8 | const factory = await hethers.getContractFactory(name) 9 | cachedFactories[name] = factory 10 | return (await factory.deploy(...args)) as unknown as Y 11 | } 12 | 13 | function attach(name: string, address: string) { 14 | return cachedFactories[name].attach(address) as Y 15 | } 16 | 17 | type Adapter = { 18 | cache: () => Promise 19 | deploy: (...args: any[]) => Promise 20 | attach: (address: string) => T 21 | factory: () => ethers.ContractFactory 22 | } 23 | 24 | function adapt(name: string): Adapter { 25 | return { 26 | cache: async () => (cachedFactories[name] = await hethers.getContractFactory(name)), 27 | deploy: (...args: any[]) => deploy(name, ...args), 28 | attach: (address: string) => attach(name, address), 29 | factory: () => cachedFactories[name] 30 | } 31 | } 32 | 33 | export type ContractType> = T extends Adapter ? U : never 34 | 35 | export const LibBytesImpl = adapt('LibBytesImpl') 36 | export const LibBytesPointerImpl = adapt('LibBytesPointerImpl') 37 | export const Factory = adapt('Factory') 38 | export const MainModule = adapt('MainModule') 39 | export const MainModuleUpgradable = adapt('MainModuleUpgradable') 40 | export const ERC165CheckerMock = adapt('ERC165CheckerMock') 41 | export const ModuleMock = adapt('ModuleMock') 42 | export const CallReceiverMock = adapt('CallReceiverMock') 43 | export const MultiCallUtils = adapt('MultiCallUtils') 44 | export const GuestModule = adapt('GuestModule') 45 | export const HookMock = adapt('HookMock') 46 | export const HookCallerMock = adapt('HookCallerMock') 47 | export const RequireUtils = adapt('RequireUtils') 48 | export const DelegateCallMock = adapt('DelegateCallMock') 49 | export const GasBurnerMock = adapt('GasBurnerMock') 50 | export const GasEstimator = adapt('GasEstimator') 51 | export const MainModuleGasEstimation = adapt('MainModuleGasEstimation') 52 | export const LibStringImp = adapt('LibStringImp') 53 | export const AlwaysRevertMock = adapt('AlwaysRevertMock') 54 | ;[ 55 | LibBytesImpl, 56 | Factory, 57 | MainModule, 58 | MainModuleUpgradable, 59 | ERC165CheckerMock, 60 | ModuleMock, 61 | CallReceiverMock, 62 | MultiCallUtils, 63 | GuestModule, 64 | HookMock, 65 | HookCallerMock, 66 | RequireUtils, 67 | DelegateCallMock, 68 | GasEstimator 69 | ].map(c => c.cache()) 70 | 71 | export const deploySequenceContext = async (owner?: string) => { 72 | const factory = await Factory.deploy() 73 | const mainModuleUpgradable = await MainModuleUpgradable.deploy() 74 | const mainModule = await MainModule.deploy(await factory.getAddress(), await mainModuleUpgradable.getAddress()) 75 | 76 | return { 77 | factory: await factory.waitForDeployment(), 78 | mainModule: await mainModule.waitForDeployment(), 79 | mainModuleUpgradable: await mainModuleUpgradable.waitForDeployment() 80 | } 81 | } 82 | 83 | export type SequenceContext = { 84 | factory: ContractType 85 | mainModule: ContractType 86 | mainModuleUpgradable: ContractType 87 | } 88 | -------------------------------------------------------------------------------- /test/utils/imposter.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from 'ethers' 2 | import { AnyStaticSigner, StaticSigner } from './wallet' 3 | 4 | export class Imposter extends ethers.AbstractSigner implements StaticSigner { 5 | static random(identity: string | AnyStaticSigner) { 6 | return new Imposter(identity, ethers.Wallet.createRandom()) 7 | } 8 | 9 | constructor( 10 | public identity: string | AnyStaticSigner, 11 | public signer: ethers.Signer 12 | ) { 13 | super() 14 | } 15 | 16 | get address() { 17 | return typeof this.identity === 'string' ? this.identity : this.identity.address 18 | } 19 | 20 | async getAddress(): Promise { 21 | return this.address 22 | } 23 | 24 | signMessage(message: string | Uint8Array): Promise { 25 | return this.signer.signMessage(message) 26 | } 27 | 28 | signTypedData( 29 | domain: ethers.TypedDataDomain, 30 | types: Record, 31 | value: Record 32 | ): Promise { 33 | return this.signer.signTypedData(domain, types, value) 34 | } 35 | 36 | signTransaction(transaction: ethers.TransactionRequest): Promise { 37 | return this.signer.signTransaction(transaction) 38 | } 39 | 40 | connect(provider: ethers.Provider): ethers.Signer { 41 | return this.signer.connect(provider) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /test/utils/index.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai' 2 | import chaiAsPromised from 'chai-as-promised' 3 | import chaiString from 'chai-string' 4 | import { ethers } from 'ethers' 5 | import { solidity } from 'ethereum-waffle' 6 | import { ethers as hethers } from 'hardhat' 7 | 8 | export const getChainId = async (): Promise => 9 | process.env.NET_ID ? BigInt(process.env.NET_ID) : (await hethers.provider.getNetwork()).chainId 10 | 11 | export const { assert, expect } = chai.use(chaiString).use(chaiAsPromised).use(solidity) 12 | 13 | export function bytes32toAddress(bytes32: ethers.BytesLike): string { 14 | const paddedValue = ethers.zeroPadValue(bytes32, 32) 15 | return ethers.getAddress(ethers.AbiCoder.defaultAbiCoder().decode(['address'], paddedValue)[0]) 16 | } 17 | 18 | export function shuffle(a: T[]): T[] { 19 | for (let i = a.length - 1; i > 0; i--) { 20 | const j = Math.floor(Math.random() * (i + 1)) 21 | ;[a[i], a[j]] = [a[j], a[i]] 22 | } 23 | 24 | return a 25 | } 26 | 27 | export function randomHex(length: number): string { 28 | return ethers.hexlify(ethers.randomBytes(length)) 29 | } 30 | 31 | export async function expectToBeRejected(promise: Promise, error: string) { 32 | if (!process.env.COVERAGE) { 33 | await expect(promise).to.be.rejectedWith(error) 34 | } else { 35 | await expect(promise).to.be.rejected 36 | } 37 | } 38 | 39 | export async function expectStaticToBeRejected(promise: Promise, signature: string, ...args: any[]) { 40 | // await expectToBeRejected(promise, `errorName="${signature.split('(')[0]}"`) 41 | // await expectToBeRejected(promise, `errorSignature="${signature}"`) 42 | 43 | await expectToBeRejected(promise, `${signature.split('(')[0]}`) 44 | 45 | const sigTypes = signature.split('(')[1].split(')')[0].split(',') 46 | 47 | expect(sigTypes.length).to.equal(args.length) 48 | 49 | const formattedArgs = args 50 | .map((arg, i) => { 51 | const type = sigTypes[i] 52 | 53 | if (arg === '*') return '*' 54 | 55 | switch (type) { 56 | case 'bytes': 57 | if (typeof arg === 'string' && arg.length === 0) { 58 | return ethers.hexlify(new Uint8Array([])) 59 | } 60 | return `"${ethers.hexlify(arg).toLowerCase()}"` 61 | case 'string': 62 | return `"${arg.toString()}"` 63 | } 64 | 65 | if (type.startsWith('uint') || type.startsWith('int')) { 66 | //return `{"type":"BigNumber","hex":"${ethers.toBeHex(BigInt(arg))}"}` 67 | return BigInt(arg).toString() 68 | } 69 | 70 | throw new Error(`Unknown type: ${type}`) 71 | }) 72 | .join(', ') 73 | 74 | const groups = formattedArgs.split('*') 75 | 76 | for (let i = 0; i < groups.length; i++) { 77 | await expectToBeRejected(promise, `${groups[i]}`) 78 | } 79 | 80 | // if (groups.length === 1) { 81 | // // await expectToBeRejected(promise, `errorArgs=[${formattedArgs}]`) 82 | // await expectToBeRejected(promise, `${formattedArgs}`) 83 | // } else { 84 | // for (let i = 0; i < groups.length; i++) { 85 | // const group = groups[i] 86 | // if (i === 0) { 87 | // // await expectToBeRejected(promise, `errorArgs=[${group}`) 88 | // await expectToBeRejected(promise, `${group}`) 89 | // } else if (i === groups.length - 1) { 90 | // await expectToBeRejected(promise, `${group}]`) 91 | // } else { 92 | // await expectToBeRejected(promise, `${group}`) 93 | // } 94 | // } 95 | // } 96 | } 97 | 98 | export function encodeError(error: string): string { 99 | return '0x08c379a0' + ethers.AbiCoder.defaultAbiCoder().encode(['string'], [error]).slice(2) 100 | } 101 | 102 | function xor(a: any, b: any) { 103 | if (!Buffer.isBuffer(a)) a = Buffer.from(ethers.getBytes(a)) 104 | if (!Buffer.isBuffer(b)) b = Buffer.from(ethers.getBytes(b)) 105 | return ethers.hexlify(a.map((v: number, i: number) => v ^ b[i])) 106 | } 107 | 108 | export function interfaceIdOf(int: ethers.Interface): string { 109 | const signatures: string[] = [] 110 | int.forEachFunction(fragment => { 111 | signatures.push(getSigHash(fragment)) 112 | }) 113 | return signatures.reduce((p, c) => xor(p, c)) 114 | } 115 | 116 | export function getSigHash(fragment: ethers.FunctionFragment): string { 117 | return ethers.dataSlice(ethers.id(fragment.format('sighash')), 0, 4) 118 | } 119 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "sourceMap": true, 7 | "allowSyntheticDefaultImports": true, 8 | "resolveJsonModule": true, 9 | "downlevelIteration": true, 10 | "removeComments": false, 11 | "skipLibCheck": true, 12 | 13 | "strictNullChecks": true, 14 | "noImplicitUseStrict": true, 15 | "noImplicitAny": false, 16 | "noImplicitReturns": true, 17 | "noImplicitThis": true, 18 | "noUnusedParameters": false, 19 | "noErrorTruncation": true, 20 | "esModuleInterop": true, 21 | 22 | "experimentalDecorators": true, 23 | "forceConsistentCasingInFileNames": true, 24 | "allowJs": false, 25 | "checkJs": false, 26 | 27 | "baseUrl": ".", 28 | "outDir": "build", 29 | "lib": ["es2020", "dom"], 30 | 31 | "typeRoots": ["./node_modules/@types"] 32 | }, 33 | 34 | "include": ["./hardhat.config.ts", "typings", "tests", "utils", "test"], 35 | 36 | "exclude": ["node_modules", "dist"] 37 | } 38 | -------------------------------------------------------------------------------- /typings/chai-bignumber.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare module 'chai-bignumber' { 4 | function chaiBignumber(bignumber: any): (chai: any, utils: any) => void 5 | 6 | namespace chaiBignumber {} 7 | 8 | export = chaiBignumber 9 | } 10 | 11 | declare namespace Chai { 12 | // For BDD API 13 | interface Assertion extends LanguageChains, NumericComparison, TypeComparison { 14 | bignumber: Assertion 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /typings/chai-bn.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /// 3 | 4 | declare module 'chai-bn' { 5 | function chaiBN(bignumber: any): (chai: any, utils: any) => void 6 | 7 | namespace chaiBN {} 8 | 9 | export = chaiBN 10 | } 11 | 12 | declare namespace Chai { 13 | interface Equal { 14 | BN: any 15 | } 16 | interface NumberComparer { 17 | BN: any 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /typings/truffle.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'truffle' { 2 | import * as truffle from 'truffle-contract' 3 | 4 | interface ArtifactsGlobal { 5 | require(name: string): truffle.TruffleContract 6 | } 7 | 8 | global { 9 | function contract(name: string, callback: (accounts: Array) => void): void 10 | const artifacts: ArtifactsGlobal 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /utils/benchmarker.ts: -------------------------------------------------------------------------------- 1 | 2 | import { spawn, Worker, Pool } from "threads" 3 | import { BenchWorker } from "./workers/bench-worker" 4 | 5 | import { task } from "hardhat/config" 6 | import { boolean, int } from "hardhat/internal/core/params/argumentTypes" 7 | import fs from "fs" 8 | 9 | class BufferSort { 10 | buffer: { i: number, val: string }[] = [] 11 | next: number = 0 12 | 13 | constructor(private onValue: (val: string) => void) {} 14 | 15 | feed = (i: number, val: string) => { 16 | this.buffer.push({ i, val }) 17 | this.buffer = this.buffer.sort((a, b) => a.i - b.i) 18 | 19 | while (this.buffer.length > 0 && this.buffer[0].i === this.next) { 20 | this.onValue(this.buffer[0].val) 21 | this.buffer.shift() 22 | this.next++ 23 | } 24 | } 25 | } 26 | 27 | async function main(args: { 28 | csv: string, 29 | topology: string, 30 | notrim: boolean, 31 | runs: number, 32 | minsign: number, 33 | maxsign: number, 34 | minidle: number, 35 | maxidle: number, 36 | cpus: number 37 | }) { 38 | let { csv, topology, notrim, runs, minsign, maxsign, minidle, maxidle, cpus } = args 39 | 40 | const disableTrim = notrim == true 41 | console.log(`Doing benchmark with params:`) 42 | console.log(` csv: ${csv}`) 43 | console.log(` topology: ${topology}`) 44 | console.log(` disableTrim: ${disableTrim}`) 45 | console.log(` runs: ${runs}`) 46 | console.log(` minSign: ${minsign}`) 47 | console.log(` maxSign: ${maxsign}`) 48 | console.log(` minIdle: ${minidle}`) 49 | console.log(` maxIdle: ${maxidle}`) 50 | console.log(` cpus: ${cpus}`) 51 | 52 | if (minidle > maxidle) throw new Error("minIdle must be <= maxIdle") 53 | if (minsign > maxsign) throw new Error("minSign must be <= maxSign") 54 | 55 | const pool = Pool(() => spawn(new Worker("./workers/bench-worker")), { size: cpus }) 56 | 57 | const file = fs.createWriteStream(csv, { flags: "a" }) 58 | const fileSorter = new BufferSort((val: string) => file.write(val)) 59 | const consoleSorter = new BufferSort((val: string) => console.log(val)) 60 | 61 | // Create CSV writter 62 | let batched = 0 63 | file.write("topology,disableTrim,signing,idle,runs,min,max,avg,cmin,cmax,cavg\n") 64 | for (let i = minsign; i < maxsign; i++) { 65 | for (let j = minidle; j < maxidle - 1; j++) { 66 | const absi = ((i - minsign) * maxidle) + j 67 | 68 | if (batched > cpus * 250) { 69 | await pool.settled() 70 | batched = 0 71 | } 72 | batched++ 73 | 74 | pool.queue(async worker => { 75 | await worker.setup(i, j, runs, topology as any, disableTrim) 76 | const r = await worker.run() 77 | 78 | fileSorter.feed(absi, `${topology},${disableTrim},${r.signing},${r.idle},${runs},${r.min},${r.max},${r.avg},${r.data.min},${r.data.max},${r.data.avg}\n`) 79 | consoleSorter.feed(absi, `${absi}: ${topology} (notrim ${disableTrim}): ${r.signing}/${r.signing + r.idle} (${runs}): min: ${r.min} max: ${r.max} avg: ${r.avg} cmin: ${r.data.min} cmax: ${r.data.max} cavg: ${r.data.avg}`) 80 | }) 81 | } 82 | } 83 | 84 | await pool.completed() 85 | await pool.terminate() 86 | file.close() 87 | } 88 | 89 | task("benchmark", "Runs sequence benchmarks") 90 | .addParam("csv", "The CSV file to write to", `benchmark-${Math.floor(Date.now())}.csv`) 91 | .addParam("topology", "The wallet topology to use", "legacy") 92 | .addParam("runs", "The number of runs to perform", 10, int) 93 | .addParam("minsign", "The start of the range of signature members who are going to sign", 1, int) 94 | .addParam("maxsign", "The end of the range of signature members who are going to sign", 255, int) 95 | .addParam("minidle", "The start of the range of idle members on the config", 0, int) 96 | .addParam("maxidle", "The end of the range of idle members on the config", 255, int) 97 | .addParam("cpus", "The number of CPUs to use", 1, int) 98 | .addParam("notrim", "Disable trimming of redudant signature parts", false, boolean) 99 | .setAction(async (args) => { 100 | return main(args) 101 | }) 102 | -------------------------------------------------------------------------------- /utils/config-loader.ts: -------------------------------------------------------------------------------- 1 | import * as dotenv from 'dotenv' 2 | import * as path from 'path' 3 | import { HttpNetworkConfig } from 'hardhat/types' 4 | import { ethers } from 'ethers' 5 | 6 | type EthereumNetworksTypes = 7 | | 'mainnet' 8 | | 'ropsten' 9 | | 'kovan' 10 | | 'goerli' 11 | | 'polygon' 12 | | 'polygon-zkevm' 13 | | 'mumbai' 14 | | 'arbitrum' 15 | | 'arbitrum-goerli' 16 | | 'arbitrum-nova' 17 | | 'optimism' 18 | | 'bnb' 19 | | 'bnb-testnet' 20 | | 'gnosis' 21 | | 'avalanche' 22 | | 'avalanche-fuji' 23 | 24 | export const getEnvConfig = (env: string) => { 25 | const envFile = path.resolve(__dirname, `../config/${env}.env`) 26 | const envLoad = dotenv.config({ path: envFile }) 27 | 28 | if (envLoad.error) { 29 | return { ETH_MNEMONIC: 'client vendor advice erosion deny cute tree fatal fuel bless simple speed' } 30 | } 31 | 32 | return envLoad.parsed || {} 33 | } 34 | 35 | export const networkGasMultiplier = (network: EthereumNetworksTypes): number => { 36 | switch (network) { 37 | default: 38 | return 1 39 | } 40 | } 41 | 42 | export const networkRpcUrl = (network: EthereumNetworksTypes): string => { 43 | const config = getEnvConfig('PROD') 44 | 45 | switch (network) { 46 | case 'mumbai': 47 | return 'https://endpoints.omniatech.io/v1/matic/mumbai/public' 48 | 49 | case 'polygon': 50 | return 'https://nodes.sequence.app/polygon' 51 | 52 | case 'polygon-zkevm': 53 | return 'https://zkevm-rpc.com' 54 | 55 | case 'arbitrum': 56 | return 'https://endpoints.omniatech.io/v1/arbitrum/one/public' 57 | 58 | case 'arbitrum-goerli': 59 | return 'https://goerli-rollup.arbitrum.io/rpc' 60 | 61 | case 'arbitrum-nova': 62 | return 'https://nova.arbitrum.io/rpc' 63 | 64 | case 'optimism': 65 | return 'https://endpoints.omniatech.io/v1/op/mainnet/public' 66 | 67 | case 'bnb': 68 | return 'https://bsc-dataseed3.binance.org' 69 | 70 | case 'bnb-testnet': 71 | return 'https://endpoints.omniatech.io/v1/bsc/testnet/public' 72 | 73 | case 'gnosis': 74 | return 'https://gnosis-mainnet.public.blastapi.io' 75 | 76 | case 'avalanche': 77 | return 'https://endpoints.omniatech.io/v1/avax/mainnet/public' 78 | 79 | case 'avalanche-fuji': 80 | return 'https://endpoints.omniatech.io/v1/avax/fuji/public' 81 | 82 | default: 83 | return `https://${network}.infura.io/v3/${config['INFURA_API_KEY']}` 84 | } 85 | } 86 | 87 | export const networkChainId = (network: EthereumNetworksTypes): number => { 88 | switch (network) { 89 | case 'mainnet': 90 | return 1 91 | 92 | case 'ropsten': 93 | return 3 94 | 95 | case 'goerli': 96 | return 5 97 | 98 | case 'kovan': 99 | return 42 100 | 101 | case 'mumbai': 102 | return 80001 103 | 104 | case 'polygon': 105 | return 137 106 | 107 | case 'polygon-zkevm': 108 | return 1101 109 | 110 | case 'arbitrum': 111 | return 42161 112 | 113 | case 'arbitrum-goerli': 114 | return 421613 115 | 116 | case 'arbitrum-nova': 117 | return 42170 118 | 119 | case 'optimism': 120 | return 10 121 | 122 | case 'bnb': 123 | return 56 124 | 125 | case 'bnb-testnet': 126 | return 97 127 | 128 | case 'gnosis': 129 | return 100 130 | 131 | case 'avalanche': 132 | return 43114 133 | 134 | case 'avalanche-fuji': 135 | return 43113 136 | } 137 | } 138 | 139 | export const networkConfig = (network: EthereumNetworksTypes): HttpNetworkConfig & { etherscan?: string } => { 140 | const prodConfig = getEnvConfig('PROD') 141 | const networkConfig = getEnvConfig(network) 142 | return { 143 | url: networkRpcUrl(network), 144 | chainId: networkChainId(network), 145 | accounts: { 146 | mnemonic: networkConfig['ETH_MNEMONIC'] ?? prodConfig['ETH_MNEMONIC'], 147 | initialIndex: 0, 148 | count: 10, 149 | path: `m/44'/60'/0'/0`, 150 | passphrase: '' 151 | }, 152 | gas: 'auto', 153 | gasPrice: 'auto', 154 | gasMultiplier: networkGasMultiplier(network), 155 | timeout: 20000, 156 | httpHeaders: {}, 157 | etherscan: networkConfig['ETHERSCAN'] ?? prodConfig['ETHERSCAN'] 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /utils/workers/bench-worker.ts: -------------------------------------------------------------------------------- 1 | import { deploySequenceContext, SequenceContext } from '../../test/utils/contracts' 2 | import { expose } from 'threads/worker' 3 | import { SequenceWallet } from '../../test/utils/wallet' 4 | import { ethers } from 'ethers' 5 | import { legacyTopology, merkleTopology } from '../../test/utils/sequence' 6 | 7 | let context: SequenceContext 8 | let wallet: SequenceWallet 9 | 10 | let d_runs: number 11 | let d_idle: number 12 | let d_signing: number 13 | let d_disableTrim: boolean 14 | 15 | let topologyConverter: any 16 | 17 | let prevsnapshot: any 18 | 19 | function report2(values: ethers.BigNumberish[]) { 20 | const bns = values.map(v => BigInt(v)) 21 | 22 | const min = bns.reduce((a, b) => (a < b ? a : b)) 23 | const max = bns.reduce((a, b) => (a > b ? a : b)) 24 | const avg = bns.reduce((p, n) => (p + n) / BigInt(values.length)) 25 | 26 | return { min, max, avg } 27 | } 28 | 29 | const worker = { 30 | async setup(signing: number, idle: number, runs: number, topology: 'legacy' | 'merkle', disableTrim: boolean) { 31 | if (!context) { 32 | context = await deploySequenceContext() 33 | } 34 | 35 | d_runs = runs 36 | d_idle = idle 37 | d_signing = signing 38 | d_disableTrim = disableTrim 39 | 40 | if (topology !== 'legacy' && topology !== 'merkle') throw new Error('Invalid topology') 41 | topologyConverter = topology === 'legacy' ? legacyTopology : merkleTopology 42 | }, 43 | async run() { 44 | const results: ethers.BigNumberish[] = [] 45 | const calldatas: ethers.BigNumberish[] = [] 46 | 47 | for (let i = 0; i < d_runs; i++) { 48 | wallet = SequenceWallet.basicWallet(context, { 49 | signing: d_signing, 50 | idle: d_idle, 51 | topologyConverter, 52 | encodingOptions: { disableTrim: d_disableTrim } 53 | }) 54 | await wallet.deploy() 55 | 56 | const signature = await wallet.signTransactions([{}]) 57 | const tx = await wallet.relayTransactions([{}], signature) 58 | const receipt = await tx.wait() 59 | 60 | if (!receipt) { 61 | throw new Error('No receipt') 62 | } 63 | 64 | results.push(receipt.gasUsed) 65 | calldatas.push(ethers.getBytes(signature).length) 66 | } 67 | 68 | const report = report2(results) 69 | const reportCalldata = report2(calldatas) 70 | 71 | return { 72 | min: Number(report.min), 73 | max: Number(report.max), 74 | avg: Number(report.avg), 75 | data: { 76 | min: Number(reportCalldata.min), 77 | max: Number(reportCalldata.max), 78 | avg: Number(reportCalldata.avg) 79 | }, 80 | idle: d_idle, 81 | signing: d_signing, 82 | runs: d_runs 83 | } 84 | } 85 | } 86 | 87 | export type BenchWorker = typeof worker 88 | 89 | expose(worker) 90 | --------------------------------------------------------------------------------