├── .husky ├── .gitignore ├── commit-msg └── pre-commit ├── .prettierignore ├── .gitattributes ├── .github ├── CODEOWNERS ├── pull_request_template.md ├── issue_template.md └── workflows │ ├── release.yml │ ├── lint.yml │ ├── canary.yml │ └── tests.yml ├── src ├── templates │ ├── partials │ │ ├── import.hbs │ │ ├── constructor.hbs │ │ ├── external-or-public-function.hbs │ │ ├── state-variable.hbs │ │ ├── array-state-variable.hbs │ │ ├── mapping-state-variable.hbs │ │ └── internal-function.hbs │ ├── contract-template.hbs │ └── helper-template.hbs ├── index.ts ├── run.ts ├── types.ts ├── smock-foundry.ts ├── context.ts └── utils.ts ├── commitlint.config.js ├── .env.example ├── CHANGELOG.md ├── solidity ├── interfaces │ ├── IContractZ.sol │ ├── IContractB2Abstract.sol │ ├── IContractA.sol │ ├── IContractBAbstract.sol │ ├── IContractCA.sol │ ├── IContractB.sol │ └── IContractTest.sol ├── contracts │ ├── Lib.sol │ ├── utils │ │ ├── ContractH.sol │ │ ├── ContractE.sol │ │ ├── ContractB.sol │ │ ├── ContractC.sol │ │ ├── ContractBAbstract.sol │ │ ├── ContractA.sol │ │ ├── ContractI.sol │ │ ├── ContractD.sol │ │ ├── ContractJ.sol │ │ ├── ContractAbstract.sol │ │ ├── ContractF.sol │ │ └── ContractG.sol │ └── ContractTest.sol └── test │ ├── utils │ ├── ContractE.t.sol │ ├── ContractB.t.sol │ ├── ContractF.t.sol │ ├── ContractA.t.sol │ ├── ContractCA.t.sol │ ├── ContractBAbstract.t.sol │ ├── ContractD.t.sol │ ├── ContractAbstract.t.sol │ ├── ContractJ.t.sol │ └── ContractG.t.sol │ └── ContractTest.t.sol ├── remappings.txt ├── .mocharc.json ├── tsconfig.json ├── .gitignore ├── copy-templates.js ├── .prettierrc ├── .eslintrc.json ├── .gitmodules ├── foundry.toml ├── LICENSE ├── test ├── unit │ └── context │ │ ├── importContext.spec.ts │ │ ├── stateVariableContext.spec.ts │ │ ├── constructorContext.spec.ts │ │ ├── arrayVariableContext.spec.ts │ │ ├── externalOrPublicFunctionContext.spec.ts │ │ ├── internalFunctionContext.spec.ts │ │ └── mappingVariableContext.spec.ts └── mocks.ts ├── package.json └── README.md /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | src/templates 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity 2 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * defi-wonderland/default-codeowner -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | # 🤖 Linear 2 | 3 | Closes BES-XXX 4 | -------------------------------------------------------------------------------- /src/templates/partials/import.hbs: -------------------------------------------------------------------------------- 1 | {{#each import}} 2 | {{this}} 3 | {{/each}} 4 | -------------------------------------------------------------------------------- /src/templates/partials/constructor.hbs: -------------------------------------------------------------------------------- 1 | constructor({{parameters}}) {{contracts}} {} 2 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { extends: ['@commitlint/config-conventional'] }; 2 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no-install commitlint --edit $1 -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | MAINNET_RPC= 2 | MAINNET_DEPLOYER_PK= 3 | 4 | GOERLI_RPC= 5 | GOERLI_DEPLOYER_PK= 6 | 7 | ETHERSCAN_API_KEY= -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './context'; 2 | export * from './smock-foundry'; 3 | export * from './types'; 4 | export * from './utils'; 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Smock Foundry 2 | 3 | ## 1.0.1 4 | 5 | ### Patch Changes 6 | 7 | - 814163c: Adds release workflow and fixes issue with abstract contracts 8 | -------------------------------------------------------------------------------- /solidity/interfaces/IContractZ.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | 3 | interface IContractZ { 4 | // View 5 | function uintVariable() external view returns (uint256); 6 | } 7 | -------------------------------------------------------------------------------- /remappings.txt: -------------------------------------------------------------------------------- 1 | isolmate/=lib/isolmate/src 2 | forge-std/=lib/forge-std/src 3 | ds-test/=lib/ds-test/src 4 | 5 | contracts/=solidity/contracts 6 | interfaces/=solidity/interfaces 7 | test/=solidity/test 8 | -------------------------------------------------------------------------------- /solidity/interfaces/IContractB2Abstract.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | 3 | interface IContractB2Abstract { 4 | function undefinedInterfaceFunc2(uint256 _someNumber) external returns (bool _result); 5 | } 6 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | # 1. Build the contracts 5 | # 2. Stage build output 6 | # 2. Lint and stage style improvements 7 | yarn build && git add out && npx lint-staged -------------------------------------------------------------------------------- /.mocharc.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": ["ts-node/register"], 3 | "extension": [".ts"], 4 | "spec": "test/unit/**/*.spec.ts", 5 | "watch-files": ["src", "test"], 6 | "timeout": "10000", 7 | "exit": true 8 | } 9 | -------------------------------------------------------------------------------- /solidity/interfaces/IContractA.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | 3 | interface IContractA { 4 | // View 5 | function uintVariable() external view returns (uint256); 6 | 7 | // Logic 8 | function setVariablesA(uint256 _newValue) external returns (bool _result); 9 | } 10 | -------------------------------------------------------------------------------- /solidity/interfaces/IContractBAbstract.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | 3 | interface IContractBAbstract { 4 | function undefinedInterfaceFunc(uint256 _someNumber) external returns (bool _result); 5 | function uintVariable() external view returns (uint256 _uintVariable); 6 | } 7 | -------------------------------------------------------------------------------- /solidity/interfaces/IContractCA.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | 3 | interface IContractCA { 4 | // View 5 | function uint256Variable() external view returns (uint256); 6 | 7 | // Logic 8 | function setVariablesC(uint256 _newUint256) external returns (bool _result); 9 | } 10 | -------------------------------------------------------------------------------- /solidity/interfaces/IContractB.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | 3 | interface IContractB { 4 | // View 5 | function stringVariable() external view returns (string memory); 6 | 7 | // Logic 8 | function setVariablesB(string memory _newString) external returns (bool _result); 9 | } 10 | -------------------------------------------------------------------------------- /solidity/contracts/Lib.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | /** 5 | * @notice This contract library is for testing that libraries mocks are not generated 6 | */ 7 | library Lib { 8 | function add(uint256 a, uint256 b) public pure returns (uint256) { 9 | return a + b; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /solidity/contracts/utils/ContractH.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | 3 | /** 4 | * @notice This contract is for testing receive and fallback functions 5 | */ 6 | contract ContractH { 7 | address public owner; 8 | 9 | constructor(address _owner) { 10 | owner = _owner; 11 | } 12 | 13 | receive() external payable {} 14 | 15 | fallback() external payable {} 16 | } 17 | -------------------------------------------------------------------------------- /src/templates/contract-template.hbs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: {{#if license}}{{license}}{{else}}UNLICENSED{{/if}} 2 | pragma solidity ^0.8.0; 3 | 4 | import { Test } from '{{testImport}}'; 5 | import { {{~exportedSymbols~}} } from '{{sourceContractRelativePath}}'; 6 | {{importsContent}} 7 | 8 | contract Mock{{contractName}} is {{contractName}}, Test { 9 | {{content}} 10 | } 11 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "commonjs", 5 | "sourceMap": true, 6 | "declaration": true, 7 | "esModuleInterop": true, 8 | "removeComments": true, 9 | "outDir": "dist", 10 | "resolveJsonModule": true 11 | }, 12 | "include": ["src/*.ts", "src/**/*.hbs"], 13 | "exclude": ["node_modules", "dist"] 14 | } 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # General 2 | yarn-error.log 3 | node_modules 4 | 5 | # Build dist files 6 | dist 7 | 8 | # Vscode settings 9 | .vscode 10 | 11 | # Foundry files 12 | cache 13 | out-via-ir 14 | 15 | # Config files 16 | .env 17 | 18 | # Avoid ignoring gitkeep 19 | !/**/.gitkeep 20 | 21 | # Ignore foundry artifacts 22 | out/*/* 23 | 24 | # Ignore generated mock contracts 25 | solidity/test/smock/ 26 | 27 | # mac 28 | **/.DS_Store 29 | -------------------------------------------------------------------------------- /copy-templates.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | /** 5 | * Copies the src/templates folder to the dist/templates folder 6 | */ 7 | const copyTemplates = () => { 8 | try { 9 | // TODO: Make sure this copies all partials 10 | fs.cpSync('src/templates', 'dist/templates', { recursive: true, overwrite: true }); 11 | } catch (err) { 12 | console.error(err); 13 | } 14 | }; 15 | 16 | copyTemplates(); 17 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "overrides": [ 3 | { 4 | "files": ["**.ts", "**.js"], 5 | "options": { 6 | "printWidth": 145, 7 | "tabWidth": 2, 8 | "semi": true, 9 | "singleQuote": true, 10 | "useTabs": false, 11 | "endOfLine": "auto" 12 | } 13 | }, 14 | { 15 | "files": "**.json", 16 | "options": { 17 | "tabWidth": 2, 18 | "printWidth": 200 19 | } 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /solidity/contracts/utils/ContractE.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | 3 | import {ContractD} from './ContractD.sol'; 4 | 5 | /** 6 | * @notice This contract is for testing a contract with internal variables and inheritance from another contract 7 | */ 8 | contract ContractE is ContractD { 9 | uint256 internal _internalUintVar2; 10 | 11 | constructor(uint256 _uintVariable, uint256 _uintVariable2) ContractD(_uintVariable) { 12 | _internalUintVar2 = _uintVariable2; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /solidity/contracts/utils/ContractB.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | 3 | import {IContractB} from '../../interfaces/IContractB.sol'; 4 | 5 | /** 6 | * @notice This contract is for testing string variables and function with return values 7 | */ 8 | contract ContractB is IContractB { 9 | string public stringVariable; 10 | 11 | function setVariablesB(string memory _stringVariable) public returns (bool _result) { 12 | stringVariable = _stringVariable; 13 | _result = true; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /solidity/contracts/utils/ContractC.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | 3 | import {IContractCA} from '../../interfaces/IContractCA.sol'; 4 | 5 | /** 6 | * @notice This contract is for testing uint256 variables and function with return values 7 | */ 8 | contract ContractCA is IContractCA { 9 | uint256 public uint256Variable; 10 | 11 | function setVariablesC(uint256 _uint256Variable) public returns (bool _result) { 12 | uint256Variable = _uint256Variable; 13 | _result = true; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | # Description 2 | Describe the feature you'd like to see implemented or the bug you've encountered in as much details as possible. 3 | If it's a bug, specify the version you're using and attach screenshots, logs, etc. 4 | 5 | ## Steps to reproduce 6 | 1. `git clone [reproduction-repo-url]` 7 | 2. `yarn smock-foundry --contracts solidity/contracts` 8 | 3. [and so on...] 9 | 10 | ## Expected behavior 11 | What you expected to happen 12 | 13 | ## Actual behavior 14 | What actually happened 15 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "plugins": ["@typescript-eslint"], 5 | "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"], 6 | "rules": { 7 | "@typescript-eslint/no-var-requires": "off", 8 | "@typescript-eslint/quotes": [ 9 | "warn", 10 | "single", 11 | { 12 | "avoidEscape": true, 13 | "allowTemplateLiterals": true 14 | } 15 | ] 16 | }, 17 | 18 | "ignorePatterns": ["templates/**/*.hbs"] 19 | } 20 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/ds-test"] 2 | path = lib/ds-test 3 | url = https://github.com/dapphub/ds-test 4 | branch = master 5 | [submodule "lib/forge-std"] 6 | path = lib/forge-std 7 | url = https://github.com/brockelmore/forge-std 8 | branch = master 9 | [submodule "lib/isolmate"] 10 | path = lib/isolmate 11 | url = https://github.com/defi-wonderland/isolmate 12 | branch = main 13 | [submodule "lib/openzeppelin-contracts"] 14 | path = lib/openzeppelin-contracts 15 | url = https://github.com/openzeppelin/openzeppelin-contracts 16 | -------------------------------------------------------------------------------- /src/templates/partials/external-or-public-function.hbs: -------------------------------------------------------------------------------- 1 | {{#unless implemented}} 2 | function {{functionName}}({{inputs}}) {{visibility}} {{stateMutability}} override {{#if overrides}}{{overrides}}{{/if}} {{#if outputs}}returns ({{outputs}}){{/if}} {} 3 | {{/unless}} 4 | 5 | function mock_call_{{functionName}}({{parameters}}) public { 6 | vm.mockCall( 7 | address(this), 8 | abi.encodeWithSignature( 9 | '{{signature}}' 10 | {{#if inputs}}, {{#each inputNames}}{{this}}{{#unless @last}}, {{/unless}}{{/each}}{{/if}} 11 | ), 12 | abi.encode( 13 | {{#each outputNames}}{{this}}{{#unless @last}}, {{/unless}}{{/each}} 14 | ) 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /src/templates/helper-template.hbs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import {Test} from '{{testImport}}'; 5 | 6 | contract SmockHelper is Test { 7 | function deployMock( 8 | string memory _label, 9 | bytes memory _creationCode, 10 | bytes memory _encodedArgs 11 | ) internal returns (address _deployed) { 12 | bytes memory _bytecode = abi.encodePacked(_creationCode, _encodedArgs); 13 | assembly { 14 | mstore(0x0, _creationCode) 15 | _deployed := create2(0, add(_bytecode, 0x20), mload(_bytecode), 'Wonderland') 16 | } 17 | vm.label(_deployed, _label); 18 | vm.allowCheatcodes(_deployed); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /foundry.toml: -------------------------------------------------------------------------------- 1 | [fmt] 2 | line_length = 120 3 | tab_width = 2 4 | bracket_spacing = false 5 | int_types = 'long' 6 | quote_style = 'single' 7 | number_underscore = 'thousands' 8 | multiline_func_header = 'params_first' 9 | 10 | [profile.default] 11 | solc = '0.8.19' 12 | evm_version = 'paris' 13 | src = 'solidity' 14 | test = 'solidity/test' 15 | out = 'out' 16 | libs = ['lib'] 17 | fuzz_runs = 1000 18 | optimizer_runs = 10_000 19 | 20 | [profile.optimized] 21 | via_ir = true 22 | out = 'out-via-ir' 23 | fuzz_runs = 5000 24 | 25 | [profile.test] 26 | via_ir = true 27 | out = 'out-via-ir' 28 | fuzz_runs = 5000 29 | src = 'solidity/test' 30 | 31 | [rpc_endpoints] 32 | mainnet = '${MAINNET_RPC}' 33 | goerli = '${GOERLI_RPC}' 34 | -------------------------------------------------------------------------------- /solidity/contracts/utils/ContractBAbstract.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | 3 | import {IContractBAbstract} from '../../interfaces/IContractBAbstract.sol'; 4 | import {IContractB2Abstract} from '../../interfaces/IContractB2Abstract.sol'; 5 | 6 | /** 7 | * @notice This contract is for testing abstract contracts with multiple inherited unimplemented functions 8 | */ 9 | abstract contract ContractBAbstract is IContractBAbstract, IContractB2Abstract { 10 | uint256 public uintVariable; 11 | 12 | constructor(uint256 _uintVariable) { 13 | uintVariable = _uintVariable; 14 | } 15 | 16 | function setVariablesA(uint256 _newValue) public returns (bool _result) { 17 | uintVariable = _newValue; 18 | _result = true; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /solidity/contracts/utils/ContractA.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | 3 | import {IContractA} from '../../interfaces/IContractA.sol'; 4 | import {IContractZ} from '../../interfaces/IContractZ.sol'; 5 | import {ContractB} from './ContractB.sol'; 6 | 7 | /** 8 | * @notice This contract is for testing contracts with interfaces and inheritance 9 | */ 10 | contract ContractA is IContractA, ContractB { 11 | uint256 public uintVariable; 12 | 13 | constructor(uint256 _uintVariable) ContractB() { 14 | uintVariable = _uintVariable; 15 | } 16 | 17 | function setVariablesA(uint256 _newValue) public returns (bool _result) { 18 | uintVariable = _newValue; 19 | _result = true; 20 | } 21 | } 22 | 23 | contract ContractZ is IContractZ { 24 | uint256 public uintVariable; 25 | } 26 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | release: 9 | name: Release 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Checkout Repo 14 | uses: actions/checkout@v3 15 | 16 | - name: Install Node 17 | uses: actions/setup-node@v4 18 | with: 19 | registry-url: 'https://registry.npmjs.org' 20 | cache: 'yarn' 21 | 22 | - name: Install Dependencies 23 | run: yarn --frozen-lockfile --network-concurrency 1 24 | 25 | - name: Build 26 | run: yarn build 27 | 28 | - name: Publish 29 | run: yarn publish --access public --tag latest 30 | env: 31 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 32 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: [push] 4 | 5 | concurrency: 6 | group: ${{github.workflow}}-${{github.ref}} 7 | cancel-in-progress: true 8 | 9 | jobs: 10 | lint: 11 | name: Run Linters 12 | runs-on: ubuntu-latest 13 | 14 | strategy: 15 | matrix: 16 | node-version: [18.x] 17 | 18 | steps: 19 | - uses: actions/checkout@v3 20 | 21 | - name: Install Foundry 22 | uses: foundry-rs/foundry-toolchain@v1 23 | with: 24 | version: nightly 25 | 26 | - name: Use Node.js 27 | uses: actions/setup-node@v3 28 | with: 29 | node-version: ${{ matrix.node-version }} 30 | cache: "yarn" 31 | 32 | - name: Install dependencies 33 | run: yarn --frozen-lockfile --network-concurrency 1 34 | 35 | - run: yarn lint:check 36 | -------------------------------------------------------------------------------- /solidity/contracts/utils/ContractI.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | 3 | /** 4 | * @notice Simple Ownable contract 5 | */ 6 | contract Ownable { 7 | address public owner; 8 | 9 | constructor(address _owner) { 10 | owner = _owner; 11 | } 12 | } 13 | 14 | /** 15 | * @notice Simple Pausable contract 16 | */ 17 | contract Pausable { 18 | bool public paused; 19 | 20 | constructor(bool _paused) { 21 | paused = _paused; 22 | } 23 | } 24 | 25 | /** 26 | * @notice Testing abstract contract with multiple inherited contracts 27 | */ 28 | abstract contract ContractI is Ownable, Pausable {} 29 | 30 | /** 31 | * @notice Testing abstract contract with constructor and with multiple inherited contracts 32 | */ 33 | abstract contract ContractI2 is Ownable, Pausable { 34 | bool public boolean; 35 | 36 | constructor(bool _boolean) { 37 | boolean = _boolean; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /.github/workflows/canary.yml: -------------------------------------------------------------------------------- 1 | name: Canary release 2 | 3 | on: workflow_dispatch 4 | 5 | jobs: 6 | canary-publish: 7 | name: Publish Packages (canary) 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - name: Checkout Repo 12 | uses: actions/checkout@v3 13 | 14 | - name: Install Node 15 | uses: actions/setup-node@v4 16 | with: 17 | registry-url: 'https://registry.npmjs.org' 18 | cache: 'yarn' 19 | 20 | - name: Install Dependencies 21 | run: yarn --frozen-lockfile --network-concurrency 1 22 | 23 | - name: Build 24 | run: yarn build 25 | 26 | - name: Update version 27 | run: yarn version --new-version "0.0.0-${GITHUB_SHA::8}" --no-git-tag-version 28 | 29 | - name: Publish 30 | run: npm publish --access public --tag canary 31 | env: 32 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 33 | -------------------------------------------------------------------------------- /src/templates/partials/state-variable.hbs: -------------------------------------------------------------------------------- 1 | function set_{{setFunction.functionName}}({{setFunction.paramType}} _{{setFunction.paramName}}) public { 2 | {{setFunction.paramName}} = _{{setFunction.paramName}}; 3 | } 4 | 5 | {{#unless isInternal}} 6 | function mock_call_{{mockFunction.functionName}}({{mockFunction.paramType}} _value) public { 7 | vm.mockCall( 8 | address(this), 9 | abi.encodeWithSignature('{{mockFunction.functionName}}()'), 10 | abi.encode( 11 | {{#if isStruct}} 12 | {{#each mockFunction.structFields}} 13 | _value.{{this}}{{#unless @last}}, {{/unless}} 14 | {{/each}} 15 | {{else}} 16 | _value 17 | {{/if}} 18 | ) 19 | ); 20 | } 21 | {{/unless}} 22 | 23 | {{#if isInternal}} 24 | function call_{{setFunction.functionName}}() view public returns ({{setFunction.paramType}}) { 25 | return {{setFunction.functionName}}; 26 | } 27 | {{/if}} 28 | -------------------------------------------------------------------------------- /solidity/contracts/utils/ContractD.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | 3 | /** 4 | * @notice This contract is for testing functions with multiple return values 5 | */ 6 | contract ContractD { 7 | uint256 internal _internalUintVar; 8 | 9 | constructor(uint256 _uintVariable) { 10 | _internalUintVar = _uintVariable; 11 | } 12 | 13 | function _setInternalUintVar(uint256 _uintVariable) internal virtual returns (bool, uint256, string memory) { 14 | _internalUintVar = _uintVariable; 15 | return (true, 111, 'test'); 16 | } 17 | 18 | function _getVariables(uint256 _uintVariable) internal view virtual returns (bool, uint256, string memory) { 19 | return (true, 111, 'test'); 20 | } 21 | 22 | function _internalFuncNoInputNoOutput() internal virtual { 23 | _internalUintVar = 11; 24 | } 25 | 26 | function _internalViewFuncNoInputNoOutput() internal view virtual { 27 | uint256 __internalUintVar = _internalUintVar; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /solidity/test/utils/ContractE.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import {Test} from 'forge-std/Test.sol'; 5 | import {MockContractE} from 'test/smock/contracts/utils/MockContractE.sol'; 6 | import {SmockHelper} from 'test/smock/SmockHelper.sol'; 7 | 8 | contract UnitMockContractE is Test, SmockHelper { 9 | address internal _owner = makeAddr('owner'); 10 | MockContractE internal _contractTest; 11 | 12 | uint256 internal _initialValue = 5; 13 | uint256 internal _initialValue2 = 10; 14 | uint256 internal _newValue = 15; 15 | 16 | function setUp() public { 17 | vm.prank(_owner); 18 | 19 | _contractTest = MockContractE( 20 | deployMock('TestContractE', type(MockContractE).creationCode, abi.encode(_initialValue, _initialValue2)) 21 | ); 22 | } 23 | 24 | function test_Set_InternalUintVar2() public { 25 | _contractTest.set__internalUintVar2(_newValue); 26 | assertEq(_contractTest.call__internalUintVar2(), _newValue); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /solidity/contracts/utils/ContractJ.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity ^0.8.8; 3 | 4 | /** 5 | * @notice This contract is for testing user defined value types 6 | */ 7 | contract ContractJ { 8 | type MyUserType is uint256; 9 | 10 | MyUserType public myUserTypeVariable; 11 | 12 | MyUserType[] public myUserTypeArray; 13 | 14 | mapping(uint256 => MyUserType) public uint256ToMyUserType; 15 | 16 | mapping(uint256 => MyUserType[]) public uint256ToMyUserTypeArray; 17 | 18 | function add(MyUserType a, MyUserType b) internal view virtual returns (MyUserType) { 19 | return MyUserType.wrap(MyUserType.unwrap(a) + MyUserType.unwrap(b)); 20 | } 21 | 22 | function internalAdd(MyUserType a, MyUserType b) internal virtual returns (MyUserType) { 23 | myUserTypeVariable = add(a, b); 24 | return myUserTypeVariable; 25 | } 26 | 27 | function externalAdd(MyUserType a, MyUserType b) external returns (MyUserType) { 28 | myUserTypeVariable = add(a, b); 29 | return myUserTypeVariable; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright © 2023 Wonderland 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /src/run.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import yargs from 'yargs'; 4 | import { hideBin } from 'yargs/helpers'; 5 | import { generateMockContracts } from './index'; 6 | 7 | (async () => { 8 | const { contracts, mocks, ignore, root } = getProcessArguments(); 9 | generateMockContracts(root, contracts, mocks, ignore); 10 | })(); 11 | 12 | function getProcessArguments() { 13 | return yargs(hideBin(process.argv)) 14 | .options({ 15 | contracts: { 16 | describe: 'Contracts directories', 17 | demandOption: true, 18 | type: 'array', 19 | string: true, 20 | }, 21 | root: { 22 | describe: 'Root directory', 23 | default: '.', 24 | type: 'string', 25 | }, 26 | mocks: { 27 | describe: `Generated contracts directory`, 28 | default: './test/smock', 29 | type: 'string', 30 | }, 31 | ignore: { 32 | describe: 'Ignore directories', 33 | default: [], 34 | type: 'array', 35 | string: true, 36 | }, 37 | }) 38 | .parseSync(); 39 | } 40 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: [push] 4 | 5 | jobs: 6 | unit: 7 | name: Run tests 8 | runs-on: ubuntu-latest 9 | 10 | strategy: 11 | matrix: 12 | node-version: [18.x] 13 | 14 | steps: 15 | - name: Check out github repository 16 | uses: actions/checkout@v3 17 | with: 18 | fetch-depth: 1 19 | 20 | - name: Install Foundry 21 | uses: foundry-rs/foundry-toolchain@v1 22 | with: 23 | version: nightly 24 | 25 | - name: Use Node.js 26 | uses: actions/setup-node@v3 27 | with: 28 | node-version: ${{ matrix.node-version }} 29 | cache: "yarn" 30 | 31 | - name: Install dependencies 32 | run: yarn --frozen-lockfile 33 | 34 | - name: "Create env file" 35 | run: | 36 | touch .env 37 | echo MAINNET_RPC="${{ secrets.MAINNET_RPC }}" >> .env 38 | echo GOERLI_RPC="${{ secrets.GOERLI_RPC }}" >> .env 39 | cat .env 40 | 41 | - name: Build smock 42 | run: yarn build 43 | 44 | - name: Run tests 45 | run: yarn test 46 | -------------------------------------------------------------------------------- /solidity/contracts/utils/ContractAbstract.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | 3 | /** 4 | * @notice This contract is for testing abstract contracts with implemented and unimplemented functions 5 | */ 6 | abstract contract ContractAbstract { 7 | uint256 public uintVariable; 8 | 9 | constructor(uint256 _uintVariable) { 10 | uintVariable = _uintVariable; 11 | } 12 | 13 | function setVariablesA(uint256 _newValue) public returns (bool _result) { 14 | uintVariable = _newValue; 15 | _result = true; 16 | } 17 | 18 | function undefinedFunc(string memory _someText) public virtual returns (bool _result); 19 | function undefinedFuncNoInputNoOutput() public virtual; 20 | 21 | function undefinedViewFunc(string memory _someText) public view virtual returns (bool _result); 22 | function undefinedViewFuncNoInputNoOutput() public view virtual; 23 | 24 | function _undefinedInternalFunc(string memory _someText) internal virtual returns (bool _result); 25 | function _undefinedInternalFuncNoInputNoOutput() internal virtual; 26 | 27 | function _undefinedInternalViewFunc(string memory _someText) internal view virtual returns (bool _result); 28 | function _undefinedInternalViewFuncNoInputNoOutput() internal view virtual; 29 | } 30 | -------------------------------------------------------------------------------- /solidity/test/utils/ContractB.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import {Test} from 'forge-std/Test.sol'; 5 | import {MockContractB} from 'test/smock/contracts/utils/MockContractB.sol'; 6 | import {SmockHelper} from 'test/smock/SmockHelper.sol'; 7 | 8 | contract UnitMockContractB is Test, SmockHelper { 9 | address internal _owner = makeAddr('owner'); 10 | MockContractB internal _contractTest; 11 | 12 | string internal _newValue = 'new value'; 13 | 14 | function setUp() public { 15 | vm.prank(_owner); 16 | 17 | _contractTest = MockContractB(deployMock('TestContractB', type(MockContractB).creationCode, abi.encode())); 18 | } 19 | 20 | function test_Set_StringVariable() public { 21 | _contractTest.set_stringVariable(_newValue); 22 | assertEq(_contractTest.stringVariable(), _newValue); 23 | } 24 | 25 | function test_Call_StringVariable() public { 26 | _contractTest.mock_call_stringVariable(_newValue); 27 | assertEq(_contractTest.stringVariable(), _newValue); 28 | } 29 | 30 | function test_Call_SetVariablesB() public { 31 | bool _result = true; 32 | 33 | _contractTest.mock_call_setVariablesB(_newValue, _result); 34 | assertEq(_contractTest.setVariablesB(_newValue), _result); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /solidity/test/utils/ContractF.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import {Test} from 'forge-std/Test.sol'; 5 | import {MockContractF} from 'test/smock/contracts/utils/MockContractF.sol'; 6 | import {SmockHelper} from 'test/smock/SmockHelper.sol'; 7 | 8 | contract UnitMockContractF is Test, SmockHelper { 9 | address internal _owner = makeAddr('owner'); 10 | MockContractF internal _contractTest; 11 | 12 | uint256 internal _initialValue = 5; 13 | uint256 internal _newValue = 15; 14 | 15 | function setUp() public { 16 | vm.prank(_owner); 17 | 18 | _contractTest = MockContractF(deployMock('TestContractF', type(MockContractF).creationCode, abi.encode(_owner))); 19 | } 20 | 21 | function test_Call_SetVariablesA() public { 22 | vm.expectCall(address(_contractTest), abi.encodeCall(MockContractF.setVariablesA, _newValue)); 23 | _contractTest.mock_call_setVariablesA(_newValue, true); 24 | 25 | assert(_contractTest.setVariablesA(_newValue)); 26 | } 27 | 28 | function test_Call_SetVariablesB() public { 29 | vm.expectCall(address(_contractTest), abi.encodeCall(MockContractF.setVariablesB, _newValue)); 30 | _contractTest.mock_call_setVariablesB(_newValue, true); 31 | 32 | assert(_contractTest.setVariablesB(_newValue)); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /solidity/test/utils/ContractA.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import {Test} from 'forge-std/Test.sol'; 5 | import {MockContractA} from 'test/smock/contracts/utils/MockContractA.sol'; 6 | import {SmockHelper} from 'test/smock/SmockHelper.sol'; 7 | 8 | contract UnitMockContractA is Test, SmockHelper { 9 | address internal _owner = makeAddr('owner'); 10 | MockContractA internal _contractTest; 11 | 12 | uint256 internal _initialValue = 5; 13 | uint256 internal _newValue = 10; 14 | 15 | function setUp() public { 16 | vm.prank(_owner); 17 | 18 | _contractTest = 19 | MockContractA(deployMock('TestContractA', type(MockContractA).creationCode, abi.encode(_initialValue))); 20 | } 21 | 22 | function test_Set_UintVariable() public { 23 | _contractTest.set_uintVariable(_newValue); 24 | assertEq(_contractTest.uintVariable(), _newValue); 25 | } 26 | 27 | function test_Call_UintVariable() public { 28 | _contractTest.mock_call_uintVariable(_newValue); 29 | assertEq(_contractTest.uintVariable(), _newValue); 30 | } 31 | 32 | function test_Call_SetVariablesA() public { 33 | bool _result = true; 34 | 35 | _contractTest.mock_call_setVariablesA(_newValue, _result); 36 | assertEq(_contractTest.setVariablesA(_newValue), _result); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /solidity/test/utils/ContractCA.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import {Test} from 'forge-std/Test.sol'; 5 | import {MockContractCA} from 'test/smock/contracts/utils/MockContractCA.sol'; 6 | import {SmockHelper} from 'test/smock/SmockHelper.sol'; 7 | 8 | contract UnitMockContractCA is Test, SmockHelper { 9 | address internal _owner = makeAddr('owner'); 10 | MockContractCA internal _contractTest; 11 | 12 | uint256 internal _initialValue = 5; 13 | uint256 internal _newValue = 10; 14 | 15 | function setUp() public { 16 | vm.prank(_owner); 17 | 18 | _contractTest = 19 | MockContractCA(deployMock('TestContractCA', type(MockContractCA).creationCode, abi.encode(_initialValue))); 20 | } 21 | 22 | function test_Set_Uint256Variable() public { 23 | _contractTest.set_uint256Variable(_newValue); 24 | assertEq(_contractTest.uint256Variable(), _newValue); 25 | } 26 | 27 | function test_Call_Uint256Variable() public { 28 | _contractTest.mock_call_uint256Variable(_newValue); 29 | assertEq(_contractTest.uint256Variable(), _newValue); 30 | } 31 | 32 | function test_Call_SetVariablesC() public { 33 | bool _result = true; 34 | 35 | _contractTest.mock_call_setVariablesC(_newValue, _result); 36 | assertEq(_contractTest.setVariablesC(_newValue), _result); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /solidity/contracts/utils/ContractF.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | 3 | /** 4 | * @notice Simple Ownable contract 5 | */ 6 | contract Ownable { 7 | address public owner; 8 | 9 | constructor(address _owner) { 10 | owner = _owner; 11 | } 12 | } 13 | 14 | /** 15 | * @notice Simple abstract contract inheriting from a contract 16 | */ 17 | abstract contract ContractFAbstract2 is Ownable { 18 | function setVariablesB(uint256 _newValue) public virtual returns (bool _result); 19 | } 20 | 21 | /** 22 | * @notice Interface for ContractFAbstract 23 | */ 24 | interface IContractFAbstract { 25 | function QUORUM() external view returns (uint256 _quorum); 26 | function setVariablesA(uint256 _newValue) external returns (bool _result); 27 | } 28 | 29 | /** 30 | * @notice Contract for testing abstract contracts with a variable and a unimplemented function 31 | * while inheriting from another abstract contract and its interface 32 | */ 33 | abstract contract ContractFAbstract is IContractFAbstract, ContractFAbstract2 { 34 | uint256 public constant QUORUM = 70; 35 | 36 | function setVariablesA(uint256 _newValue) public virtual returns (bool _result); 37 | } 38 | 39 | /** 40 | * @notice Contract for testing abstract contracts with multiple inheritance of same functions 41 | * Also testing multiple levels of abstract inheritance 42 | */ 43 | abstract contract ContractF is IContractFAbstract, ContractFAbstract {} 44 | -------------------------------------------------------------------------------- /src/templates/partials/array-state-variable.hbs: -------------------------------------------------------------------------------- 1 | {{#unless isMultiDimensionalStruct}} 2 | function set_{{setFunction.functionName}}({{setFunction.arrayType}} _{{setFunction.paramName}}) public { 3 | {{#if isStructArray}} 4 | for (uint256 _i; _i < _{{setFunction.paramName}}.length; ++_i) { 5 | {{setFunction.functionName}}.push(_{{setFunction.paramName}}[_i]); 6 | } 7 | {{else}} 8 | {{setFunction.functionName}} = _{{setFunction.paramName}}; 9 | {{/if}} 10 | } 11 | 12 | {{#unless isInternal}} 13 | function mock_call_{{mockFunction.functionName}}({{#each dimensions}}uint256 _index{{@index}}, {{/each}} {{mockFunction.baseType}} _value) public { 14 | vm.mockCall( 15 | address(this), 16 | abi.encodeWithSignature( 17 | '{{mockFunction.functionName}}({{#each dimensions}}uint256{{#unless @last}},{{/unless}}{{/each}})', 18 | {{#each dimensions}}_index{{@index}}{{#unless @last}}, {{/unless}} {{/each}}), 19 | abi.encode( 20 | {{#if isStructArray}} 21 | {{#each mockFunction.structFields}} 22 | _value.{{this}}{{#unless @last}}, {{/unless}} 23 | {{/each}} 24 | {{else}} 25 | _value 26 | {{/if}} 27 | ) 28 | ); 29 | } 30 | {{/unless}} 31 | 32 | {{#if isInternal}} 33 | function call_{{setFunction.functionName}}() view public returns ({{setFunction.arrayType}}) { 34 | return {{setFunction.functionName}}; 35 | } 36 | {{/if}} 37 | {{/unless}} 38 | -------------------------------------------------------------------------------- /test/unit/context/importContext.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { importContext } from '../../../src/context'; 3 | import { mockImportDirective, mockSymbolAliases } from '../../mocks'; 4 | import { Identifier } from 'solc-typed-ast'; 5 | 6 | describe('importContext', () => { 7 | const absolutePath = '../contracts/MyContract.sol'; 8 | 9 | const defaultAttributes = { 10 | symbolAliases: mockSymbolAliases([]), 11 | absolutePath, 12 | }; 13 | 14 | it('returns the absolute path if there are no named imports', () => { 15 | const node = mockImportDirective(defaultAttributes); 16 | const context = importContext(node); 17 | 18 | expect(context).to.eql({ 19 | absolutePath, 20 | }); 21 | }); 22 | 23 | it('returns the named imports and the absolute path', () => { 24 | const symbolAliases = mockSymbolAliases([ 25 | { foreign: new Identifier(1, 'src', 'string', 'a', 2), local: null }, 26 | { foreign: new Identifier(1, 'src', 'string', 'b', 2), local: null }, 27 | { foreign: new Identifier(1, 'src', 'string', 'c', 2), local: null }, 28 | ]); 29 | const node = mockImportDirective({ ...defaultAttributes, symbolAliases }); 30 | const context = importContext(node); 31 | 32 | expect(context).to.eql({ 33 | namedImports: ['a', 'b', 'c'], 34 | absolutePath, 35 | }); 36 | }); 37 | 38 | it('returns the symbol aliases without names', () => { 39 | const symbolAliases = mockSymbolAliases([ 40 | { foreign: 1, local: null }, 41 | { foreign: 2, local: null }, 42 | { foreign: 3, local: null }, 43 | ]); 44 | const node = mockImportDirective({ ...defaultAttributes, symbolAliases }); 45 | const context = importContext(node); 46 | 47 | expect(context).to.eql({ 48 | namedImports: [1, 2, 3], 49 | absolutePath, 50 | }); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /src/templates/partials/mapping-state-variable.hbs: -------------------------------------------------------------------------------- 1 | {{#unless hasNestedMapping}} 2 | function set_{{setFunction.functionName}}( 3 | {{#each setFunction.keyTypes}}{{this}} _key{{@index}}, {{/each}} 4 | {{setFunction.valueType}} _value 5 | ) public { 6 | {{#if isStructArray}} 7 | for (uint256 _i; _i < _value.length; ++_i) { 8 | {{setFunction.functionName}} 9 | {{#each setFunction.keyTypes}}[_key{{@index}}]{{/each}}.push(_value[_i]); 10 | } 11 | {{else}} 12 | {{setFunction.functionName}} 13 | {{#each setFunction.keyTypes}}[_key{{@index}}]{{/each}} = _value; 14 | {{/if}} 15 | } 16 | 17 | {{#unless isInternal}} 18 | function mock_call_{{mockFunction.functionName}}( 19 | {{#each mockFunction.keyTypes}}{{this}} _key{{@index}}, {{/each}} 20 | {{#if isArray}}uint256 _index, {{mockFunction.baseType}}{{else}}{{mockFunction.valueType}}{{/if}} _value 21 | ) public { 22 | vm.mockCall( 23 | address(this), 24 | abi.encodeWithSignature( 25 | '{{mockFunction.functionName}}({{#each mockFunction.keyTypes}}{{this}}{{#unless @last}},{{/unless}}{{/each}}{{#if isArray}},uint256{{/if}})' 26 | {{#each mockFunction.keyTypes}}, _key{{@index}}{{/each}}{{#if isArray}}, _index{{/if}} 27 | ), 28 | abi.encode( 29 | {{#if isStruct}} 30 | {{#each mockFunction.structFields}}_value.{{this}}{{#unless @last}}, {{/unless}}{{/each}} 31 | {{else}} 32 | _value 33 | {{/if}} 34 | ) 35 | ); 36 | } 37 | {{/unless}} 38 | 39 | {{#if isInternal}} 40 | function call_{{setFunction.functionName}}({{#each setFunction.keyTypes}}{{this}} _key{{@index}}{{#unless @last}}, {{/unless}}{{/each}}) view public returns ({{setFunction.valueType}}) { 41 | return {{setFunction.functionName}}{{#each setFunction.keyTypes}}[_key{{@index}}]{{/each}}; 42 | } 43 | {{/if}} 44 | {{/unless}} 45 | -------------------------------------------------------------------------------- /solidity/test/utils/ContractBAbstract.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import {Test} from 'forge-std/Test.sol'; 5 | import {MockContractBAbstract} from 'test/smock/contracts/utils/MockContractBAbstract.sol'; 6 | import {SmockHelper} from 'test/smock/SmockHelper.sol'; 7 | 8 | contract UnitMockContractBAbstract is Test, SmockHelper { 9 | address internal _owner = makeAddr('owner'); 10 | MockContractBAbstract internal _contractTest; 11 | 12 | uint256 internal _initialValue = 5; 13 | uint256 internal _newValue = 10; 14 | bool internal _result = true; 15 | 16 | function setUp() public { 17 | vm.prank(_owner); 18 | 19 | _contractTest = MockContractBAbstract( 20 | deployMock('TestContractBAbstract', type(MockContractBAbstract).creationCode, abi.encode(_initialValue)) 21 | ); 22 | } 23 | 24 | function test_Set_UintVariable() public { 25 | _contractTest.set_uintVariable(_newValue); 26 | assertEq(_contractTest.uintVariable(), _newValue); 27 | } 28 | 29 | function test_Call_UintVariable() public { 30 | _contractTest.mock_call_uintVariable(_newValue); 31 | assertEq(_contractTest.uintVariable(), _newValue); 32 | } 33 | 34 | function test_Call_SetVariablesA() public { 35 | _contractTest.mock_call_setVariablesA(_newValue, _result); 36 | assertEq(_contractTest.setVariablesA(_newValue), _result); 37 | } 38 | 39 | function test_Call_UndefinedInterfaceFunc() public { 40 | vm.expectCall(address(_contractTest), abi.encodeCall(MockContractBAbstract.undefinedInterfaceFunc, (_newValue)), 1); 41 | 42 | _contractTest.mock_call_undefinedInterfaceFunc(_newValue, _result); 43 | assertEq(_contractTest.undefinedInterfaceFunc(_newValue), _result); 44 | } 45 | 46 | function test_Call_UndefinedInterfaceFunc2() public { 47 | vm.expectCall(address(_contractTest), abi.encodeCall(MockContractBAbstract.undefinedInterfaceFunc2, (_newValue)), 1); 48 | 49 | _contractTest.mock_call_undefinedInterfaceFunc2(_newValue, _result); 50 | assertEq(_contractTest.undefinedInterfaceFunc2(_newValue), _result); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@defi-wonderland/smock-foundry", 3 | "version": "1.5.1", 4 | "description": "Smock-style mocks for Foundry projects", 5 | "homepage": "https://github.com/defi-wonderland/smock-foundry#readme", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/defi-wonderland/smock-foundry.git" 9 | }, 10 | "license": "MIT", 11 | "author": "Wonderland", 12 | "main": "dist/run.js", 13 | "types": "dist/run.d.ts", 14 | "bin": "dist/run.js", 15 | "files": [ 16 | "dist" 17 | ], 18 | "scripts": { 19 | "build": "rm -rf dist && tsc -p tsconfig.json && node copy-templates.js", 20 | "lint:check": "yarn lint:ts-logic && forge fmt --check", 21 | "lint:fix": "sort-package-json && forge fmt && yarn lint:ts-logic --fix", 22 | "lint:ts-logic": "eslint ./src/**/*.ts", 23 | "smock-test": "node dist/run.js --contracts solidity --mocks solidity/test/smock --ignore solidity/test", 24 | "test": "forge build --skip test && cross-env mocha 'test/**/*.spec.ts' && yarn smock-test && forge test", 25 | "test:mocha": "cross-env mocha 'test/**/*.spec.ts'" 26 | }, 27 | "lint-staged": { 28 | "*.{js,css,md,ts,sol}": "forge fmt", 29 | "*.ts": "eslint ./src/**/*.ts", 30 | "package.json": "sort-package-json" 31 | }, 32 | "dependencies": { 33 | "fast-glob": "3.3.2", 34 | "handlebars": "4.7.7", 35 | "solc-typed-ast": "18.1.3", 36 | "yargs": "17.7.2" 37 | }, 38 | "devDependencies": { 39 | "@commitlint/cli": "17.0.3", 40 | "@commitlint/config-conventional": "17.0.3", 41 | "@types/chai": "4.3.5", 42 | "@types/chai-as-promised": "7.1.5", 43 | "@types/fs-extra": "11.0.1", 44 | "@types/mocha": "10.0.1", 45 | "@types/node": "20.2.3", 46 | "@types/yargs": "17.0.24", 47 | "@typescript-eslint/eslint-plugin": "5.59.6", 48 | "@typescript-eslint/parser": "5.59.6", 49 | "chai": "4.3.7", 50 | "chai-as-promised": "7.1.1", 51 | "cross-env": "7.0.3", 52 | "eslint": "8.40.0", 53 | "husky": "8.0.3", 54 | "lint-staged": "10", 55 | "mocha": "10.2.0", 56 | "prettier": "3.0.3", 57 | "sort-package-json": "2.4.1", 58 | "ts-node": "10.9.1", 59 | "typescript": "5.0.4" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/templates/partials/internal-function.hbs: -------------------------------------------------------------------------------- 1 | function mock_call_{{functionName}}({{parameters}}) public { 2 | vm.mockCall( 3 | address(this), 4 | abi.encodeWithSignature('{{signature}}'{{#if inputs}}, {{#each inputNames}}{{this}}{{#unless @last}}, {{/unless}}{{/each}}{{/if}}), 5 | abi.encode({{#each outputNames}}{{this}}{{#unless @last}}, {{/unless}}{{/each}}) 6 | ); 7 | } 8 | 9 | function {{functionName}}{{#if isPure}}Helper{{/if}}({{inputs}}) internal {{#if isView}}view{{/if}} {{#unless isPure}}override{{/unless}} {{#if outputs}}returns ({{outputs}}){{/if}} { 10 | (bool _success, bytes memory _data) = address(this).{{#if isView}}static{{/if}}call(abi.encodeWithSignature('{{signature}}'{{#if inputs}}, {{#each inputNames}}{{this}}{{#unless @last}}, {{/unless}}{{/each}}{{/if}})); 11 | 12 | if (_success) return abi.decode(_data, ({{#each outputTypes}}{{this}}{{#unless @last}}, {{/unless}}{{/each}})); 13 | 14 | {{#if implemented}} 15 | else return super.{{functionName}}({{#each inputNames}}{{this}}{{#unless @last}}, {{/unless}}{{/each}}); 16 | {{/if}} 17 | } 18 | 19 | function call_{{functionName}}({{inputs}}) public {{#if outputs}}returns ({{outputs}}){{/if}} { 20 | return {{functionName}}({{#each inputNames}}{{this}}{{#unless @last}}, {{/unless}}{{/each}}); 21 | } 22 | 23 | function expectCall_{{functionName}}({{#if inputs}}{{inputs}}{{/if}}) public { 24 | vm.expectCall(address(this), abi.encodeWithSignature('{{signature}}'{{#if inputs}}, {{#each inputNames}}{{this}}{{#unless @last}}, {{/unless}}{{/each}}{{/if}})); 25 | } 26 | 27 | {{#if isPure}} 28 | 29 | function _{{functionName}}CastToPure(function({{#each inputTypes}}{{this}}{{#unless @last}}, {{/unless}}{{/each}}) internal view {{#if outputs}}returns ({{explicitOutputTypes}}){{/if}} fnIn) 30 | internal 31 | pure 32 | returns (function({{#each inputTypes}}{{this}}{{#unless @last}}, {{/unless}}{{/each}}) internal pure {{#if outputs}}returns ({{explicitOutputTypes}}){{/if}} fnOut) 33 | { 34 | assembly { 35 | fnOut := fnIn 36 | } 37 | } 38 | 39 | function {{functionName}}({{inputs}}) internal pure override {{#if outputs}}returns ({{outputs}}){{/if}} { 40 | return _{{functionName}}CastToPure({{functionName}}Helper)({{#each inputNames}}{{this}}{{#unless @last}}, {{/unless}}{{/each}}); 41 | } 42 | 43 | {{/if}} -------------------------------------------------------------------------------- /solidity/contracts/utils/ContractG.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | 3 | /** 4 | * @notice This contract is for testing complex data structures with mappings using them 5 | * - Structs with nestep mapings 6 | * - Nested structs 7 | * - Nested structs arrays 8 | * - Structs with multiple structs 9 | */ 10 | contract ContractG { 11 | struct Set { 12 | bytes32[] _values; 13 | mapping(bytes32 value => uint256) _positions; 14 | } 15 | 16 | struct AddressSet { 17 | Set _inner; 18 | } 19 | 20 | struct AddressSets { 21 | Set[] _inner; 22 | } 23 | 24 | struct CommonStruct { 25 | uint256 _value; 26 | } 27 | 28 | struct ComplexStruct { 29 | string _name; 30 | CommonStruct _common; 31 | AddressSet _addressSet; 32 | Set _set; 33 | } 34 | 35 | struct NestedStruct { 36 | uint256 _counter; 37 | CommonStruct _common; 38 | } 39 | 40 | mapping(bytes32 _disputeId => bool _finished) public finished; 41 | 42 | mapping(bytes32 _disputeId => bool _finished) internal _finishedInternal; 43 | 44 | mapping(bytes32 _key1 => mapping(bytes32 _key2 => bool _finished)) internal _doubleFinishedInternal; 45 | 46 | mapping(bytes32 _disputeId => Set _votersSet) internal _votersA; 47 | 48 | mapping(bytes32 _disputeId => mapping(address => Set _votersSet)) internal _votersB; 49 | 50 | mapping(bytes32 _disputeId => AddressSet _votersSet) internal _votersC; 51 | 52 | mapping(bytes32 _disputeId => AddressSets _votersSets) internal _votersD; 53 | 54 | mapping(bytes32 _disputeId => ComplexStruct _complexStruct) internal _complexStructs; 55 | 56 | mapping(bytes32 _disputeId => NestedStruct _nestedStruct) public _nestedStructs; 57 | 58 | mapping(bytes32 _disputeId => NestedStruct _nestedStruct) internal _nestedStructsInternal; 59 | 60 | NestedStruct public nestedStruct; 61 | 62 | CommonStruct[] public structArray; 63 | 64 | CommonStruct[] internal _structArrayInternal; 65 | 66 | CommonStruct[][] public twoDimensionalStruct; 67 | 68 | CommonStruct[][][] public threeDimensionalStruct; 69 | 70 | uint256[][] public twoDimensionalArray; 71 | 72 | string[][] public twoDimensionalStringArray; 73 | 74 | uint256[][][] public threeDimensionalArray; 75 | 76 | function setNestedStruct(NestedStruct memory _nestedStruct) public { 77 | nestedStruct = _nestedStruct; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /solidity/test/utils/ContractD.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import {Test} from 'forge-std/Test.sol'; 5 | import {MockContractD} from 'test/smock/contracts/utils/MockContractD.sol'; 6 | import {SmockHelper} from 'test/smock/SmockHelper.sol'; 7 | 8 | contract UnitMockContractD is Test, SmockHelper { 9 | address internal _owner = makeAddr('owner'); 10 | MockContractD internal _contractTest; 11 | 12 | uint256 internal _initialValue = 5; 13 | uint256 internal _newValue = 10; 14 | string internal _someText = 'some text'; 15 | 16 | function setUp() public { 17 | vm.prank(_owner); 18 | 19 | _contractTest = 20 | MockContractD(deployMock('TestContractD', type(MockContractD).creationCode, abi.encode(_initialValue))); 21 | } 22 | 23 | function test_Set_InternalUintVar() public { 24 | _contractTest.set__internalUintVar(_newValue); 25 | assertEq(_contractTest.call__internalUintVar(), _newValue); 26 | } 27 | 28 | function test_Call_InternalUintVar() public { 29 | _contractTest.expectCall__setInternalUintVar(_initialValue); 30 | _contractTest.mock_call__setInternalUintVar(_initialValue, true, _newValue, _someText); 31 | 32 | (bool _success, uint256 _value, string memory _text) = _contractTest.call__setInternalUintVar(_initialValue); 33 | 34 | assert(_success); 35 | assertEq(_value, _newValue); 36 | assertEq(_text, _someText); 37 | } 38 | 39 | function test_Call_GetVariables() public { 40 | _contractTest.expectCall__getVariables(_initialValue); 41 | _contractTest.mock_call__getVariables(_initialValue, true, _newValue, _someText); 42 | 43 | (bool _success, uint256 _value, string memory _text) = _contractTest.call__getVariables(_initialValue); 44 | 45 | assert(_success); 46 | assertEq(_value, _newValue); 47 | assertEq(_text, _someText); 48 | } 49 | 50 | function test_Call_InternalFuncNoInputNoOutput() public { 51 | _contractTest.expectCall__internalFuncNoInputNoOutput(); 52 | _contractTest.mock_call__internalFuncNoInputNoOutput(); 53 | 54 | _contractTest.call__internalFuncNoInputNoOutput(); 55 | } 56 | 57 | function test_Call_InternalViewFuncNoInputNoOutput() public { 58 | _contractTest.expectCall__internalViewFuncNoInputNoOutput(); 59 | _contractTest.mock_call__internalViewFuncNoInputNoOutput(); 60 | 61 | _contractTest.call__internalViewFuncNoInputNoOutput(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /solidity/interfaces/IContractTest.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | contract MyContract {} 5 | 6 | /** 7 | * @notice This interface is for general testing 8 | * @dev It contains the most common variables types and functions 9 | */ 10 | interface IContractTest { 11 | // Structs 12 | struct MyStruct { 13 | uint256 value; 14 | string name; 15 | } 16 | 17 | // Enums 18 | enum MyEnum { 19 | A, 20 | B, 21 | C 22 | } 23 | 24 | // View 25 | function uintVariable() external view returns (uint256); 26 | function stringVariable() external view returns (string memory); 27 | function boolVariable() external view returns (bool); 28 | function addressVariable() external view returns (address); 29 | function bytes32Variable() external view returns (bytes32); 30 | function myStructVariable() external view returns (uint256, string memory); 31 | function myEnumVariable() external view returns (MyEnum); 32 | function myContractVariable() external view returns (MyContract); 33 | function addressArray(uint256) external view returns (address); 34 | function uint256Array(uint256) external view returns (uint256); 35 | function bytes32Array(uint256) external view returns (bytes32); 36 | function myStructArray(uint256) external view returns (uint256, string memory); 37 | function uint256ToAddress(uint256) external view returns (address); 38 | function addressToUint(address) external view returns (uint256); 39 | function bytes32ToBytes(bytes32) external view returns (bytes memory); 40 | function uint256ToMyStruct(uint256) external view returns (uint256, string memory); 41 | function uint256ToAddressArray(uint256, uint256) external view returns (address); 42 | function uint256ToMyStructArray(uint256, uint256) external view returns (uint256, string memory); 43 | function uint256ToAddressToBytes32(uint256, address) external view returns (bytes32); 44 | function immutableUintVariable() external view returns (uint256); 45 | // Logic 46 | function setVariables( 47 | uint256 _newValue, 48 | string memory _newString, 49 | bool _newBool, 50 | address _newAddress, 51 | bytes32 _newBytes32, 52 | address[] memory _addressArray, 53 | uint256[] memory _uint256Array, 54 | bytes32[] memory _bytes32Array 55 | ) external returns (bool _result); 56 | 57 | function setVariables(uint256) external returns (bool); 58 | 59 | function setVariables(uint256, bool) external returns (bool); 60 | 61 | function testFunc(uint256) external returns (bool); 62 | } 63 | -------------------------------------------------------------------------------- /test/mocks.ts: -------------------------------------------------------------------------------- 1 | import { 2 | FunctionDefinition, 3 | VariableDeclaration, 4 | ParameterList, 5 | ContractDefinition, 6 | ImportDirective, 7 | SymbolAlias, 8 | Identifier, 9 | TypeName, 10 | ArrayTypeName, 11 | Mapping, 12 | UserDefinedTypeName, 13 | UserDefinedValueTypeDefinition, 14 | ElementaryTypeName, 15 | } from 'solc-typed-ast'; 16 | 17 | export function mockFunctionDefinition(mockFunctionDefinition: Partial): FunctionDefinition { 18 | return mockFunctionDefinition as FunctionDefinition; 19 | } 20 | 21 | export function mockParameterList(mockParameterList: Partial): ParameterList { 22 | return mockParameterList as ParameterList; 23 | } 24 | 25 | export function mockVariableDeclaration(mockVariableDeclaration: Partial): VariableDeclaration { 26 | return mockVariableDeclaration as VariableDeclaration; 27 | } 28 | 29 | export function mockContractDefinition(mockContractDefinition: Partial): ContractDefinition { 30 | return mockContractDefinition as ContractDefinition; 31 | } 32 | 33 | export function mockImportDirective(mockImportDirective: Partial): ImportDirective { 34 | return mockImportDirective as ImportDirective; 35 | } 36 | 37 | export function mockSymbolAliases(mockSymbolAliases: Partial): SymbolAlias[] { 38 | return mockSymbolAliases as SymbolAlias[]; 39 | } 40 | 41 | export function mockIdentifier(mockIdentifier: Partial): Identifier { 42 | return mockIdentifier as Identifier; 43 | } 44 | 45 | export function mockArrayTypeName(mockArrayTypeName: Partial): ArrayTypeName { 46 | return mockArrayTypeName as ArrayTypeName; 47 | } 48 | 49 | export function mockTypeName(mockTypeName: Partial): TypeName { 50 | return mockTypeName as TypeName; 51 | } 52 | 53 | export function mockMapping(mockMapping: Partial): Mapping { 54 | return mockMapping as Mapping; 55 | } 56 | 57 | export function mockUserDefinedTypeName(mockTypeName: Partial): UserDefinedTypeName { 58 | return mockTypeName as UserDefinedTypeName; 59 | } 60 | 61 | export function mockUserDefinedValueTypeDefinition( 62 | mockUserDefinedValueTypeDefinition: Partial, 63 | ): UserDefinedValueTypeDefinition { 64 | return mockUserDefinedValueTypeDefinition as UserDefinedValueTypeDefinition; 65 | } 66 | 67 | export function mockElementaryTypeName(mockElementaryTypeName: Partial): ElementaryTypeName { 68 | return mockElementaryTypeName as ElementaryTypeName; 69 | } 70 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import { FunctionDefinition } from 'solc-typed-ast'; 2 | 3 | export const userDefinedTypes = ['contract', 'enum', 'struct']; 4 | export const explicitTypes = ['string', 'bytes', 'mapping', 'struct']; 5 | 6 | // Contexts to pass to Handlebars templates 7 | export interface ConstructorContext { 8 | parameters: string; 9 | contracts: string; 10 | } 11 | 12 | export interface ExternalFunctionContext { 13 | functionName: string; 14 | signature: string; 15 | parameters: string; 16 | inputs: string; 17 | outputs: string; 18 | inputNames: string[]; 19 | outputNames: string[]; 20 | visibility: string; 21 | stateMutability: string; 22 | implemented: boolean; 23 | overrides?: string; 24 | } 25 | 26 | export interface InternalFunctionContext extends Omit { 27 | inputTypes: string[]; 28 | outputTypes: string[]; 29 | explicitOutputTypes: string[]; 30 | isView: boolean; 31 | isPure: boolean; 32 | } 33 | 34 | export interface ImportContext { 35 | absolutePath: string; 36 | namedImports?: (string | number)[]; 37 | } 38 | 39 | export interface MappingVariableContext { 40 | setFunction: { 41 | functionName: string; 42 | keyTypes: string[]; 43 | valueType: string; 44 | }; 45 | mockFunction: { 46 | functionName: string; 47 | keyTypes: string[]; 48 | valueType: string; 49 | baseType: string; 50 | structFields?: string[]; 51 | }; 52 | isInternal: boolean; 53 | isArray: boolean; 54 | isStruct: boolean; 55 | isStructArray: boolean; 56 | hasNestedMapping: boolean; 57 | } 58 | 59 | export interface ArrayVariableContext { 60 | setFunction: { 61 | functionName: string; 62 | arrayType: string; 63 | paramName: string; 64 | }; 65 | mockFunction: { 66 | functionName: string; 67 | arrayType: string; 68 | baseType: string; 69 | structFields?: string[]; 70 | }; 71 | isInternal: boolean; 72 | isStructArray: boolean; 73 | isMultiDimensionalStruct: boolean; 74 | dimensions: number[]; 75 | } 76 | 77 | export interface StateVariableContext { 78 | isInternal: boolean; 79 | isStruct: boolean; 80 | setFunction: { 81 | functionName: string; 82 | paramType: string; 83 | paramName: string; 84 | }; 85 | mockFunction: { 86 | functionName: string; 87 | paramType: string; 88 | structFields?: string[]; 89 | }; 90 | } 91 | interface Selector { 92 | implemented: boolean; 93 | contracts?: Set; 94 | function?: FunctionDefinition; 95 | constructors?: FunctionDefinition[]; 96 | } 97 | 98 | export interface SelectorsMap { 99 | [selector: string]: Selector; 100 | } 101 | 102 | export type FullFunctionDefinition = FunctionDefinition & { selectors: SelectorsMap }; 103 | -------------------------------------------------------------------------------- /solidity/test/utils/ContractAbstract.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import {Test} from 'forge-std/Test.sol'; 5 | import {MockContractAbstract} from 'test/smock/contracts/utils/MockContractAbstract.sol'; 6 | import {SmockHelper} from 'test/smock/SmockHelper.sol'; 7 | 8 | contract UnitMockContractAbstract is Test, SmockHelper { 9 | address internal _owner = makeAddr('owner'); 10 | MockContractAbstract internal _contractTest; 11 | 12 | uint256 internal _initialValue = 5; 13 | uint256 internal _newValue = 10; 14 | string internal _someText = 'some text'; 15 | bool internal _result = true; 16 | 17 | function setUp() public { 18 | vm.prank(_owner); 19 | 20 | _contractTest = MockContractAbstract( 21 | deployMock('TestContractAbstract', type(MockContractAbstract).creationCode, abi.encode(_initialValue)) 22 | ); 23 | } 24 | 25 | function test_Set_UintVariable() public { 26 | _contractTest.set_uintVariable(_newValue); 27 | assertEq(_contractTest.uintVariable(), _newValue); 28 | } 29 | 30 | function test_Call_UintVariable() public { 31 | _contractTest.mock_call_uintVariable(_newValue); 32 | assertEq(_contractTest.uintVariable(), _newValue); 33 | } 34 | 35 | function test_Call_SetVariablesA() public { 36 | _contractTest.mock_call_setVariablesA(_newValue, _result); 37 | assertEq(_contractTest.setVariablesA(_newValue), _result); 38 | } 39 | 40 | function test_Call_UndefinedFunc() public { 41 | _contractTest.mock_call_undefinedFunc(_someText, _result); 42 | assertEq(_contractTest.undefinedFunc(_someText), _result); 43 | } 44 | 45 | function test_Call_UndefinedFuncNoInputNoOutput() public { 46 | vm.expectCall(address(_contractTest), abi.encodeCall(MockContractAbstract.undefinedFuncNoInputNoOutput, ()), 1); 47 | 48 | _contractTest.mock_call_undefinedFuncNoInputNoOutput(); 49 | _contractTest.undefinedFuncNoInputNoOutput(); 50 | } 51 | 52 | function test_Call_UndefinedViewFunc() public { 53 | _contractTest.mock_call_undefinedViewFunc(_someText, _result); 54 | assertEq(_contractTest.undefinedViewFunc(_someText), _result); 55 | } 56 | 57 | function test_Call_UndefinedViewFuncNoInputNoOutput() public { 58 | vm.expectCall(address(_contractTest), abi.encodeCall(MockContractAbstract.undefinedViewFuncNoInputNoOutput, ()), 1); 59 | 60 | _contractTest.mock_call_undefinedViewFuncNoInputNoOutput(); 61 | _contractTest.undefinedViewFuncNoInputNoOutput(); 62 | } 63 | 64 | function test_Call_UndefinedInternalFunc() public { 65 | _contractTest.expectCall__undefinedInternalFunc(_someText); 66 | 67 | _contractTest.mock_call__undefinedInternalFunc(_someText, _result); 68 | assertEq(_contractTest.call__undefinedInternalFunc(_someText), _result); 69 | } 70 | 71 | function test_Call_UndefinedInternalFuncNoInputNoOutput() public { 72 | _contractTest.expectCall__undefinedInternalFuncNoInputNoOutput(); 73 | 74 | _contractTest.mock_call__undefinedInternalFuncNoInputNoOutput(); 75 | _contractTest.call__undefinedInternalFuncNoInputNoOutput(); 76 | } 77 | 78 | function test_Call_UndefinedInternalViewFunc() public { 79 | _contractTest.expectCall__undefinedInternalViewFunc(_someText); 80 | 81 | _contractTest.mock_call__undefinedInternalViewFunc(_someText, _result); 82 | assertEq(_contractTest.call__undefinedInternalViewFunc(_someText), _result); 83 | } 84 | 85 | function test_Call_UndefinedInternalViewFuncNoInputNoOutput() public { 86 | _contractTest.expectCall__undefinedInternalViewFuncNoInputNoOutput(); 87 | 88 | _contractTest.mock_call__undefinedInternalViewFuncNoInputNoOutput(); 89 | _contractTest.call__undefinedInternalViewFuncNoInputNoOutput(); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /test/unit/context/stateVariableContext.spec.ts: -------------------------------------------------------------------------------- 1 | import { DataLocation, StateVariableVisibility } from 'solc-typed-ast'; 2 | import { mockUserDefinedTypeName, mockVariableDeclaration } from '../../mocks'; 3 | import { stateVariableContext } from '../../../src/context'; 4 | import { expect } from 'chai'; 5 | 6 | describe('stateVariableContext', () => { 7 | const defaultAttributes = { 8 | name: 'testStateVariable', 9 | typeString: 'uint256', 10 | visibility: StateVariableVisibility.Default, 11 | vType: mockVariableDeclaration({ typeString: 'uint256' }), 12 | }; 13 | 14 | it('processes state variables', () => { 15 | const node = mockVariableDeclaration(defaultAttributes); 16 | const context = stateVariableContext(node); 17 | 18 | expect(context).to.eql({ 19 | setFunction: { 20 | functionName: 'testStateVariable', 21 | paramType: 'uint256', 22 | paramName: 'testStateVariable', 23 | }, 24 | mockFunction: { 25 | functionName: 'testStateVariable', 26 | paramType: 'uint256', 27 | structFields: [], 28 | }, 29 | isInternal: false, 30 | isStruct: false, 31 | }); 32 | }); 33 | 34 | it('processes internal state variables', () => { 35 | const node = mockVariableDeclaration({ ...defaultAttributes, visibility: StateVariableVisibility.Internal }); 36 | const context = stateVariableContext(node); 37 | 38 | expect(context).to.eql({ 39 | setFunction: { 40 | functionName: 'testStateVariable', 41 | paramType: 'uint256', 42 | paramName: 'testStateVariable', 43 | }, 44 | mockFunction: { 45 | functionName: 'testStateVariable', 46 | paramType: 'uint256', 47 | structFields: [], 48 | }, 49 | isInternal: true, 50 | isStruct: false, 51 | }); 52 | }); 53 | 54 | it('recognizes storage location of parameters', () => { 55 | const node = mockVariableDeclaration({ ...defaultAttributes, typeString: 'string', storageLocation: DataLocation.Memory }); 56 | const context = stateVariableContext(node); 57 | 58 | expect(context).to.eql({ 59 | setFunction: { 60 | functionName: 'testStateVariable', 61 | paramType: 'string memory', 62 | paramName: 'testStateVariable', 63 | }, 64 | mockFunction: { 65 | functionName: 'testStateVariable', 66 | paramType: 'string memory', 67 | structFields: [], 68 | }, 69 | isInternal: false, 70 | isStruct: false, 71 | }); 72 | }); 73 | 74 | it('processes struct state variables', () => { 75 | const node = mockVariableDeclaration({ 76 | ...defaultAttributes, 77 | typeString: 'struct MyStruct', 78 | vType: mockUserDefinedTypeName({ 79 | typeString: 'struct MyStruct', 80 | vReferencedDeclaration: mockUserDefinedTypeName({ 81 | children: [ 82 | mockVariableDeclaration({ name: 'field1', typeString: 'mapping(uint256 => uint256)' }), 83 | mockVariableDeclaration({ name: 'field2', typeString: 'uint256)' }), 84 | ], 85 | }), 86 | }), 87 | }); 88 | const context = stateVariableContext(node); 89 | 90 | expect(context).to.eql({ 91 | setFunction: { 92 | functionName: 'testStateVariable', 93 | paramType: 'MyStruct memory', 94 | paramName: 'testStateVariable', 95 | }, 96 | mockFunction: { 97 | functionName: 'testStateVariable', 98 | paramType: 'MyStruct memory', 99 | structFields: ['field1', 'field2'], 100 | }, 101 | isInternal: false, 102 | isStruct: true, 103 | }); 104 | }); 105 | }); 106 | -------------------------------------------------------------------------------- /solidity/test/utils/ContractJ.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import {Test} from 'forge-std/Test.sol'; 5 | import {MockContractJ} from 'test/smock/contracts/utils/MockContractJ.sol'; 6 | import {SmockHelper} from 'test/smock/SmockHelper.sol'; 7 | import {ContractJ} from 'contracts/utils/ContractJ.sol'; 8 | 9 | contract UnitMockContractJ is Test, SmockHelper { 10 | address internal _owner = makeAddr('owner'); 11 | MockContractJ internal _contractTest; 12 | 13 | uint256 internal _valueA = 10; 14 | uint256 internal _valueB = 20; 15 | uint256 internal _result = 40; 16 | ContractJ.MyUserType internal _myUserTypeA = ContractJ.MyUserType.wrap(_valueA); 17 | ContractJ.MyUserType internal _myUserTypeB = ContractJ.MyUserType.wrap(_valueB); 18 | ContractJ.MyUserType internal _myUserTypeResult = ContractJ.MyUserType.wrap(_result); 19 | 20 | function setUp() public { 21 | vm.prank(_owner); 22 | 23 | _contractTest = MockContractJ(deployMock('TestContractJ', type(MockContractJ).creationCode, abi.encode())); 24 | } 25 | 26 | function test_Set_MyUserTypeVariable() public { 27 | _contractTest.set_myUserTypeVariable(_myUserTypeA); 28 | assertEq(ContractJ.MyUserType.unwrap(_contractTest.myUserTypeVariable()), _valueA); 29 | } 30 | 31 | function test_Call_MyUserTypeVariable() public { 32 | _contractTest.mock_call_myUserTypeVariable(_myUserTypeA); 33 | assertEq(ContractJ.MyUserType.unwrap(_contractTest.myUserTypeVariable()), _valueA); 34 | } 35 | 36 | function test_Set_MyUserTypeArray() public { 37 | ContractJ.MyUserType[] memory _myUserTypeArray = new ContractJ.MyUserType[](1); 38 | _myUserTypeArray[0] = _myUserTypeA; 39 | 40 | _contractTest.set_myUserTypeArray(_myUserTypeArray); 41 | assertEq(ContractJ.MyUserType.unwrap(_contractTest.myUserTypeArray(0)), _valueA); 42 | } 43 | 44 | function test_Call_MyUserTypeArray() public { 45 | ContractJ.MyUserType[] memory _myUserTypeArray = new ContractJ.MyUserType[](1); 46 | _myUserTypeArray[0] = _myUserTypeA; 47 | 48 | _contractTest.mock_call_myUserTypeArray(0, _myUserTypeArray[0]); 49 | assertEq(ContractJ.MyUserType.unwrap(_contractTest.myUserTypeArray(0)), _valueA); 50 | } 51 | 52 | function test_Set_Uint256ToMyUserType() public { 53 | uint256 _key = 0; 54 | 55 | _contractTest.set_uint256ToMyUserType(_key, _myUserTypeA); 56 | assertEq(ContractJ.MyUserType.unwrap(_contractTest.uint256ToMyUserType(_key)), _valueA); 57 | } 58 | 59 | function test_Call_Uint256ToMyUserType() public { 60 | uint256 _key = 0; 61 | 62 | _contractTest.mock_call_uint256ToMyUserType(_key, _myUserTypeA); 63 | assertEq(ContractJ.MyUserType.unwrap(_contractTest.uint256ToMyUserType(_key)), _valueA); 64 | } 65 | 66 | function test_Set_Uint256ToMyUserTypeArray() public { 67 | uint256 _key = 0; 68 | ContractJ.MyUserType[] memory _myUserTypeArray = new ContractJ.MyUserType[](1); 69 | _myUserTypeArray[0] = _myUserTypeA; 70 | 71 | _contractTest.set_uint256ToMyUserTypeArray(_key, _myUserTypeArray); 72 | assertEq(ContractJ.MyUserType.unwrap(_contractTest.uint256ToMyUserTypeArray(_key, 0)), _valueA); 73 | } 74 | 75 | function test_Call_Uint256ToMyUserTypeArray() public { 76 | uint256 _key = 0; 77 | ContractJ.MyUserType[] memory _myUserTypeArray = new ContractJ.MyUserType[](1); 78 | _myUserTypeArray[0] = _myUserTypeA; 79 | 80 | _contractTest.mock_call_uint256ToMyUserTypeArray(_key, 0, _myUserTypeArray[0]); 81 | assertEq(ContractJ.MyUserType.unwrap(_contractTest.uint256ToMyUserTypeArray(_key, 0)), _valueA); 82 | } 83 | 84 | function test_Set_Add() public { 85 | _contractTest.mock_call_add(_myUserTypeA, _myUserTypeB, _myUserTypeResult); 86 | assertEq(ContractJ.MyUserType.unwrap(_contractTest.externalAdd(_myUserTypeA, _myUserTypeB)), _result); 87 | } 88 | 89 | function test_InternalAdd() public { 90 | _contractTest.mock_call_internalAdd(_myUserTypeA, _myUserTypeB, _myUserTypeResult); 91 | assertEq(ContractJ.MyUserType.unwrap(_contractTest.call_internalAdd(_myUserTypeA, _myUserTypeB)), _result); 92 | } 93 | 94 | function test_ExternalAdd() public { 95 | _contractTest.mock_call_externalAdd(_myUserTypeA, _myUserTypeB, _myUserTypeResult); 96 | assertEq(ContractJ.MyUserType.unwrap(_contractTest.externalAdd(_myUserTypeA, _myUserTypeB)), _result); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Version](https://img.shields.io/npm/v/@defi-wonderland/smock-foundry?label=Version)](https://www.npmjs.com/package/@defi-wonderland/smock-foundry) 2 | [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/defi-wonderland/smock-foundry/blob/dev/LICENSE) 3 | 4 | # Smock Foundry 5 | 6 | A plugin for [foundry](https://github.com/foundry-rs/foundry) that automatically generates Solidity mocks for every contract in your project. 7 | 8 | ## Features 9 | 10 | - Get rid of your folder of "mock" contracts and **just use 11 | foundry**. 12 | - Keep your tests **simple** with straightforward mock functions. 13 | - Mock up external calls and internal variables in a beautiful and orderly fashion. 14 | 15 | ## Installation 16 | 17 | You can install the plugin via yarn: 18 | 19 | ```bash 20 | yarn add @defi-wonderland/smock-foundry --save-dev 21 | ``` 22 | 23 | ## Basic Usage 24 | 25 | ### Creating mocks 26 | 27 | To generate the mock contracts all you have to do is run: 28 | 29 | ```bash 30 | yarn smock-foundry --contracts solidity/contracts 31 | ``` 32 | 33 | The `smock-foundry` command accepts the following options: 34 | 35 | | Option | Default | Notes | 36 | | ----------- | -------------- | ---------------------------------------------------------- | 37 | | `contracts` | — | The path to the solidity contracts to mock | 38 | | `root` | `.` | The path to the root of the project | 39 | | `mocks ` | `./test/smock` | The path to the generated mock contracts | 40 | | `ignore` | [] | A list of directories to ignore, e.g. `--ignore libraries` | 41 | 42 | Be sure to `gitignore` the generated smock directory. 43 | 44 | ### Using mocks 45 | 46 | Let's say you have a `Greeter` contract in your project at `contracts/Greeter.sol`: 47 | 48 | ```solidity 49 | contract Greeter { 50 | string internal _greeting; 51 | 52 | constructor(string memory greeting) { 53 | _greeting = greeting; 54 | } 55 | 56 | function greet() public view returns (string memory) { 57 | return _greeting; 58 | } 59 | } 60 | ``` 61 | 62 | After running the generator, you will have a mock contract located at `${mocks}/contracts/MockGreeter.sol`: 63 | 64 | ```solidity 65 | contract MockGreeter is Greeter { 66 | function mock_call_greet(string memory __greeting) external { 67 | // Mocks the greet() function calls 68 | } 69 | 70 | function set__greeting(string memory greeting) public { 71 | // Sets the value of `greeting` 72 | } 73 | } 74 | ``` 75 | 76 | The next step would be importing the mock contract in your unit tests, deploying it and allowing it to use the cheatcodes, specifically `vm.mockCall`. 77 | 78 | ```solidity 79 | import 'forge-std/Test.sol'; 80 | 81 | import { MockGreeter } from '/path/to/smock/contracts/MockGreeter.sol'; 82 | import { SmockHelper } from '/path/to/smock/SmockHelper.sol'; 83 | 84 | contract BaseTest is Test, SmockHelper { 85 | MockGreeter public greeter; 86 | 87 | function setUp() public { 88 | // The `deployMock` call is equivalent to 89 | // address _greeterAddress = address(new MockGreeter('Hello')); 90 | // vm.label(_greeterAddress, 'Greeter'); 91 | // vm.allowCheatcodes(_greeterAddress); 92 | // return _greeterAddress; 93 | 94 | greeter = MockGreeter( 95 | deployMock('Greeter', type(MockGreeter).creationCode, abi.encode('Hello')) 96 | ); 97 | } 98 | } 99 | ``` 100 | 101 | Then enjoy the wonders of mocking: 102 | 103 | ```solidity 104 | // Mock the `greet` function to return 'Hola' instead of 'Hello' 105 | greeter.mock_call_greet('Hola'); 106 | 107 | // Or you can achieve the same by setting the internal variable 108 | greeter.set__greeting('Hola'); 109 | ``` 110 | 111 | ### Gotchas 112 | 113 | - Please, note that if you want to mock `internal` functions, you **must** make them `virtual`. The tool will not generate mocks for internal functions that are not virtual. 114 | - Cannot set `private` variables and mock `private` functions. 115 | - Mocking of structs containing mappings is not supported. 116 | - Mocking of multi-dimensional arrays of structs is not supported. 117 | 118 | # Licensing 119 | 120 | The primary license for Smock Foundry is MIT, see [`LICENSE`](./LICENSE). 121 | 122 | # Contributors 123 | 124 | Maintained with love by [Wonderland](https://defi.sucks). Made possible by viewers like you. 125 | -------------------------------------------------------------------------------- /test/unit/context/constructorContext.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { mockContractDefinition, mockFunctionDefinition, mockParameterList, mockVariableDeclaration } from '../../mocks'; 3 | import { constructorContext } from '../../../src/context'; 4 | import { DataLocation } from 'solc-typed-ast'; 5 | import { FullFunctionDefinition, SelectorsMap } from '../../../src/types'; 6 | 7 | describe('constructorContext', () => { 8 | const defaultAttributes = { 9 | name: 'constructor', 10 | isConstructor: true, 11 | vParameters: mockParameterList({ vParameters: [] }), 12 | vScope: mockContractDefinition({ name: 'TestContract' }), 13 | }; 14 | 15 | it('throws an error if the node is not a constructor', () => { 16 | const node = mockFunctionDefinition({ ...defaultAttributes, isConstructor: false }); 17 | expect(() => constructorContext(node)).to.throw('The node is not a constructor'); 18 | }); 19 | 20 | it('processes constructors without parameters', () => { 21 | const node = mockFunctionDefinition(defaultAttributes); 22 | const context = constructorContext(node); 23 | 24 | expect(context).to.eql({ 25 | parameters: '', 26 | contracts: 'TestContract()', 27 | }); 28 | }); 29 | 30 | it('processes constructors with named parameters', () => { 31 | const parameters = [ 32 | mockVariableDeclaration({ name: 'a', typeString: 'uint256' }), 33 | mockVariableDeclaration({ name: 'b', typeString: 'boolean' }), 34 | ]; 35 | const node = mockFunctionDefinition({ ...defaultAttributes, vParameters: mockParameterList({ vParameters: parameters }) }); 36 | const context = constructorContext(node); 37 | 38 | expect(context).to.eql({ 39 | parameters: 'uint256 a, boolean b', 40 | contracts: 'TestContract(a, b)', 41 | }); 42 | }); 43 | 44 | it('processes constructors with unnamed parameters', () => { 45 | const parameters = [mockVariableDeclaration({ typeString: 'uint256' }), mockVariableDeclaration({ typeString: 'boolean' })]; 46 | const node = mockFunctionDefinition({ ...defaultAttributes, vParameters: mockParameterList({ vParameters: parameters }) }); 47 | const context = constructorContext(node); 48 | 49 | expect(context).to.eql({ 50 | parameters: 'uint256 _param0, boolean _param1', 51 | contracts: 'TestContract(_param0, _param1)', 52 | }); 53 | }); 54 | 55 | it('recognizes storage location of parameters', () => { 56 | const parameters = [ 57 | mockVariableDeclaration({ name: 'a', typeString: 'uint256', storageLocation: DataLocation.Memory }), 58 | mockVariableDeclaration({ name: 'b', typeString: 'boolean', storageLocation: DataLocation.CallData }), 59 | ]; 60 | const node = mockFunctionDefinition({ ...defaultAttributes, vParameters: mockParameterList({ vParameters: parameters }) }); 61 | const context = constructorContext(node); 62 | 63 | expect(context).to.eql({ 64 | parameters: 'uint256 memory a, boolean calldata b', 65 | contracts: 'TestContract(a, b)', 66 | }); 67 | }); 68 | 69 | it('processes inherited constructors', () => { 70 | const parameters = [ 71 | mockVariableDeclaration({ name: 'a', typeString: 'uint256', storageLocation: DataLocation.Memory }), 72 | mockVariableDeclaration({ name: 'b', typeString: 'boolean', storageLocation: DataLocation.CallData }), 73 | ]; 74 | 75 | const nodeA = mockFunctionDefinition({ 76 | ...defaultAttributes, 77 | vParameters: mockParameterList({ vParameters: parameters }), 78 | vScope: mockContractDefinition({ name: 'TestContractA' }), 79 | }); 80 | 81 | const nodeB = mockFunctionDefinition({ 82 | ...defaultAttributes, 83 | vParameters: mockParameterList({ vParameters: parameters }), 84 | vScope: mockContractDefinition({ name: 'TestContractB' }), 85 | }); 86 | 87 | const selectors: SelectorsMap = { 88 | constructor: { 89 | implemented: false, 90 | contracts: new Set(['TestContractA', 'TestContractB']), 91 | constructors: [nodeA, nodeB], 92 | }, 93 | }; 94 | 95 | const nodeWithSelectors = nodeA as FullFunctionDefinition; 96 | nodeWithSelectors.selectors = selectors; 97 | 98 | const context = constructorContext(nodeWithSelectors); 99 | 100 | expect(context).to.eql({ 101 | parameters: 'uint256 memory a, boolean calldata b, uint256 memory a, boolean calldata b', 102 | contracts: 'TestContractA(a, b) TestContractB(a, b)', 103 | }); 104 | }); 105 | }); 106 | -------------------------------------------------------------------------------- /src/smock-foundry.ts: -------------------------------------------------------------------------------- 1 | import { writeFileSync } from 'fs'; 2 | import { 3 | getContractTemplate, 4 | getSmockHelperTemplate, 5 | renderNodeMock, 6 | emptySmockDirectory, 7 | getSourceUnits, 8 | smockableNode, 9 | compileSolidityFilesFoundry, 10 | renderAbstractUnimplementedFunctions, 11 | getRemappings, 12 | getTestImport, 13 | } from './utils'; 14 | import path from 'path'; 15 | import { ensureDir } from 'fs-extra'; 16 | import { exec } from 'child_process'; 17 | 18 | /** 19 | * Generates the mock contracts 20 | * @param mocksDirectory The directory where the mock contracts will be generated 21 | */ 22 | export async function generateMockContracts( 23 | rootPath: string, 24 | contractsDirectories: string[], 25 | mocksDirectory: string, 26 | ignoreDirectories: string[], 27 | ) { 28 | const mocksPath = path.resolve(rootPath, mocksDirectory); 29 | await emptySmockDirectory(mocksPath); 30 | const contractTemplate = getContractTemplate(); 31 | 32 | try { 33 | console.log('Parsing contracts...'); 34 | 35 | try { 36 | const remappings: string[] = await getRemappings(rootPath); 37 | const testImport = getTestImport(remappings); 38 | const sourceUnits = await getSourceUnits(rootPath, contractsDirectories, ignoreDirectories, remappings); 39 | 40 | if (!sourceUnits.length) return console.error('No solidity files found in the specified directory'); 41 | 42 | for (const sourceUnit of sourceUnits) { 43 | let importsContent = ''; 44 | // First process the imports, they will be on top of each mock contract 45 | for (const importDirective of sourceUnit.vImportDirectives) { 46 | importsContent += await renderNodeMock(importDirective); 47 | } 48 | 49 | for (const contract of sourceUnit.vContracts) { 50 | let mockContent = ''; 51 | // Libraries are not mocked 52 | if (contract.kind === 'library') continue; 53 | 54 | if (contract.abstract) { 55 | mockContent += await renderAbstractUnimplementedFunctions(contract); 56 | } 57 | 58 | for (const node of contract.children) { 59 | if (!smockableNode(node)) continue; 60 | mockContent += await renderNodeMock(node); 61 | } 62 | 63 | if (mockContent === '') continue; 64 | 65 | const scope = contract.vScope; 66 | const sourceContractAbsolutePath = path.resolve(rootPath, sourceUnit.absolutePath); 67 | 68 | // The mock contract is written to the mocks directory, taking into account the source contract's place in the directory structure 69 | // So if the source is in a subdirectory of the contracts directory, the mock will be in the same subdirectory of the mocks directory 70 | const escapedSubstrings = contractsDirectories.map((directory) => directory.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); 71 | const regexPattern = new RegExp(escapedSubstrings.join('|'), 'g'); 72 | const mockContractPath = scope.absolutePath 73 | .replace(regexPattern, path.join(mocksDirectory, path.sep)) 74 | .replace(path.basename(sourceUnit.absolutePath), `Mock${contract.name}.sol`); 75 | const mockContractAbsolutePath = path.resolve(rootPath, mockContractPath); 76 | 77 | // Relative path to the source is used in mock's imports 78 | const sourceContractRelativePath = path.relative(path.dirname(mockContractAbsolutePath), sourceContractAbsolutePath); 79 | 80 | const contractCode: string = contractTemplate({ 81 | content: mockContent, 82 | importsContent: importsContent, 83 | contractName: contract.name, 84 | sourceContractRelativePath: sourceContractRelativePath, 85 | exportedSymbols: Array.from(scope.exportedSymbols.keys()), 86 | license: sourceUnit.license, 87 | testImport, 88 | }); 89 | 90 | await ensureDir(path.dirname(mockContractAbsolutePath)); 91 | writeFileSync(mockContractAbsolutePath, contractCode); 92 | } 93 | } 94 | 95 | // Generate SmockHelper contract 96 | const smockHelperTemplate = await getSmockHelperTemplate(); 97 | const smockHelperCode: string = smockHelperTemplate({ testImport }); 98 | writeFileSync(`${mocksPath}/SmockHelper.sol`, smockHelperCode); 99 | 100 | console.log('Mock contracts generated successfully'); 101 | 102 | await compileSolidityFilesFoundry(rootPath, mocksDirectory, remappings); 103 | 104 | // Format the generated files 105 | console.log('Formatting generated files...'); 106 | exec(`forge fmt --root ${rootPath} ${mocksPath}`); 107 | } catch (error) { 108 | console.error(error); 109 | } 110 | } catch (error) { 111 | console.error(error); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /solidity/contracts/ContractTest.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import {IContractTest, MyContract} from '../interfaces/IContractTest.sol'; 5 | 6 | /** 7 | * @notice This contract is for general testing 8 | * @dev It contains the most common variables types and functions 9 | */ 10 | contract ContractTest is IContractTest { 11 | uint256 public immutable immutableUintVariable = 10; 12 | 13 | uint256 internal internalUint256; 14 | /// @inheritdoc IContractTest 15 | uint256 public uintVariable; 16 | /// @inheritdoc IContractTest 17 | string public stringVariable; 18 | /// @inheritdoc IContractTest 19 | bool public boolVariable; 20 | /// @inheritdoc IContractTest 21 | address public addressVariable; 22 | /// @inheritdoc IContractTest 23 | bytes32 public bytes32Variable; 24 | /// @inheritdoc IContractTest 25 | MyStruct public myStructVariable; 26 | /// @inheritdoc IContractTest 27 | MyEnum public myEnumVariable; 28 | /// @inheritdoc IContractTest 29 | MyContract public myContractVariable; 30 | 31 | address[] internal internalAddressArray; 32 | /// @inheritdoc IContractTest 33 | address[] public addressArray; 34 | /// @inheritdoc IContractTest 35 | uint256[] public uint256Array; 36 | /// @inheritdoc IContractTest 37 | bytes32[] public bytes32Array; 38 | /// @inheritdoc IContractTest 39 | MyStruct[] public myStructArray; 40 | 41 | mapping(uint256 => address) internal internalUint256ToAddress; 42 | /// @inheritdoc IContractTest 43 | mapping(uint256 => address) public uint256ToAddress; 44 | /// @inheritdoc IContractTest 45 | mapping(address => uint256) public addressToUint; 46 | /// @inheritdoc IContractTest 47 | mapping(bytes32 => bytes) public bytes32ToBytes; 48 | /// @inheritdoc IContractTest 49 | mapping(uint256 => MyStruct) public uint256ToMyStruct; 50 | /// @inheritdoc IContractTest 51 | mapping(uint256 => address[]) public uint256ToAddressArray; 52 | /// @inheritdoc IContractTest 53 | mapping(uint256 => MyStruct[]) public uint256ToMyStructArray; 54 | /// @inheritdoc IContractTest 55 | mapping(uint256 => mapping(address => bytes32)) public uint256ToAddressToBytes32; 56 | 57 | /// Constructor 58 | constructor( 59 | uint256 _uintVariable, 60 | string memory _stringVariable, 61 | bool _boolVariable, 62 | address _addressVariable, 63 | bytes32 _bytes32Variable 64 | ) { 65 | uintVariable = _uintVariable; 66 | stringVariable = _stringVariable; 67 | boolVariable = _boolVariable; 68 | addressVariable = _addressVariable; 69 | bytes32Variable = _bytes32Variable; 70 | } 71 | 72 | /// @inheritdoc IContractTest 73 | function setVariables( 74 | uint256 _newValue, 75 | string memory _newString, 76 | bool _newBool, 77 | address _newAddress, 78 | bytes32 _newBytes32, 79 | address[] memory _addressArray, 80 | uint256[] memory _uint256Array, 81 | bytes32[] memory _bytes32Array 82 | ) public returns (bool _result) { 83 | uintVariable = _newValue; 84 | stringVariable = _newString; 85 | boolVariable = _newBool; 86 | addressVariable = _newAddress; 87 | bytes32Variable = _newBytes32; 88 | addressArray = _addressArray; 89 | uint256Array = _uint256Array; 90 | bytes32Array = _bytes32Array; 91 | _result = true; 92 | } 93 | 94 | function setVariables(uint256 _newValue) public returns (bool _result) { 95 | uintVariable = _newValue; 96 | _result = true; 97 | } 98 | 99 | function setVariables(uint256 _newValue, bool) public returns (bool _result) { 100 | uintVariable = _newValue; 101 | _result = true; 102 | } 103 | 104 | function setStructVariable(MyStruct memory _myStruct) public returns (bool _result) { 105 | myStructVariable = _myStruct; 106 | _result = true; 107 | } 108 | 109 | function internalVirtualFunction(uint256 _newValue) 110 | internal 111 | virtual 112 | returns (bool _result, uint256 _value, string memory _string) 113 | { 114 | internalUint256 = _newValue; 115 | _result = true; 116 | _value = 1; 117 | _string = 'test'; 118 | } 119 | 120 | function internalViewVirtualFunction(uint256 _newValue) 121 | internal 122 | view 123 | virtual 124 | returns (bool _result, uint256 _value, string memory _string) 125 | { 126 | _result = true; 127 | _value = 1; 128 | _string = 'test'; 129 | } 130 | 131 | function internalPureVirtualFunction(uint256 _newValue) 132 | internal 133 | pure 134 | virtual 135 | returns (bool _result, uint256 _value, string memory _string) 136 | { 137 | _result = true; 138 | _value = _newValue; 139 | _string = 'test'; 140 | } 141 | 142 | function internalNonVirtualFunction( 143 | uint256 _newValue, 144 | bool 145 | ) internal returns (bool _result, uint256 _value, string memory _string) { 146 | internalUint256 = _newValue; 147 | _result = true; 148 | _value = 1; 149 | _string = 'test'; 150 | } 151 | 152 | function testFunc(uint256) public pure returns (bool) { 153 | return true; 154 | } 155 | 156 | // ========================= View ========================= 157 | 158 | function getInternalUint256() public view returns (uint256) { 159 | return internalUint256; 160 | } 161 | 162 | function getInternalAddressArray() public view returns (address[] memory) { 163 | return internalAddressArray; 164 | } 165 | 166 | function getInternalUint256ToAddress(uint256 _index) public view returns (address) { 167 | return internalUint256ToAddress[_index]; 168 | } 169 | 170 | // =============== virtual call =============== 171 | 172 | function callInternalVirtualFunction(uint256 _newValue) 173 | public 174 | returns (bool _result, uint256 _value, string memory _string) 175 | { 176 | (_result, _value, _string) = internalVirtualFunction(_newValue); 177 | } 178 | 179 | function callInternalViewVirtualFunction(uint256 _newValue) 180 | public 181 | view 182 | returns (bool _result, uint256 _value, string memory _string) 183 | { 184 | (_result, _value, _string) = internalViewVirtualFunction(_newValue); 185 | } 186 | 187 | function callInternalPureVirtualFunction(uint256 _newValue) 188 | public 189 | pure 190 | returns (bool _result, uint256 _value, string memory _string) 191 | { 192 | (_result, _value, _string) = internalPureVirtualFunction(_newValue); 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /test/unit/context/arrayVariableContext.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { DataLocation, StateVariableVisibility } from 'solc-typed-ast'; 3 | import { mockArrayTypeName, mockTypeName, mockUserDefinedTypeName, mockVariableDeclaration } from '../../mocks'; 4 | import { arrayVariableContext } from '../../../src/context'; 5 | 6 | describe('arrayVariableContext', () => { 7 | const defaultAttributes = { 8 | name: 'testArrayVariable', 9 | typeString: 'uint256[]', 10 | vType: mockArrayTypeName({ vBaseType: mockTypeName({ typeString: 'uint256' }) }), 11 | visibility: StateVariableVisibility.Default, 12 | }; 13 | 14 | it('should return the correct context for a non-struct array', () => { 15 | const node = mockVariableDeclaration(defaultAttributes); 16 | const context = arrayVariableContext(node); 17 | 18 | expect(context).to.eql({ 19 | setFunction: { 20 | functionName: 'testArrayVariable', 21 | arrayType: 'uint256[] memory', 22 | paramName: 'testArrayVariable', 23 | }, 24 | mockFunction: { 25 | functionName: 'testArrayVariable', 26 | arrayType: 'uint256[] memory', 27 | baseType: 'uint256', 28 | structFields: [], 29 | }, 30 | isInternal: false, 31 | isStructArray: false, 32 | isMultiDimensionalStruct: false, 33 | dimensions: [0], 34 | }); 35 | }); 36 | 37 | it('should return the correct context for a struct array', () => { 38 | const node = mockVariableDeclaration({ 39 | ...defaultAttributes, 40 | typeString: 'struct MyStruct[]', 41 | vType: mockArrayTypeName({ 42 | typeString: 'struct MyStruct[]', 43 | vBaseType: mockUserDefinedTypeName({ 44 | typeString: 'struct MyStruct', 45 | vReferencedDeclaration: mockTypeName({ 46 | children: [mockVariableDeclaration({ name: 'field1' }), mockVariableDeclaration({ name: 'field2' })], 47 | }), 48 | }), 49 | }), 50 | }); 51 | const context = arrayVariableContext(node); 52 | 53 | expect(context).to.eql({ 54 | setFunction: { 55 | functionName: 'testArrayVariable', 56 | arrayType: 'MyStruct[] memory', 57 | paramName: 'testArrayVariable', 58 | }, 59 | mockFunction: { 60 | functionName: 'testArrayVariable', 61 | arrayType: 'MyStruct[] memory', 62 | baseType: 'MyStruct memory', 63 | structFields: ['field1', 'field2'], 64 | }, 65 | isInternal: false, 66 | isStructArray: true, 67 | isMultiDimensionalStruct: false, 68 | dimensions: [0], 69 | }); 70 | }); 71 | 72 | it('should return the correct context for an internal array', () => { 73 | const node = mockVariableDeclaration({ ...defaultAttributes, visibility: StateVariableVisibility.Internal }); 74 | const context = arrayVariableContext(node); 75 | 76 | expect(context).to.eql({ 77 | setFunction: { 78 | functionName: 'testArrayVariable', 79 | arrayType: 'uint256[] memory', 80 | paramName: 'testArrayVariable', 81 | }, 82 | mockFunction: { 83 | functionName: 'testArrayVariable', 84 | arrayType: 'uint256[] memory', 85 | baseType: 'uint256', 86 | structFields: [], 87 | }, 88 | isInternal: true, 89 | isStructArray: false, 90 | isMultiDimensionalStruct: false, 91 | dimensions: [0], 92 | }); 93 | }); 94 | 95 | it('should return the correct context for an internal struct array', () => { 96 | const node = mockVariableDeclaration({ 97 | ...defaultAttributes, 98 | typeString: 'struct MyStruct[]', 99 | vType: mockArrayTypeName({ vBaseType: mockTypeName({ typeString: 'struct MyStruct' }) }), 100 | visibility: StateVariableVisibility.Internal, 101 | }); 102 | const context = arrayVariableContext(node); 103 | 104 | expect(context).to.eql({ 105 | setFunction: { 106 | functionName: 'testArrayVariable', 107 | arrayType: 'MyStruct[] memory', 108 | paramName: 'testArrayVariable', 109 | }, 110 | mockFunction: { 111 | functionName: 'testArrayVariable', 112 | arrayType: 'MyStruct[] memory', 113 | baseType: 'MyStruct memory', 114 | structFields: [], 115 | }, 116 | isInternal: true, 117 | isStructArray: true, 118 | isMultiDimensionalStruct: false, 119 | dimensions: [0], 120 | }); 121 | }); 122 | 123 | it('should return the correct context for an array with a memory storage location', () => { 124 | const node = mockVariableDeclaration({ 125 | ...defaultAttributes, 126 | typeString: 'string[]', 127 | vType: mockArrayTypeName({ vBaseType: mockTypeName({ typeString: 'string' }) }), 128 | storageLocation: DataLocation.Memory, 129 | }); 130 | const context = arrayVariableContext(node); 131 | 132 | expect(context).to.eql({ 133 | setFunction: { 134 | functionName: 'testArrayVariable', 135 | arrayType: 'string[] memory', 136 | paramName: 'testArrayVariable', 137 | }, 138 | mockFunction: { 139 | functionName: 'testArrayVariable', 140 | arrayType: 'string[] memory', 141 | baseType: 'string memory', 142 | structFields: [], 143 | }, 144 | isInternal: false, 145 | isStructArray: false, 146 | isMultiDimensionalStruct: false, 147 | dimensions: [0], 148 | }); 149 | }); 150 | 151 | it('should return the correct context for a multi-dimensional array', () => { 152 | const node = mockVariableDeclaration({ 153 | ...defaultAttributes, 154 | typeString: 'uint256[][]', 155 | vType: mockArrayTypeName({ 156 | vBaseType: mockArrayTypeName({ typeString: 'uint256[]', vBaseType: mockTypeName({ typeString: 'uint256' }) }), 157 | }), 158 | }); 159 | const context = arrayVariableContext(node); 160 | 161 | expect(context).to.eql({ 162 | setFunction: { 163 | functionName: 'testArrayVariable', 164 | arrayType: 'uint256[][] memory', 165 | paramName: 'testArrayVariable', 166 | }, 167 | mockFunction: { 168 | functionName: 'testArrayVariable', 169 | arrayType: 'uint256[][] memory', 170 | baseType: 'uint256', 171 | structFields: [], 172 | }, 173 | isInternal: false, 174 | isStructArray: false, 175 | isMultiDimensionalStruct: false, 176 | dimensions: [0, 1], 177 | }); 178 | }); 179 | }); 180 | -------------------------------------------------------------------------------- /src/context.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ExternalFunctionContext, 3 | ConstructorContext, 4 | InternalFunctionContext, 5 | ImportContext, 6 | MappingVariableContext, 7 | ArrayVariableContext, 8 | StateVariableContext, 9 | FullFunctionDefinition, 10 | } from './types'; 11 | import { 12 | sanitizeParameterType, 13 | explicitTypeStorageLocation, 14 | extractParameters, 15 | extractReturnParameters, 16 | extractOverrides, 17 | hasNestedMappings, 18 | extractStructFieldsNames, 19 | extractConstructorsParameters, 20 | } from './utils'; 21 | import { FunctionDefinition, VariableDeclaration, Identifier, ImportDirective, FunctionStateMutability } from 'solc-typed-ast'; 22 | 23 | export function internalFunctionContext(node: FunctionDefinition): InternalFunctionContext { 24 | // Check if the function is internal 25 | if (node.visibility !== 'internal') throw new Error('The function is not internal'); 26 | // Check if the function is internal 27 | if (!node.virtual) throw new Error('The function is not virtual'); 28 | 29 | const { functionParameters, parameterTypes, parameterNames } = extractParameters(node.vParameters.vParameters); 30 | const { functionReturnParameters, returnParameterTypes, returnParameterNames, returnExplicitParameterTypes } = extractReturnParameters( 31 | node.vReturnParameters.vParameters, 32 | ); 33 | const signature = parameterTypes ? `${node.name}(${parameterTypes.join(',')})` : `${node.name}()`; 34 | 35 | // Create the string that will be used in the mock function signature 36 | const inputs: string = functionParameters.length ? functionParameters.join(', ') : ''; 37 | const outputs: string = functionReturnParameters.length ? functionReturnParameters.join(', ') : ''; 38 | 39 | let params: string; 40 | if (!inputs) { 41 | params = outputs; 42 | } else if (!outputs) { 43 | params = inputs; 44 | } else { 45 | params = `${inputs}, ${outputs}`; 46 | } 47 | 48 | // Check if the function is view or pure 49 | const isPure = node.stateMutability === FunctionStateMutability.Pure; 50 | const isView = node.stateMutability === FunctionStateMutability.View || isPure; 51 | 52 | // Save the internal function information 53 | return { 54 | functionName: node.name, 55 | signature: signature, 56 | parameters: params, 57 | inputs: inputs, 58 | outputs: outputs, 59 | inputTypes: parameterTypes, 60 | outputTypes: returnParameterTypes, 61 | explicitOutputTypes: returnExplicitParameterTypes, 62 | inputNames: parameterNames, 63 | outputNames: returnParameterNames, 64 | isView: isView, 65 | isPure: isPure, 66 | implemented: node.implemented, 67 | }; 68 | } 69 | 70 | export function externalOrPublicFunctionContext(node: FunctionDefinition): ExternalFunctionContext { 71 | // Check if the function is external or public 72 | if (node.visibility != 'external' && node.visibility != 'public') throw new Error('The function is not external nor public'); 73 | 74 | // Save state mutability 75 | const stateMutability = node.stateMutability === 'nonpayable' ? '' : `${node.stateMutability ?? ''}`; 76 | 77 | const { functionParameters, parameterTypes, parameterNames } = extractParameters(node.vParameters.vParameters); 78 | const { functionReturnParameters, returnParameterNames } = extractReturnParameters(node.vReturnParameters.vParameters); 79 | const signature = parameterTypes ? `${node.name}(${parameterTypes.join(',')})` : `${node.name}()`; 80 | 81 | // Create the string that will be used in the mock function signature 82 | const inputs: string = functionParameters.length ? functionParameters.join(', ') : ''; 83 | const outputs: string = functionReturnParameters.length ? functionReturnParameters.join(', ') : ''; 84 | 85 | let params: string; 86 | if (!inputs) { 87 | params = outputs; 88 | } else if (!outputs) { 89 | params = inputs; 90 | } else { 91 | params = `${inputs}, ${outputs}`; 92 | } 93 | 94 | // Extract function overrides 95 | const overrides = extractOverrides(node as FullFunctionDefinition); 96 | 97 | // Save the external function information 98 | return { 99 | functionName: node.name, 100 | signature: signature, 101 | parameters: params, 102 | inputs: inputs, 103 | outputs: outputs, 104 | inputNames: parameterNames, 105 | outputNames: returnParameterNames, 106 | visibility: node.visibility, 107 | stateMutability: stateMutability, 108 | implemented: node.implemented, 109 | overrides: overrides, 110 | }; 111 | } 112 | 113 | export function constructorContext(node: FunctionDefinition): ConstructorContext { 114 | if (!node.isConstructor) throw new Error('The node is not a constructor'); 115 | 116 | // Get the parameters of the constructors, if there are no parameters then we use an empty array 117 | const { parameters, contracts } = extractConstructorsParameters(node as FullFunctionDefinition); 118 | 119 | return { 120 | parameters: parameters.join(', '), 121 | contracts: Array.from(contracts).join(' '), 122 | }; 123 | } 124 | 125 | export function importContext(node: ImportDirective): ImportContext { 126 | // Get the absolute path and the symbol aliases, the symbol aliases are the named imports 127 | const { symbolAliases, absolutePath } = node; 128 | 129 | // If there are no named imports then we import the whole file 130 | if (!symbolAliases.length) return { absolutePath }; 131 | 132 | // Get the names of the named imports 133 | const namedImports = symbolAliases.map((symbolAlias) => 134 | symbolAlias.foreign instanceof Identifier ? symbolAlias.foreign.name : symbolAlias.foreign, 135 | ); 136 | 137 | return { 138 | namedImports, 139 | absolutePath, 140 | }; 141 | } 142 | 143 | export function mappingVariableContext(node: VariableDeclaration): MappingVariableContext { 144 | // Name of the mapping 145 | const mappingName: string = node.name; 146 | 147 | // Type name 148 | let mappingTypeNameNode = node.vType; 149 | 150 | // Key types 151 | const keyTypes: string[] = []; 152 | 153 | do { 154 | const keyType: string = sanitizeParameterType(explicitTypeStorageLocation(mappingTypeNameNode['vKeyType'].typeString)); 155 | keyTypes.push(keyType); 156 | mappingTypeNameNode = mappingTypeNameNode['vValueType']; 157 | } while (mappingTypeNameNode.typeString.startsWith('mapping')); 158 | 159 | // Value type 160 | const valueType: string = sanitizeParameterType(explicitTypeStorageLocation(mappingTypeNameNode.typeString)); 161 | 162 | // Array flag 163 | const isArray: boolean = valueType.includes('[]'); 164 | 165 | // Base type 166 | const baseType: string = isArray ? sanitizeParameterType(explicitTypeStorageLocation(mappingTypeNameNode['vBaseType'].typeString)) : valueType; 167 | 168 | // Struct array flag 169 | const isStructArray: boolean = isArray && mappingTypeNameNode.typeString.startsWith('struct '); 170 | 171 | // Check if value is a struct and has nested mappings 172 | const hasNestedMapping = hasNestedMappings(mappingTypeNameNode); 173 | 174 | // Check if the variable is a struct and get its fields 175 | const structFields = extractStructFieldsNames(mappingTypeNameNode); 176 | 177 | // If the mapping is internal we don't create mockCall for it 178 | const isInternal: boolean = node.visibility === 'internal'; 179 | 180 | return { 181 | setFunction: { 182 | functionName: mappingName, 183 | keyTypes: keyTypes, 184 | valueType: valueType, 185 | }, 186 | mockFunction: { 187 | functionName: mappingName, 188 | keyTypes: keyTypes, 189 | valueType: valueType, 190 | baseType: baseType, 191 | structFields, 192 | }, 193 | isInternal: isInternal, 194 | isArray: isArray, 195 | isStruct: structFields.length > 0, 196 | isStructArray: isStructArray, 197 | hasNestedMapping, 198 | }; 199 | } 200 | 201 | export function arrayVariableContext(node: VariableDeclaration): ArrayVariableContext { 202 | // Name of the array 203 | const arrayName: string = node.name; 204 | 205 | // Array type 206 | const arrayType: string = sanitizeParameterType(explicitTypeStorageLocation(node.typeString)); 207 | 208 | // Struct flag 209 | const isStructArray: boolean = node.typeString.startsWith('struct '); 210 | 211 | // Check if the array is multi-dimensional 212 | const dimensionsQuantity = node.typeString.split('[]').length - 1; 213 | const isMultiDimensional = dimensionsQuantity > 1; 214 | const isMultiDimensionalStruct = isMultiDimensional && isStructArray; 215 | const dimensions = [...Array(dimensionsQuantity).keys()]; 216 | 217 | // Base type 218 | const baseTypeString = isMultiDimensional ? node.typeString.replace(/\[\]/g, '') : node.vType['vBaseType'].typeString; 219 | const baseType = sanitizeParameterType(explicitTypeStorageLocation(baseTypeString)); 220 | 221 | // Check if the variable is a struct and get its fields 222 | const structFields = extractStructFieldsNames(node.vType); 223 | 224 | // If the array is internal we don't create mockCall for it 225 | const isInternal: boolean = node.visibility === 'internal'; 226 | 227 | return { 228 | setFunction: { 229 | functionName: arrayName, 230 | arrayType: arrayType, 231 | paramName: arrayName, 232 | }, 233 | mockFunction: { 234 | functionName: arrayName, 235 | arrayType: arrayType, 236 | baseType: baseType, 237 | structFields, 238 | }, 239 | isInternal: isInternal, 240 | isStructArray: isStructArray, 241 | isMultiDimensionalStruct, 242 | dimensions, 243 | }; 244 | } 245 | 246 | export function stateVariableContext(node: VariableDeclaration): StateVariableContext { 247 | // Name of the variable 248 | const variableName: string = node.name; 249 | 250 | // Remove spec type leading string 251 | const variableType: string = sanitizeParameterType(explicitTypeStorageLocation(node.typeString)); 252 | 253 | // If the variable is internal we don't create mockCall for it 254 | const isInternal: boolean = node.visibility === 'internal'; 255 | 256 | // Check if the variable is a struct and get its fields 257 | const structFields = extractStructFieldsNames(node.vType); 258 | 259 | // Save the state variable information 260 | return { 261 | setFunction: { 262 | functionName: variableName, 263 | paramType: variableType, 264 | paramName: variableName, 265 | }, 266 | mockFunction: { 267 | functionName: variableName, 268 | paramType: variableType, 269 | structFields, 270 | }, 271 | isInternal: isInternal, 272 | isStruct: structFields.length > 0, 273 | }; 274 | } 275 | -------------------------------------------------------------------------------- /test/unit/context/externalOrPublicFunctionContext.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { DataLocation, FunctionStateMutability, FunctionVisibility, UserDefinedTypeName, UserDefinedValueTypeDefinition } from 'solc-typed-ast'; 3 | import { mockElementaryTypeName, mockFunctionDefinition, mockParameterList, mockVariableDeclaration } from '../../mocks'; 4 | import { externalOrPublicFunctionContext } from '../../../src/context'; 5 | import { FullFunctionDefinition, SelectorsMap } from '../../../src/types'; 6 | 7 | describe('externalOrPublicFunctionContext', () => { 8 | const defaultAttributes = { 9 | name: 'testExternalFunction', 10 | visibility: FunctionVisibility.External, 11 | virtual: true, 12 | vParameters: mockParameterList({ vParameters: [] }), 13 | vReturnParameters: mockParameterList({ vParameters: [] }), 14 | implemented: true, 15 | functionStateMutability: FunctionStateMutability.NonPayable, 16 | }; 17 | 18 | it('throws an error if the function is not public nor external', () => { 19 | for (const visibility in FunctionVisibility) { 20 | if ([FunctionVisibility.External, FunctionVisibility.Public].includes(FunctionVisibility[visibility])) continue; 21 | const node = mockFunctionDefinition({ ...defaultAttributes, visibility: FunctionVisibility[visibility] }); 22 | expect(() => externalOrPublicFunctionContext(node)).to.throw('The function is not external nor public'); 23 | } 24 | }); 25 | 26 | it('processes functions without parameters and returned values', () => { 27 | const node = mockFunctionDefinition(defaultAttributes); 28 | const context = externalOrPublicFunctionContext(node); 29 | 30 | expect(context).to.eql({ 31 | functionName: 'testExternalFunction', 32 | signature: 'testExternalFunction()', 33 | parameters: '', 34 | inputs: '', 35 | outputs: '', 36 | inputNames: [], 37 | outputNames: [], 38 | implemented: true, 39 | stateMutability: '', 40 | visibility: 'external', 41 | overrides: null, 42 | }); 43 | }); 44 | 45 | it('processes named parameters', () => { 46 | const parameters = [ 47 | mockVariableDeclaration({ name: 'a', typeString: 'uint256' }), 48 | mockVariableDeclaration({ name: 'b', typeString: 'boolean' }), 49 | ]; 50 | const node = mockFunctionDefinition({ ...defaultAttributes, vParameters: mockParameterList({ vParameters: parameters }) }); 51 | const context = externalOrPublicFunctionContext(node); 52 | 53 | expect(context).to.eql({ 54 | functionName: 'testExternalFunction', 55 | signature: 'testExternalFunction(uint256,boolean)', 56 | parameters: 'uint256 a, boolean b', 57 | inputs: 'uint256 a, boolean b', 58 | outputs: '', 59 | inputNames: ['a', 'b'], 60 | outputNames: [], 61 | implemented: true, 62 | stateMutability: '', 63 | visibility: 'external', 64 | overrides: null, 65 | }); 66 | }); 67 | 68 | it('processes unnamed parameters', () => { 69 | const parameters = [mockVariableDeclaration({ typeString: 'uint256' }), mockVariableDeclaration({ typeString: 'boolean' })]; 70 | const node = mockFunctionDefinition({ ...defaultAttributes, vParameters: mockParameterList({ vParameters: parameters }) }); 71 | const context = externalOrPublicFunctionContext(node); 72 | 73 | expect(context).to.eql({ 74 | functionName: 'testExternalFunction', 75 | signature: 'testExternalFunction(uint256,boolean)', 76 | parameters: 'uint256 _param0, boolean _param1', 77 | inputs: 'uint256 _param0, boolean _param1', 78 | outputs: '', 79 | inputNames: ['_param0', '_param1'], 80 | outputNames: [], 81 | implemented: true, 82 | stateMutability: '', 83 | visibility: 'external', 84 | overrides: null, 85 | }); 86 | }); 87 | 88 | it('processes unnamed returned parameters', () => { 89 | const parameters = [mockVariableDeclaration({ typeString: 'uint256' }), mockVariableDeclaration({ typeString: 'boolean' })]; 90 | const node = mockFunctionDefinition({ ...defaultAttributes, vReturnParameters: mockParameterList({ vParameters: parameters }) }); 91 | const context = externalOrPublicFunctionContext(node); 92 | 93 | expect(context).to.eql({ 94 | functionName: 'testExternalFunction', 95 | signature: 'testExternalFunction()', 96 | parameters: 'uint256 _returnParam0, boolean _returnParam1', 97 | inputs: '', 98 | outputs: 'uint256 _returnParam0, boolean _returnParam1', 99 | inputNames: [], 100 | outputNames: ['_returnParam0', '_returnParam1'], 101 | implemented: true, 102 | stateMutability: '', 103 | visibility: 'external', 104 | overrides: null, 105 | }); 106 | }); 107 | 108 | it('processes named returned parameters', () => { 109 | const parameters = [ 110 | mockVariableDeclaration({ name: 'a', typeString: 'uint256' }), 111 | mockVariableDeclaration({ name: 'b', typeString: 'boolean' }), 112 | ]; 113 | const node = mockFunctionDefinition({ ...defaultAttributes, vReturnParameters: mockParameterList({ vParameters: parameters }) }); 114 | const context = externalOrPublicFunctionContext(node); 115 | 116 | expect(context).to.eql({ 117 | functionName: 'testExternalFunction', 118 | signature: 'testExternalFunction()', 119 | parameters: 'uint256 a, boolean b', 120 | inputs: '', 121 | outputs: 'uint256 a, boolean b', 122 | inputNames: [], 123 | outputNames: ['a', 'b'], 124 | implemented: true, 125 | stateMutability: '', 126 | visibility: 'external', 127 | overrides: null, 128 | }); 129 | }); 130 | 131 | it('recognizes storage location of parameters', () => { 132 | const parameters = [ 133 | mockVariableDeclaration({ name: 'a', typeString: 'uint256', storageLocation: DataLocation.Storage }), 134 | mockVariableDeclaration({ name: 'b', typeString: 'string', storageLocation: DataLocation.Memory }), 135 | mockVariableDeclaration({ name: 'c', typeString: 'bytes', storageLocation: DataLocation.CallData }), 136 | mockVariableDeclaration({ name: 'd', typeString: 'boolean', storageLocation: DataLocation.Default }), 137 | ]; 138 | const node = mockFunctionDefinition({ ...defaultAttributes, vParameters: mockParameterList({ vParameters: parameters }) }); 139 | const context = externalOrPublicFunctionContext(node); 140 | 141 | expect(context).to.eql({ 142 | functionName: 'testExternalFunction', 143 | signature: 'testExternalFunction(uint256,string,bytes,boolean)', 144 | parameters: 'uint256 a, string memory b, bytes calldata c, boolean d', 145 | inputs: 'uint256 a, string memory b, bytes calldata c, boolean d', 146 | outputs: '', 147 | inputNames: ['a', 'b', 'c', 'd'], 148 | outputNames: [], 149 | implemented: true, 150 | stateMutability: '', 151 | visibility: 'external', 152 | overrides: null, 153 | }); 154 | }); 155 | 156 | it('recognizes storage location of returned parameters', () => { 157 | const parameters = [ 158 | mockVariableDeclaration({ name: 'a', typeString: 'uint256', storageLocation: DataLocation.Storage }), 159 | mockVariableDeclaration({ name: 'b', typeString: 'string', storageLocation: DataLocation.Memory }), 160 | mockVariableDeclaration({ name: 'c', typeString: 'bytes', storageLocation: DataLocation.CallData }), 161 | mockVariableDeclaration({ name: 'd', typeString: 'boolean', storageLocation: DataLocation.Default }), 162 | ]; 163 | const node = mockFunctionDefinition({ ...defaultAttributes, vReturnParameters: mockParameterList({ vParameters: parameters }) }); 164 | const context = externalOrPublicFunctionContext(node); 165 | 166 | expect(context).to.eql({ 167 | functionName: 'testExternalFunction', 168 | signature: 'testExternalFunction()', 169 | parameters: 'uint256 a, string memory b, bytes calldata c, boolean d', 170 | inputs: '', 171 | outputs: 'uint256 a, string memory b, bytes calldata c, boolean d', 172 | inputNames: [], 173 | outputNames: ['a', 'b', 'c', 'd'], 174 | implemented: true, 175 | stateMutability: '', 176 | visibility: 'external', 177 | overrides: null, 178 | }); 179 | }); 180 | 181 | it('determines whether the function is implemented or not', () => { 182 | for (const implemented of [true, false]) { 183 | const node = mockFunctionDefinition({ ...defaultAttributes, implemented: implemented }); 184 | const context = externalOrPublicFunctionContext(node); 185 | expect(context.implemented).to.be.equal(implemented); 186 | } 187 | }); 188 | 189 | it('process functions with overrides', () => { 190 | const node = mockFunctionDefinition({ ...defaultAttributes, raw: { functionSelector: '0x12345678' } }); 191 | 192 | const selectors: SelectorsMap = { 193 | '0x12345678': { 194 | implemented: true, 195 | contracts: new Set(['TestContractA', 'TestContractB']), 196 | }, 197 | }; 198 | 199 | const nodeWithSelectors = node as FullFunctionDefinition; 200 | nodeWithSelectors.selectors = selectors; 201 | 202 | const context = externalOrPublicFunctionContext(nodeWithSelectors); 203 | 204 | expect(context).to.eql({ 205 | functionName: 'testExternalFunction', 206 | signature: 'testExternalFunction()', 207 | parameters: '', 208 | inputs: '', 209 | outputs: '', 210 | inputNames: [], 211 | outputNames: [], 212 | implemented: true, 213 | stateMutability: '', 214 | visibility: 'external', 215 | overrides: '(TestContractA, TestContractB)', 216 | }); 217 | }); 218 | 219 | it('process functions with user defined value types', () => { 220 | const vReferencedDeclaration = new UserDefinedValueTypeDefinition(0, '', 'MyType', mockElementaryTypeName({ typeString: 'uint256' })); 221 | 222 | const vType = Object.create(UserDefinedTypeName.prototype, { 223 | vReferencedDeclaration: { value: vReferencedDeclaration }, 224 | }); 225 | 226 | const node = mockFunctionDefinition({ 227 | ...defaultAttributes, 228 | vParameters: mockParameterList({ 229 | vParameters: [ 230 | mockVariableDeclaration({ 231 | name: 'a', 232 | typeString: 'MyType', 233 | vType, 234 | }), 235 | ], 236 | }), 237 | }); 238 | 239 | const context = externalOrPublicFunctionContext(node); 240 | 241 | expect(context).to.eql({ 242 | functionName: 'testExternalFunction', 243 | signature: 'testExternalFunction(uint256)', 244 | parameters: 'MyType a', 245 | inputs: 'MyType a', 246 | outputs: '', 247 | inputNames: ['a'], 248 | outputNames: [], 249 | implemented: true, 250 | stateMutability: '', 251 | visibility: 'external', 252 | overrides: null, 253 | }); 254 | }); 255 | }); 256 | -------------------------------------------------------------------------------- /test/unit/context/internalFunctionContext.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { DataLocation, FunctionStateMutability, FunctionVisibility, UserDefinedTypeName, UserDefinedValueTypeDefinition } from 'solc-typed-ast'; 3 | import { mockElementaryTypeName, mockFunctionDefinition, mockParameterList, mockVariableDeclaration } from '../../mocks'; 4 | import { internalFunctionContext } from '../../../src/context'; 5 | 6 | describe('internalFunctionContext', () => { 7 | const defaultAttributes = { 8 | name: 'testInternalFunction', 9 | visibility: FunctionVisibility.Internal, 10 | virtual: true, 11 | vParameters: mockParameterList({ vParameters: [] }), 12 | vReturnParameters: mockParameterList({ vParameters: [] }), 13 | implemented: true, 14 | }; 15 | 16 | it('throws an error if the function is not internal', () => { 17 | for (const visibility in FunctionVisibility) { 18 | if (FunctionVisibility[visibility] === FunctionVisibility.Internal) continue; 19 | const node = mockFunctionDefinition({ ...defaultAttributes, visibility: FunctionVisibility[visibility] }); 20 | expect(() => internalFunctionContext(node)).to.throw('The function is not internal'); 21 | } 22 | }); 23 | 24 | it('throws an error if the function is not virtual', () => { 25 | const node = mockFunctionDefinition({ ...defaultAttributes, virtual: false }); 26 | expect(() => internalFunctionContext(node)).to.throw('The function is not virtual'); 27 | }); 28 | 29 | it('processes functions without parameters and returned values', () => { 30 | const node = mockFunctionDefinition(defaultAttributes); 31 | const context = internalFunctionContext(node); 32 | 33 | expect(context).to.eql({ 34 | functionName: 'testInternalFunction', 35 | signature: 'testInternalFunction()', 36 | parameters: '', 37 | inputs: '', 38 | outputs: '', 39 | inputNames: [], 40 | outputNames: [], 41 | inputTypes: [], 42 | outputTypes: [], 43 | implemented: true, 44 | isView: false, 45 | explicitOutputTypes: [], 46 | isPure: false, 47 | }); 48 | }); 49 | 50 | it('processes named parameters', () => { 51 | const parameters = [ 52 | mockVariableDeclaration({ name: 'a', typeString: 'uint256' }), 53 | mockVariableDeclaration({ name: 'b', typeString: 'boolean' }), 54 | ]; 55 | const node = mockFunctionDefinition({ ...defaultAttributes, vParameters: mockParameterList({ vParameters: parameters }) }); 56 | const context = internalFunctionContext(node); 57 | 58 | expect(context).to.eql({ 59 | functionName: 'testInternalFunction', 60 | signature: 'testInternalFunction(uint256,boolean)', 61 | parameters: 'uint256 a, boolean b', 62 | inputs: 'uint256 a, boolean b', 63 | outputs: '', 64 | inputNames: ['a', 'b'], 65 | outputNames: [], 66 | inputTypes: ['uint256', 'boolean'], 67 | outputTypes: [], 68 | implemented: true, 69 | isView: false, 70 | explicitOutputTypes: [], 71 | isPure: false, 72 | }); 73 | }); 74 | 75 | it('processes unnamed parameters', () => { 76 | const parameters = [mockVariableDeclaration({ typeString: 'uint256' }), mockVariableDeclaration({ typeString: 'boolean' })]; 77 | const node = mockFunctionDefinition({ ...defaultAttributes, vParameters: mockParameterList({ vParameters: parameters }) }); 78 | const context = internalFunctionContext(node); 79 | 80 | expect(context).to.eql({ 81 | functionName: 'testInternalFunction', 82 | signature: 'testInternalFunction(uint256,boolean)', 83 | parameters: 'uint256 _param0, boolean _param1', 84 | inputs: 'uint256 _param0, boolean _param1', 85 | outputs: '', 86 | inputNames: ['_param0', '_param1'], 87 | outputNames: [], 88 | inputTypes: ['uint256', 'boolean'], 89 | outputTypes: [], 90 | implemented: true, 91 | isView: false, 92 | explicitOutputTypes: [], 93 | isPure: false, 94 | }); 95 | }); 96 | 97 | it('processes unnamed returned parameters', () => { 98 | const parameters = [mockVariableDeclaration({ typeString: 'uint256' }), mockVariableDeclaration({ typeString: 'boolean' })]; 99 | const node = mockFunctionDefinition({ ...defaultAttributes, vReturnParameters: mockParameterList({ vParameters: parameters }) }); 100 | const context = internalFunctionContext(node); 101 | 102 | expect(context).to.eql({ 103 | functionName: 'testInternalFunction', 104 | signature: 'testInternalFunction()', 105 | parameters: 'uint256 _returnParam0, boolean _returnParam1', 106 | inputs: '', 107 | outputs: 'uint256 _returnParam0, boolean _returnParam1', 108 | inputNames: [], 109 | outputNames: ['_returnParam0', '_returnParam1'], 110 | inputTypes: [], 111 | outputTypes: ['uint256', 'boolean'], 112 | implemented: true, 113 | isView: false, 114 | explicitOutputTypes: ['uint256', 'boolean'], 115 | isPure: false, 116 | }); 117 | }); 118 | 119 | it('processes named returned parameters', () => { 120 | const parameters = [ 121 | mockVariableDeclaration({ name: 'a', typeString: 'uint256' }), 122 | mockVariableDeclaration({ name: 'b', typeString: 'boolean' }), 123 | ]; 124 | const node = mockFunctionDefinition({ ...defaultAttributes, vReturnParameters: mockParameterList({ vParameters: parameters }) }); 125 | const context = internalFunctionContext(node); 126 | 127 | expect(context).to.eql({ 128 | functionName: 'testInternalFunction', 129 | signature: 'testInternalFunction()', 130 | parameters: 'uint256 a, boolean b', 131 | inputs: '', 132 | outputs: 'uint256 a, boolean b', 133 | inputNames: [], 134 | outputNames: ['a', 'b'], 135 | inputTypes: [], 136 | outputTypes: ['uint256', 'boolean'], 137 | implemented: true, 138 | isView: false, 139 | explicitOutputTypes: ['uint256', 'boolean'], 140 | isPure: false, 141 | }); 142 | }); 143 | 144 | it('recognizes storage location of parameters', () => { 145 | const parameters = [ 146 | mockVariableDeclaration({ name: 'a', typeString: 'uint256', storageLocation: DataLocation.Storage }), 147 | mockVariableDeclaration({ name: 'b', typeString: 'string', storageLocation: DataLocation.Memory }), 148 | mockVariableDeclaration({ name: 'c', typeString: 'bytes', storageLocation: DataLocation.CallData }), 149 | mockVariableDeclaration({ name: 'd', typeString: 'boolean', storageLocation: DataLocation.Default }), 150 | ]; 151 | const node = mockFunctionDefinition({ ...defaultAttributes, vParameters: mockParameterList({ vParameters: parameters }) }); 152 | const context = internalFunctionContext(node); 153 | 154 | expect(context).to.eql({ 155 | functionName: 'testInternalFunction', 156 | signature: 'testInternalFunction(uint256,string,bytes,boolean)', 157 | parameters: 'uint256 a, string memory b, bytes calldata c, boolean d', 158 | inputs: 'uint256 a, string memory b, bytes calldata c, boolean d', 159 | outputs: '', 160 | inputNames: ['a', 'b', 'c', 'd'], 161 | outputNames: [], 162 | inputTypes: ['uint256', 'string', 'bytes', 'boolean'], 163 | outputTypes: [], 164 | implemented: true, 165 | isView: false, 166 | explicitOutputTypes: [], 167 | isPure: false, 168 | }); 169 | }); 170 | 171 | it('recognizes storage location of returned parameters', () => { 172 | const parameters = [ 173 | mockVariableDeclaration({ name: 'a', typeString: 'uint256', storageLocation: DataLocation.Storage }), 174 | mockVariableDeclaration({ name: 'b', typeString: 'string', storageLocation: DataLocation.Memory }), 175 | mockVariableDeclaration({ name: 'c', typeString: 'bytes', storageLocation: DataLocation.CallData }), 176 | mockVariableDeclaration({ name: 'd', typeString: 'boolean', storageLocation: DataLocation.Default }), 177 | ]; 178 | const node = mockFunctionDefinition({ ...defaultAttributes, vReturnParameters: mockParameterList({ vParameters: parameters }) }); 179 | const context = internalFunctionContext(node); 180 | 181 | expect(context).to.eql({ 182 | functionName: 'testInternalFunction', 183 | signature: 'testInternalFunction()', 184 | parameters: 'uint256 a, string memory b, bytes calldata c, boolean d', 185 | inputs: '', 186 | outputs: 'uint256 a, string memory b, bytes calldata c, boolean d', 187 | inputNames: [], 188 | outputNames: ['a', 'b', 'c', 'd'], 189 | inputTypes: [], 190 | outputTypes: ['uint256', 'string', 'bytes', 'boolean'], 191 | implemented: true, 192 | isView: false, 193 | explicitOutputTypes: ['uint256', 'string memory', 'bytes memory', 'boolean'], 194 | isPure: false, 195 | }); 196 | }); 197 | 198 | it('determines whether the function is view or not', () => { 199 | for (const stateMutability in FunctionStateMutability) { 200 | const isView = FunctionStateMutability[stateMutability] === FunctionStateMutability.View; 201 | const isPure = FunctionStateMutability[stateMutability] === FunctionStateMutability.Pure; 202 | const node = mockFunctionDefinition({ ...defaultAttributes, stateMutability: FunctionStateMutability[stateMutability] }); 203 | const context = internalFunctionContext(node); 204 | expect(context.isView).to.be.equal(isView || isPure); 205 | } 206 | }); 207 | 208 | it('determines whether the function is pure or not', () => { 209 | for (const stateMutability in FunctionStateMutability) { 210 | const isPure = FunctionStateMutability[stateMutability] === FunctionStateMutability.Pure; 211 | const node = mockFunctionDefinition({ ...defaultAttributes, stateMutability: FunctionStateMutability[stateMutability] }); 212 | const context = internalFunctionContext(node); 213 | expect(context.isPure).to.be.equal(isPure); 214 | } 215 | }); 216 | 217 | it('determines whether the function is implemented or not', () => { 218 | for (const implemented of [true, false]) { 219 | const node = mockFunctionDefinition({ ...defaultAttributes, implemented: implemented }); 220 | const context = internalFunctionContext(node); 221 | expect(context.implemented).to.be.equal(implemented); 222 | } 223 | }); 224 | 225 | it('process functions with user defined value types', () => { 226 | const vReferencedDeclaration = new UserDefinedValueTypeDefinition(0, '', 'MyType', mockElementaryTypeName({ typeString: 'uint256' })); 227 | 228 | const vType = Object.create(UserDefinedTypeName.prototype, { 229 | vReferencedDeclaration: { value: vReferencedDeclaration }, 230 | }); 231 | 232 | const node = mockFunctionDefinition({ 233 | ...defaultAttributes, 234 | vParameters: mockParameterList({ 235 | vParameters: [ 236 | mockVariableDeclaration({ 237 | name: 'a', 238 | typeString: 'MyType', 239 | vType, 240 | }), 241 | ], 242 | }), 243 | }); 244 | 245 | const context = internalFunctionContext(node); 246 | 247 | expect(context).to.eql({ 248 | functionName: 'testInternalFunction', 249 | signature: 'testInternalFunction(uint256)', 250 | parameters: 'MyType a', 251 | inputs: 'MyType a', 252 | outputs: '', 253 | inputNames: ['a'], 254 | outputNames: [], 255 | inputTypes: ['uint256'], 256 | outputTypes: [], 257 | implemented: true, 258 | isView: false, 259 | explicitOutputTypes: [], 260 | isPure: false, 261 | }); 262 | }); 263 | }); 264 | -------------------------------------------------------------------------------- /test/unit/context/mappingVariableContext.spec.ts: -------------------------------------------------------------------------------- 1 | import { StateVariableVisibility } from 'solc-typed-ast'; 2 | import { mockArrayTypeName, mockMapping, mockTypeName, mockUserDefinedTypeName, mockVariableDeclaration } from '../../mocks'; 3 | import { mappingVariableContext } from '../../../src/context'; 4 | import { expect } from 'chai'; 5 | 6 | describe('mappingVariableContext', () => { 7 | const defaultAttributes = { 8 | name: 'testMappingVariable', 9 | typeString: 'mapping(uint256 => uint256)', 10 | vType: mockMapping({ vKeyType: mockTypeName({ typeString: 'uint256' }), vValueType: mockTypeName({ typeString: 'uint256' }) }), 11 | visibility: StateVariableVisibility.Default, 12 | }; 13 | 14 | it('should return the correct context for a non-struct non-array mapping', () => { 15 | const node = mockVariableDeclaration(defaultAttributes); 16 | const context = mappingVariableContext(node); 17 | 18 | expect(context).to.eql({ 19 | setFunction: { 20 | functionName: 'testMappingVariable', 21 | keyTypes: ['uint256'], 22 | valueType: 'uint256', 23 | }, 24 | mockFunction: { 25 | functionName: 'testMappingVariable', 26 | keyTypes: ['uint256'], 27 | valueType: 'uint256', 28 | baseType: 'uint256', 29 | structFields: [], 30 | }, 31 | isInternal: false, 32 | isArray: false, 33 | isStruct: false, 34 | isStructArray: false, 35 | hasNestedMapping: false, 36 | }); 37 | }); 38 | 39 | it('should return the correct context for a struct mapping', () => { 40 | const node = mockVariableDeclaration({ 41 | ...defaultAttributes, 42 | typeString: 'mapping(uint256 => struct MyStruct)', 43 | vType: mockMapping({ 44 | vKeyType: mockTypeName({ typeString: 'uint256' }), 45 | vValueType: mockTypeName({ typeString: 'struct MyStruct' }), 46 | }), 47 | }); 48 | const context = mappingVariableContext(node); 49 | 50 | expect(context).to.eql({ 51 | setFunction: { 52 | functionName: 'testMappingVariable', 53 | keyTypes: ['uint256'], 54 | valueType: 'MyStruct memory', 55 | }, 56 | mockFunction: { 57 | functionName: 'testMappingVariable', 58 | keyTypes: ['uint256'], 59 | valueType: 'MyStruct memory', 60 | baseType: 'MyStruct memory', 61 | structFields: [], 62 | }, 63 | isInternal: false, 64 | isArray: false, 65 | isStruct: false, 66 | isStructArray: false, 67 | hasNestedMapping: false, 68 | }); 69 | }); 70 | 71 | it('should return the correct context for an array mapping', () => { 72 | const node = mockVariableDeclaration({ 73 | ...defaultAttributes, 74 | typeString: 'mapping(uint256 => uint256[])', 75 | vType: mockMapping({ 76 | vKeyType: mockTypeName({ typeString: 'uint256' }), 77 | vValueType: mockArrayTypeName({ vBaseType: mockTypeName({ typeString: 'uint256' }), typeString: 'uint256[]' }), 78 | }), 79 | }); 80 | const context = mappingVariableContext(node); 81 | 82 | expect(context).to.eql({ 83 | setFunction: { 84 | functionName: 'testMappingVariable', 85 | keyTypes: ['uint256'], 86 | valueType: 'uint256[] memory', 87 | }, 88 | mockFunction: { 89 | functionName: 'testMappingVariable', 90 | keyTypes: ['uint256'], 91 | valueType: 'uint256[] memory', 92 | baseType: 'uint256', 93 | structFields: [], 94 | }, 95 | isInternal: false, 96 | isArray: true, 97 | isStruct: false, 98 | isStructArray: false, 99 | hasNestedMapping: false, 100 | }); 101 | }); 102 | 103 | it('should return the correct context for an internal mapping', () => { 104 | const node = mockVariableDeclaration({ ...defaultAttributes, visibility: StateVariableVisibility.Internal }); 105 | const context = mappingVariableContext(node); 106 | 107 | expect(context).to.eql({ 108 | setFunction: { 109 | functionName: 'testMappingVariable', 110 | keyTypes: ['uint256'], 111 | valueType: 'uint256', 112 | }, 113 | mockFunction: { 114 | functionName: 'testMappingVariable', 115 | keyTypes: ['uint256'], 116 | valueType: 'uint256', 117 | baseType: 'uint256', 118 | structFields: [], 119 | }, 120 | isInternal: true, 121 | isArray: false, 122 | isStruct: false, 123 | isStructArray: false, 124 | hasNestedMapping: false, 125 | }); 126 | }); 127 | 128 | it('should return the correct context for a struct array mapping', () => { 129 | const node = mockVariableDeclaration({ 130 | ...defaultAttributes, 131 | typeString: 'mapping(uint256 => struct MyStruct[])', 132 | vType: mockMapping({ 133 | vKeyType: mockTypeName({ typeString: 'uint256' }), 134 | vValueType: mockArrayTypeName({ 135 | typeString: 'struct MyStruct[]', 136 | vBaseType: mockUserDefinedTypeName({ 137 | typeString: 'struct MyStruct', 138 | vReferencedDeclaration: mockTypeName({ 139 | children: [mockVariableDeclaration({ name: 'field1' }), mockVariableDeclaration({ name: 'field2' })], 140 | }), 141 | }), 142 | }), 143 | }), 144 | }); 145 | 146 | const context = mappingVariableContext(node); 147 | 148 | expect(context).to.eql({ 149 | setFunction: { 150 | functionName: 'testMappingVariable', 151 | keyTypes: ['uint256'], 152 | valueType: 'MyStruct[] memory', 153 | }, 154 | mockFunction: { 155 | functionName: 'testMappingVariable', 156 | keyTypes: ['uint256'], 157 | valueType: 'MyStruct[] memory', 158 | baseType: 'MyStruct memory', 159 | structFields: ['field1', 'field2'], 160 | }, 161 | isInternal: false, 162 | isArray: true, 163 | isStruct: true, 164 | isStructArray: true, 165 | hasNestedMapping: false, 166 | }); 167 | }); 168 | 169 | it('should return the correct context for a nested mapping', () => { 170 | const node = mockVariableDeclaration({ 171 | ...defaultAttributes, 172 | typeString: 'mapping(uint256 => mapping(uint256 => uint256))', 173 | vType: mockMapping({ 174 | vKeyType: mockTypeName({ typeString: 'uint256' }), 175 | vValueType: mockMapping({ 176 | typeString: 'mapping(uint256 => uint256)', 177 | vKeyType: mockTypeName({ typeString: 'uint256' }), 178 | vValueType: mockTypeName({ typeString: 'uint256' }), 179 | }), 180 | }), 181 | }); 182 | const context = mappingVariableContext(node); 183 | 184 | expect(context).to.eql({ 185 | setFunction: { 186 | functionName: 'testMappingVariable', 187 | keyTypes: ['uint256', 'uint256'], 188 | valueType: 'uint256', 189 | }, 190 | mockFunction: { 191 | functionName: 'testMappingVariable', 192 | keyTypes: ['uint256', 'uint256'], 193 | valueType: 'uint256', 194 | baseType: 'uint256', 195 | structFields: [], 196 | }, 197 | isInternal: false, 198 | isArray: false, 199 | isStruct: false, 200 | isStructArray: false, 201 | hasNestedMapping: false, 202 | }); 203 | }); 204 | 205 | it('should return the correct context for a nested struct array mapping', () => { 206 | const node = mockVariableDeclaration({ 207 | ...defaultAttributes, 208 | typeString: 'mapping(uint256 => mapping(uint256 => struct MyStruct[]))', 209 | vType: mockMapping({ 210 | vKeyType: mockTypeName({ typeString: 'uint256' }), 211 | vValueType: mockMapping({ 212 | typeString: 'mapping(uint256 => struct MyStruct[])', 213 | vKeyType: mockTypeName({ typeString: 'uint256' }), 214 | vValueType: mockArrayTypeName({ 215 | typeString: 'struct MyStruct[]', 216 | vBaseType: mockUserDefinedTypeName({ 217 | typeString: 'struct MyStruct', 218 | vReferencedDeclaration: mockTypeName({ 219 | children: [mockVariableDeclaration({ name: 'field1' }), mockVariableDeclaration({ name: 'field2' })], 220 | }), 221 | }), 222 | }), 223 | }), 224 | }), 225 | }); 226 | const context = mappingVariableContext(node); 227 | 228 | expect(context).to.eql({ 229 | setFunction: { 230 | functionName: 'testMappingVariable', 231 | keyTypes: ['uint256', 'uint256'], 232 | valueType: 'MyStruct[] memory', 233 | }, 234 | mockFunction: { 235 | functionName: 'testMappingVariable', 236 | keyTypes: ['uint256', 'uint256'], 237 | valueType: 'MyStruct[] memory', 238 | baseType: 'MyStruct memory', 239 | structFields: ['field1', 'field2'], 240 | }, 241 | isInternal: false, 242 | isArray: true, 243 | isStruct: true, 244 | isStructArray: true, 245 | hasNestedMapping: false, 246 | }); 247 | }); 248 | 249 | it('should return the correct context for a triple nested mapping with different types', () => { 250 | const node = mockVariableDeclaration({ 251 | ...defaultAttributes, 252 | typeString: 'mapping(uint256 => mapping(uint128 => mapping(uint64 => uint8)))', 253 | vType: mockMapping({ 254 | vKeyType: mockTypeName({ typeString: 'uint256' }), 255 | vValueType: mockMapping({ 256 | typeString: 'mapping(uint128 => mapping(uint64 => uint8)))', 257 | vKeyType: mockTypeName({ typeString: 'uint128' }), 258 | vValueType: mockMapping({ 259 | typeString: 'mapping(uint64 => uint8)', 260 | vKeyType: mockTypeName({ typeString: 'uint64' }), 261 | vValueType: mockTypeName({ typeString: 'uint8' }), 262 | }), 263 | }), 264 | }), 265 | }); 266 | const context = mappingVariableContext(node); 267 | 268 | expect(context).to.eql({ 269 | setFunction: { 270 | functionName: 'testMappingVariable', 271 | keyTypes: ['uint256', 'uint128', 'uint64'], 272 | valueType: 'uint8', 273 | }, 274 | mockFunction: { 275 | functionName: 'testMappingVariable', 276 | keyTypes: ['uint256', 'uint128', 'uint64'], 277 | valueType: 'uint8', 278 | baseType: 'uint8', 279 | structFields: [], 280 | }, 281 | isInternal: false, 282 | isArray: false, 283 | isStruct: false, 284 | isStructArray: false, 285 | hasNestedMapping: false, 286 | }); 287 | }); 288 | 289 | it('should return the correct context for a mapping with a struct with nested mappings', () => { 290 | const node = mockVariableDeclaration({ 291 | ...defaultAttributes, 292 | typeString: 'mapping(uint256 => struct MyStruct)', 293 | vType: mockMapping({ 294 | vKeyType: mockTypeName({ typeString: 'uint256' }), 295 | vValueType: mockUserDefinedTypeName({ 296 | typeString: 'struct MyStruct', 297 | vReferencedDeclaration: mockUserDefinedTypeName({ 298 | children: [ 299 | mockVariableDeclaration({ name: 'field1', typeString: 'mapping(uint256 => uint256)' }), 300 | mockVariableDeclaration({ name: 'field2', typeString: 'uint256)' }), 301 | ], 302 | }), 303 | }), 304 | }), 305 | }); 306 | const context = mappingVariableContext(node); 307 | 308 | expect(context).to.eql({ 309 | setFunction: { 310 | functionName: 'testMappingVariable', 311 | keyTypes: ['uint256'], 312 | valueType: 'MyStruct memory', 313 | }, 314 | mockFunction: { 315 | functionName: 'testMappingVariable', 316 | keyTypes: ['uint256'], 317 | valueType: 'MyStruct memory', 318 | baseType: 'MyStruct memory', 319 | structFields: ['field1', 'field2'], 320 | }, 321 | isInternal: false, 322 | isArray: false, 323 | isStruct: true, 324 | isStructArray: false, 325 | hasNestedMapping: true, 326 | }); 327 | }); 328 | 329 | it('should return the correct context for a nested struct array mapping with nested mapping', () => { 330 | const node = mockVariableDeclaration({ 331 | ...defaultAttributes, 332 | typeString: 'mapping(uint256 => mapping(uint256 => struct MyStruct[]))', 333 | vType: mockMapping({ 334 | vKeyType: mockTypeName({ typeString: 'uint256' }), 335 | vValueType: mockMapping({ 336 | typeString: 'mapping(uint256 => struct MyStruct[])', 337 | vKeyType: mockTypeName({ typeString: 'uint256' }), 338 | vValueType: mockArrayTypeName({ 339 | typeString: 'struct MyStruct[]', 340 | vBaseType: mockUserDefinedTypeName({ 341 | typeString: 'struct MyStruct', 342 | vReferencedDeclaration: mockTypeName({ 343 | typeString: 'struct MyStruct', 344 | children: [ 345 | mockVariableDeclaration({ name: 'field1', typeString: 'mapping(uint256 => uint256)' }), 346 | mockVariableDeclaration({ name: 'field2', typeString: 'uint256)' }), 347 | ], 348 | }), 349 | }), 350 | }), 351 | }), 352 | }), 353 | }); 354 | const context = mappingVariableContext(node); 355 | 356 | expect(context).to.eql({ 357 | setFunction: { 358 | functionName: 'testMappingVariable', 359 | keyTypes: ['uint256', 'uint256'], 360 | valueType: 'MyStruct[] memory', 361 | }, 362 | mockFunction: { 363 | functionName: 'testMappingVariable', 364 | keyTypes: ['uint256', 'uint256'], 365 | valueType: 'MyStruct[] memory', 366 | baseType: 'MyStruct memory', 367 | structFields: ['field1', 'field2'], 368 | }, 369 | isInternal: false, 370 | isArray: true, 371 | isStruct: true, 372 | isStructArray: true, 373 | hasNestedMapping: true, 374 | }); 375 | }); 376 | }); 377 | -------------------------------------------------------------------------------- /solidity/test/utils/ContractG.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import {Test} from 'forge-std/Test.sol'; 5 | import {MockContractG} from 'test/smock/contracts/utils/MockContractG.sol'; 6 | import {SmockHelper} from 'test/smock/SmockHelper.sol'; 7 | import {ContractG} from 'contracts/utils/ContractG.sol'; 8 | 9 | contract UnitMockContractG is Test, SmockHelper { 10 | address internal _owner = makeAddr('owner'); 11 | MockContractG internal _contractTest; 12 | 13 | function setUp() public { 14 | vm.prank(_owner); 15 | 16 | _contractTest = MockContractG(deployMock('TestContractG', type(MockContractG).creationCode, abi.encode())); 17 | } 18 | 19 | function test_Set_MyStructArray() public { 20 | ContractG.CommonStruct[] memory _myStructArray = new ContractG.CommonStruct[](2); 21 | _myStructArray[0] = ContractG.CommonStruct(10); 22 | _myStructArray[1] = ContractG.CommonStruct(20); 23 | 24 | _contractTest.set_structArray(_myStructArray); 25 | (uint256 _value) = _contractTest.structArray(0); 26 | assertEq(_value, 10); 27 | 28 | (_value) = _contractTest.structArray(1); 29 | assertEq(_value, 20); 30 | } 31 | 32 | function test_Set_TwoDimensionalArray() public { 33 | uint256[][] memory _twoDimensionalStruct = new uint256[][](2); 34 | _twoDimensionalStruct[0] = new uint256[](2); 35 | _twoDimensionalStruct[1] = new uint256[](2); 36 | 37 | _twoDimensionalStruct[0][0] = 10; 38 | _twoDimensionalStruct[0][1] = 20; 39 | _twoDimensionalStruct[1][0] = 30; 40 | _twoDimensionalStruct[1][1] = 40; 41 | 42 | _contractTest.set_twoDimensionalArray(_twoDimensionalStruct); 43 | (uint256 _value) = _contractTest.twoDimensionalArray(0, 0); 44 | assertEq(_value, 10); 45 | 46 | (_value) = _contractTest.twoDimensionalArray(0, 1); 47 | assertEq(_value, 20); 48 | 49 | (_value) = _contractTest.twoDimensionalArray(1, 0); 50 | assertEq(_value, 30); 51 | 52 | (_value) = _contractTest.twoDimensionalArray(1, 1); 53 | assertEq(_value, 40); 54 | } 55 | 56 | function test_Call_TwoDimensionalArray() public { 57 | uint256[][] memory _twoDimensionalStruct = new uint256[][](2); 58 | _twoDimensionalStruct[0] = new uint256[](2); 59 | _twoDimensionalStruct[1] = new uint256[](2); 60 | 61 | _twoDimensionalStruct[0][0] = 10; 62 | _twoDimensionalStruct[0][1] = 20; 63 | _twoDimensionalStruct[1][0] = 30; 64 | _twoDimensionalStruct[1][1] = 40; 65 | 66 | _contractTest.mock_call_twoDimensionalArray(0, 0, _twoDimensionalStruct[0][0]); 67 | (uint256 _value) = _contractTest.twoDimensionalArray(0, 0); 68 | assertEq(_value, 10); 69 | 70 | _contractTest.mock_call_twoDimensionalArray(0, 1, _twoDimensionalStruct[0][1]); 71 | (_value) = _contractTest.twoDimensionalArray(0, 1); 72 | assertEq(_value, 20); 73 | 74 | _contractTest.mock_call_twoDimensionalArray(1, 0, _twoDimensionalStruct[1][0]); 75 | (_value) = _contractTest.twoDimensionalArray(1, 0); 76 | assertEq(_value, 30); 77 | 78 | _contractTest.mock_call_twoDimensionalArray(1, 1, _twoDimensionalStruct[1][1]); 79 | (_value) = _contractTest.twoDimensionalArray(1, 1); 80 | assertEq(_value, 40); 81 | } 82 | 83 | function test_Set_ThreeDimensionalArray() public { 84 | uint256[][][] memory _threeDimensionalStruct = new uint256[][][](2); 85 | _threeDimensionalStruct[0] = new uint256[][](2); 86 | _threeDimensionalStruct[1] = new uint256[][](2); 87 | 88 | _threeDimensionalStruct[0][0] = new uint256[](2); 89 | _threeDimensionalStruct[0][1] = new uint256[](2); 90 | _threeDimensionalStruct[1][0] = new uint256[](2); 91 | _threeDimensionalStruct[1][1] = new uint256[](2); 92 | 93 | _threeDimensionalStruct[0][0][0] = 10; 94 | _threeDimensionalStruct[0][0][1] = 20; 95 | _threeDimensionalStruct[0][1][0] = 30; 96 | _threeDimensionalStruct[0][1][1] = 40; 97 | _threeDimensionalStruct[1][0][0] = 50; 98 | _threeDimensionalStruct[1][0][1] = 60; 99 | _threeDimensionalStruct[1][1][0] = 70; 100 | _threeDimensionalStruct[1][1][1] = 80; 101 | 102 | _contractTest.set_threeDimensionalArray(_threeDimensionalStruct); 103 | (uint256 _value) = _contractTest.threeDimensionalArray(0, 0, 0); 104 | assertEq(_value, 10); 105 | 106 | (_value) = _contractTest.threeDimensionalArray(0, 0, 1); 107 | assertEq(_value, 20); 108 | 109 | (_value) = _contractTest.threeDimensionalArray(0, 1, 0); 110 | assertEq(_value, 30); 111 | 112 | (_value) = _contractTest.threeDimensionalArray(0, 1, 1); 113 | assertEq(_value, 40); 114 | 115 | (_value) = _contractTest.threeDimensionalArray(1, 0, 0); 116 | assertEq(_value, 50); 117 | 118 | (_value) = _contractTest.threeDimensionalArray(1, 0, 1); 119 | assertEq(_value, 60); 120 | 121 | (_value) = _contractTest.threeDimensionalArray(1, 1, 0); 122 | assertEq(_value, 70); 123 | 124 | (_value) = _contractTest.threeDimensionalArray(1, 1, 1); 125 | assertEq(_value, 80); 126 | } 127 | 128 | function test_Call_ThreeDimensionalArray() public { 129 | uint256[][][] memory _threeDimensionalStruct = new uint256[][][](2); 130 | _threeDimensionalStruct[0] = new uint256[][](2); 131 | _threeDimensionalStruct[1] = new uint256[][](2); 132 | 133 | _threeDimensionalStruct[0][0] = new uint256[](2); 134 | _threeDimensionalStruct[0][1] = new uint256[](2); 135 | _threeDimensionalStruct[1][0] = new uint256[](2); 136 | _threeDimensionalStruct[1][1] = new uint256[](2); 137 | 138 | _threeDimensionalStruct[0][0][0] = 10; 139 | _threeDimensionalStruct[0][0][1] = 20; 140 | _threeDimensionalStruct[0][1][0] = 30; 141 | _threeDimensionalStruct[0][1][1] = 40; 142 | _threeDimensionalStruct[1][0][0] = 50; 143 | _threeDimensionalStruct[1][0][1] = 60; 144 | _threeDimensionalStruct[1][1][0] = 70; 145 | _threeDimensionalStruct[1][1][1] = 80; 146 | 147 | _contractTest.mock_call_threeDimensionalArray(0, 0, 0, _threeDimensionalStruct[0][0][0]); 148 | (uint256 _value) = _contractTest.threeDimensionalArray(0, 0, 0); 149 | assertEq(_value, 10); 150 | 151 | _contractTest.mock_call_threeDimensionalArray(0, 0, 1, _threeDimensionalStruct[0][0][1]); 152 | (_value) = _contractTest.threeDimensionalArray(0, 0, 1); 153 | assertEq(_value, 20); 154 | 155 | _contractTest.mock_call_threeDimensionalArray(0, 1, 0, _threeDimensionalStruct[0][1][0]); 156 | (_value) = _contractTest.threeDimensionalArray(0, 1, 0); 157 | assertEq(_value, 30); 158 | 159 | _contractTest.mock_call_threeDimensionalArray(0, 1, 1, _threeDimensionalStruct[0][1][1]); 160 | (_value) = _contractTest.threeDimensionalArray(0, 1, 1); 161 | assertEq(_value, 40); 162 | 163 | _contractTest.mock_call_threeDimensionalArray(1, 0, 0, _threeDimensionalStruct[1][0][0]); 164 | (_value) = _contractTest.threeDimensionalArray(1, 0, 0); 165 | assertEq(_value, 50); 166 | 167 | _contractTest.mock_call_threeDimensionalArray(1, 0, 1, _threeDimensionalStruct[1][0][1]); 168 | (_value) = _contractTest.threeDimensionalArray(1, 0, 1); 169 | assertEq(_value, 60); 170 | 171 | _contractTest.mock_call_threeDimensionalArray(1, 1, 0, _threeDimensionalStruct[1][1][0]); 172 | (_value) = _contractTest.threeDimensionalArray(1, 1, 0); 173 | assertEq(_value, 70); 174 | 175 | _contractTest.mock_call_threeDimensionalArray(1, 1, 1, _threeDimensionalStruct[1][1][1]); 176 | (_value) = _contractTest.threeDimensionalArray(1, 1, 1); 177 | assertEq(_value, 80); 178 | } 179 | 180 | function test_Set_TwoDimensionalStringArray() public { 181 | string[][] memory _twoDimensionalStringArray = new string[][](2); 182 | _twoDimensionalStringArray[0] = new string[](2); 183 | _twoDimensionalStringArray[1] = new string[](2); 184 | 185 | _twoDimensionalStringArray[0][0] = '10'; 186 | _twoDimensionalStringArray[0][1] = '20'; 187 | _twoDimensionalStringArray[1][0] = '30'; 188 | _twoDimensionalStringArray[1][1] = '40'; 189 | 190 | _contractTest.set_twoDimensionalStringArray(_twoDimensionalStringArray); 191 | (string memory _value) = _contractTest.twoDimensionalStringArray(0, 0); 192 | assertEq(_value, '10'); 193 | 194 | (_value) = _contractTest.twoDimensionalStringArray(0, 1); 195 | assertEq(_value, '20'); 196 | 197 | (_value) = _contractTest.twoDimensionalStringArray(1, 0); 198 | assertEq(_value, '30'); 199 | 200 | (_value) = _contractTest.twoDimensionalStringArray(1, 1); 201 | assertEq(_value, '40'); 202 | } 203 | 204 | function test_Call_TwoDimensionalStringArray() public { 205 | string[][] memory _twoDimensionalStringArray = new string[][](2); 206 | _twoDimensionalStringArray[0] = new string[](2); 207 | _twoDimensionalStringArray[1] = new string[](2); 208 | 209 | _twoDimensionalStringArray[0][0] = '10'; 210 | _twoDimensionalStringArray[0][1] = '20'; 211 | _twoDimensionalStringArray[1][0] = '30'; 212 | _twoDimensionalStringArray[1][1] = '40'; 213 | 214 | _contractTest.mock_call_twoDimensionalStringArray(0, 0, _twoDimensionalStringArray[0][0]); 215 | (string memory _value) = _contractTest.twoDimensionalStringArray(0, 0); 216 | assertEq(_value, '10'); 217 | 218 | _contractTest.mock_call_twoDimensionalStringArray(0, 1, _twoDimensionalStringArray[0][1]); 219 | (_value) = _contractTest.twoDimensionalStringArray(0, 1); 220 | assertEq(_value, '20'); 221 | 222 | _contractTest.mock_call_twoDimensionalStringArray(1, 0, _twoDimensionalStringArray[1][0]); 223 | (_value) = _contractTest.twoDimensionalStringArray(1, 0); 224 | assertEq(_value, '30'); 225 | 226 | _contractTest.mock_call_twoDimensionalStringArray(1, 1, _twoDimensionalStringArray[1][1]); 227 | (_value) = _contractTest.twoDimensionalStringArray(1, 1); 228 | assertEq(_value, '40'); 229 | } 230 | 231 | function test_Set_NestedStruct() public { 232 | ContractG.CommonStruct memory _commonStruct = ContractG.CommonStruct(20); 233 | ContractG.NestedStruct memory _nestedStruct = ContractG.NestedStruct(10, _commonStruct); 234 | 235 | _contractTest.set_nestedStruct(_nestedStruct); 236 | 237 | (uint256 _counter, ContractG.CommonStruct memory _commonResult) = _contractTest.nestedStruct(); 238 | 239 | assertEq(_counter, 10); 240 | assertEq(_commonResult._value, 20); 241 | } 242 | 243 | function test_Call_NestedStruct() public { 244 | ContractG.CommonStruct memory _commonStruct = ContractG.CommonStruct(20); 245 | ContractG.NestedStruct memory _nestedStruct = ContractG.NestedStruct(10, _commonStruct); 246 | 247 | _contractTest.mock_call_nestedStruct(_nestedStruct); 248 | 249 | (uint256 _counter, ContractG.CommonStruct memory _commonResult) = _contractTest.nestedStruct(); 250 | 251 | assertEq(_counter, 10); 252 | assertEq(_commonResult._value, 20); 253 | } 254 | 255 | function test_Call_SetNestedStruct() public { 256 | ContractG.CommonStruct memory _commonStruct = ContractG.CommonStruct(20); 257 | ContractG.NestedStruct memory _nestedStruct = ContractG.NestedStruct(10, _commonStruct); 258 | 259 | _contractTest.mock_call_setNestedStruct(_nestedStruct); 260 | _contractTest.setNestedStruct(_nestedStruct); 261 | 262 | (uint256 _counter, ContractG.CommonStruct memory _commonResult) = _contractTest.nestedStruct(); 263 | 264 | assertEq(_counter, 10); 265 | assertEq(_commonResult._value, 20); 266 | } 267 | 268 | function test_Set_StructArray() public { 269 | ContractG.CommonStruct[] memory _structArray = new ContractG.CommonStruct[](2); 270 | _structArray[0] = ContractG.CommonStruct(10); 271 | _structArray[1] = ContractG.CommonStruct(20); 272 | 273 | _contractTest.set_structArray(_structArray); 274 | (uint256 _value) = _contractTest.structArray(0); 275 | assertEq(_value, 10); 276 | 277 | (_value) = _contractTest.structArray(1); 278 | assertEq(_value, 20); 279 | } 280 | 281 | function test_Call_StructArray() public { 282 | ContractG.CommonStruct[] memory _structArray = new ContractG.CommonStruct[](2); 283 | _structArray[0] = ContractG.CommonStruct(10); 284 | _structArray[1] = ContractG.CommonStruct(20); 285 | 286 | _contractTest.mock_call_structArray(0, _structArray[0]); 287 | (uint256 _value) = _contractTest.structArray(0); 288 | assertEq(_value, 10); 289 | 290 | _contractTest.mock_call_structArray(1, _structArray[1]); 291 | (_value) = _contractTest.structArray(1); 292 | assertEq(_value, 20); 293 | } 294 | 295 | function test_Set_StructArrayInternal() public { 296 | ContractG.CommonStruct[] memory _structArray = new ContractG.CommonStruct[](2); 297 | _structArray[0] = ContractG.CommonStruct(10); 298 | _structArray[1] = ContractG.CommonStruct(20); 299 | 300 | _contractTest.set__structArrayInternal(_structArray); 301 | 302 | ContractG.CommonStruct[] memory _resultArray = _contractTest.call__structArrayInternal(); 303 | assertEq(_resultArray[0]._value, 10); 304 | assertEq(_resultArray[1]._value, 20); 305 | } 306 | 307 | function test_Set_Finished() public { 308 | bytes32 _key = 'key'; 309 | 310 | _contractTest.set_finished(_key, true); 311 | 312 | bool _value = _contractTest.finished(_key); 313 | 314 | assertTrue(_value); 315 | } 316 | 317 | function test_Call_Finished() public { 318 | bytes32 _key = 'key'; 319 | _contractTest.mock_call_finished(_key, true); 320 | 321 | bool _value = _contractTest.finished(_key); 322 | assertTrue(_value); 323 | } 324 | 325 | function test_Set_FinishedInternal() public { 326 | bytes32 _key = 'key'; 327 | _contractTest.set__finishedInternal(_key, true); 328 | 329 | bool _value = _contractTest.call__finishedInternal(_key); 330 | assertTrue(_value); 331 | } 332 | 333 | function test_Set_DoubleFinishedInternal() public { 334 | bytes32 _key0 = 'key0'; 335 | bytes32 _key1 = 'key1'; 336 | _contractTest.set__doubleFinishedInternal(_key0, _key1, true); 337 | 338 | bool _value = _contractTest.call__doubleFinishedInternal(_key0, _key1); 339 | assertTrue(_value); 340 | } 341 | 342 | function test_Set_NestedStructsInternal() public { 343 | bytes32 _key = 'key'; 344 | ContractG.NestedStruct memory _nestedStruct = ContractG.NestedStruct(10, ContractG.CommonStruct(20)); 345 | 346 | _contractTest.set__nestedStructsInternal(_key, _nestedStruct); 347 | 348 | ContractG.NestedStruct memory _result = _contractTest.call__nestedStructsInternal(_key); 349 | assertEq(_result._counter, 10); 350 | assertEq(_result._common._value, 20); 351 | } 352 | } 353 | -------------------------------------------------------------------------------- /solidity/test/ContractTest.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import {Test} from 'forge-std/Test.sol'; 5 | import {IERC20} from 'isolmate/interfaces/tokens/IERC20.sol'; 6 | import {MockContractTest, IContractTest} from 'test/smock/contracts/MockContractTest.sol'; 7 | import {console} from 'forge-std/console.sol'; 8 | import {SmockHelper} from 'test/smock/SmockHelper.sol'; 9 | 10 | contract CommonE2EBase is Test, SmockHelper { 11 | uint256 internal constant _FORK_BLOCK = 15_452_788; 12 | 13 | address internal _user = makeAddr('user'); 14 | address internal _owner = makeAddr('owner'); 15 | MockContractTest internal _contractTest; 16 | 17 | function setUp() public { 18 | vm.createSelectFork(vm.rpcUrl('mainnet'), _FORK_BLOCK); 19 | vm.prank(_owner); 20 | 21 | _contractTest = MockContractTest( 22 | deployMock('Test', type(MockContractTest).creationCode, abi.encode(1, '2', true, _owner, bytes32('4'))) 23 | ); 24 | } 25 | } 26 | 27 | contract E2EMockContractTest_Set_Simple_Vars is CommonE2EBase { 28 | function test_Set_UintVar() public { 29 | _contractTest.set_uintVariable(1); 30 | assertEq(_contractTest.uintVariable(), 1); 31 | } 32 | 33 | function test_Set_StringVar() public { 34 | _contractTest.set_stringVariable('2'); 35 | assertEq(_contractTest.stringVariable(), '2'); 36 | } 37 | 38 | function test_Set_BoolVar() public { 39 | _contractTest.set_boolVariable(true); 40 | assertEq(_contractTest.boolVariable(), true); 41 | } 42 | 43 | function test_Set_AddressVar() public { 44 | _contractTest.set_addressVariable(_user); 45 | assertEq(_contractTest.addressVariable(), _user); 46 | } 47 | 48 | function test_Set_Bytes32Var() public { 49 | _contractTest.set_bytes32Variable(bytes32('4')); 50 | assertEq(_contractTest.bytes32Variable(), bytes32('4')); 51 | } 52 | 53 | function test_Set_MyStructVar() public { 54 | _contractTest.set_myStructVariable(IContractTest.MyStruct(10, 'ten')); 55 | (uint256 _value, string memory _name) = _contractTest.myStructVariable(); 56 | assertEq(_value, 10); 57 | assertEq(_name, 'ten'); 58 | } 59 | 60 | // internal 61 | function test_Set_InternalUintVar() public { 62 | _contractTest.set_internalUint256(1); 63 | assertEq(_contractTest.getInternalUint256(), 1); 64 | } 65 | } 66 | 67 | contract E2EMockContractTest_Set_Array_Vars is CommonE2EBase { 68 | function test_Set_AddressArray() public { 69 | address[] memory _addressArray = new address[](2); 70 | _addressArray[0] = _user; 71 | _addressArray[1] = _owner; 72 | 73 | _contractTest.set_addressArray(_addressArray); 74 | assertEq(_contractTest.addressArray(0), _user); 75 | assertEq(_contractTest.addressArray(1), _owner); 76 | } 77 | 78 | function test_Set_UintArray() public { 79 | uint256[] memory _uintArray = new uint256[](2); 80 | _uintArray[0] = 1; 81 | _uintArray[1] = 2; 82 | 83 | _contractTest.set_uint256Array(_uintArray); 84 | assertEq(_contractTest.uint256Array(0), 1); 85 | assertEq(_contractTest.uint256Array(1), 2); 86 | } 87 | 88 | function test_Set_Bytes32Array() public { 89 | bytes32[] memory _bytes32Array = new bytes32[](2); 90 | _bytes32Array[0] = bytes32('4'); 91 | _bytes32Array[1] = bytes32('5'); 92 | 93 | _contractTest.set_bytes32Array(_bytes32Array); 94 | assertEq(_contractTest.bytes32Array(0), bytes32('4')); 95 | assertEq(_contractTest.bytes32Array(1), bytes32('5')); 96 | } 97 | 98 | function test_Set_MyStructArray() public { 99 | IContractTest.MyStruct[] memory _myStructArray = new IContractTest.MyStruct[](2); 100 | _myStructArray[0] = IContractTest.MyStruct(10, 'ten'); 101 | _myStructArray[1] = IContractTest.MyStruct(20, 'twenty'); 102 | 103 | _contractTest.set_myStructArray(_myStructArray); 104 | (uint256 _value, string memory _name) = _contractTest.myStructArray(0); 105 | assertEq(_value, 10); 106 | assertEq(_name, 'ten'); 107 | (_value, _name) = _contractTest.myStructArray(1); 108 | assertEq(_value, 20); 109 | assertEq(_name, 'twenty'); 110 | } 111 | 112 | // internal 113 | function test_Set_InternalAddressArray() public { 114 | address[] memory _addressArray = new address[](2); 115 | _addressArray[0] = _user; 116 | _addressArray[1] = _owner; 117 | 118 | _contractTest.set_internalAddressArray(_addressArray); 119 | assertEq(_contractTest.getInternalAddressArray(), _addressArray); 120 | } 121 | } 122 | 123 | contract E2EMockContractTest_Set_Mapping_Vars is CommonE2EBase { 124 | function test_Set_Uint256ToAddressMappings() public { 125 | _contractTest.set_uint256ToAddress(1, _owner); 126 | assertEq(_contractTest.uint256ToAddress(1), _owner); 127 | } 128 | 129 | function test_Set_AddressToUintMappings() public { 130 | _contractTest.set_addressToUint(_user, 1); 131 | assertEq(_contractTest.addressToUint(_user), 1); 132 | } 133 | 134 | function test_Set_Bytes32ToBytesMappings() public { 135 | _contractTest.set_bytes32ToBytes(bytes32('4'), bytes('5')); 136 | assertEq(_contractTest.bytes32ToBytes(bytes32('4')), bytes('5')); 137 | } 138 | 139 | function test_Set_Uint256ToMyStructMappings() public { 140 | _contractTest.set_uint256ToMyStruct(1, IContractTest.MyStruct(10, 'ten')); 141 | (uint256 _value, string memory _name) = _contractTest.uint256ToMyStruct(1); 142 | assertEq(_value, 10); 143 | assertEq(_name, 'ten'); 144 | } 145 | 146 | function test_Set_Uint256ToAddressArrayMappings() public { 147 | address[] memory _addressArray = new address[](2); 148 | _addressArray[0] = _user; 149 | _addressArray[1] = _owner; 150 | 151 | _contractTest.set_uint256ToAddressArray(1, _addressArray); 152 | assertEq(_contractTest.uint256ToAddressArray(1, 0), _user); 153 | assertEq(_contractTest.uint256ToAddressArray(1, 1), _owner); 154 | } 155 | 156 | function test_Set_Uint256ToMyStructArray() public { 157 | IContractTest.MyStruct[] memory _myStructArray = new IContractTest.MyStruct[](2); 158 | _myStructArray[0] = IContractTest.MyStruct(10, 'ten'); 159 | _myStructArray[1] = IContractTest.MyStruct(20, 'twenty'); 160 | 161 | _contractTest.set_uint256ToMyStructArray(1, _myStructArray); 162 | (uint256 _value, string memory _name) = _contractTest.uint256ToMyStructArray(1, 0); 163 | assertEq(_value, 10); 164 | assertEq(_name, 'ten'); 165 | (_value, _name) = _contractTest.uint256ToMyStructArray(1, 1); 166 | assertEq(_value, 20); 167 | assertEq(_name, 'twenty'); 168 | } 169 | 170 | function test_Set_Uint256ToAddressToBytes32Mappings() public { 171 | _contractTest.set_uint256ToAddressToBytes32(1, _owner, bytes32('4')); 172 | assertEq(_contractTest.uint256ToAddressToBytes32(1, _owner), bytes32('4')); 173 | } 174 | 175 | // internal 176 | function test_SetInternalUint256ToAddressMappings() public { 177 | _contractTest.set_internalUint256ToAddress(1, _owner); 178 | assertEq(_contractTest.getInternalUint256ToAddress(1), _owner); 179 | } 180 | } 181 | 182 | contract E2EMockContractTest_Mock_call_Simple_Vars is CommonE2EBase { 183 | function test_MockCall_UintVar() public { 184 | _contractTest.mock_call_uintVariable(10); 185 | assertEq(_contractTest.uintVariable(), 10); 186 | } 187 | 188 | function test_MockCall_StringVar() public { 189 | _contractTest.mock_call_stringVariable('20'); 190 | assertEq(_contractTest.stringVariable(), '20'); 191 | } 192 | 193 | function test_MockCall_BoolVar() public { 194 | _contractTest.mock_call_boolVariable(true); 195 | assertEq(_contractTest.boolVariable(), true); 196 | } 197 | 198 | function test_MockCall_AddressVar() public { 199 | _contractTest.mock_call_addressVariable(_user); 200 | assertEq(_contractTest.addressVariable(), _user); 201 | } 202 | 203 | function test_MockCall_Bytes32Var() public { 204 | _contractTest.mock_call_bytes32Variable(bytes32('40')); 205 | assertEq(_contractTest.bytes32Variable(), bytes32('40')); 206 | } 207 | 208 | function test_MockCall_MyStructVar() public { 209 | _contractTest.mock_call_myStructVariable(IContractTest.MyStruct(100, 'hundred')); 210 | (uint256 _value, string memory _name) = _contractTest.myStructVariable(); 211 | assertEq(_value, 100); 212 | assertEq(_name, 'hundred'); 213 | } 214 | 215 | function test_MockCall_InternalUintVar_Fail() public { 216 | // no mock calls for internal vars 217 | (bool _success,) = 218 | address(_contractTest).call(abi.encodeWithSignature('mock_call_internalUintVariable(uint256)', 10)); 219 | assertEq(_success, false); 220 | } 221 | } 222 | 223 | contract E2EMockContractTest_Mock_call_Array_Vars is CommonE2EBase { 224 | function test_MockCall_AddressArray() public { 225 | _contractTest.mock_call_addressArray(0, _user); 226 | assertEq(_contractTest.addressArray(0), _user); 227 | } 228 | 229 | function test_MockCall_UintArray() public { 230 | _contractTest.mock_call_uint256Array(0, 10); 231 | assertEq(_contractTest.uint256Array(0), 10); 232 | } 233 | 234 | function test_MockCall_Bytes32Array() public { 235 | _contractTest.mock_call_bytes32Array(0, bytes32('40')); 236 | assertEq(_contractTest.bytes32Array(0), bytes32('40')); 237 | } 238 | 239 | function test_MockCall_MyStructArray() public { 240 | _contractTest.mock_call_myStructArray(0, IContractTest.MyStruct(100, 'hundred')); 241 | (uint256 _value, string memory _name) = _contractTest.myStructArray(0); 242 | assertEq(_value, 100); 243 | assertEq(_name, 'hundred'); 244 | } 245 | 246 | function test_MockCall_InternalAddressArray_Fail() public { 247 | // no mock calls for internal vars 248 | (bool _success,) = 249 | address(_contractTest).call(abi.encodeWithSignature('mock_call_internalAddressArray(uint256,address)', 0, _user)); 250 | assertEq(_success, false); 251 | } 252 | } 253 | 254 | contract E2EMockContractTest_Mock_call_Mapping_Vars is CommonE2EBase { 255 | function test_MockCall_Uint256ToAddressMappings() public { 256 | _contractTest.mock_call_uint256ToAddress(10, _owner); 257 | assertEq(_contractTest.uint256ToAddress(10), _owner); 258 | } 259 | 260 | function test_MockCall_AddressToUintMappings() public { 261 | _contractTest.mock_call_addressToUint(_user, 10); 262 | assertEq(_contractTest.addressToUint(_user), 10); 263 | } 264 | 265 | function test_MockCall_Bytes32ToBytesMappings() public { 266 | _contractTest.mock_call_bytes32ToBytes(bytes32('40'), bytes('50')); 267 | assertEq(_contractTest.bytes32ToBytes(bytes32('40')), bytes('50')); 268 | } 269 | 270 | function test_MockCall_Uint256ToMyStructMappings() public { 271 | _contractTest.mock_call_uint256ToMyStruct(10, IContractTest.MyStruct(100, 'hundred')); 272 | (uint256 _value, string memory _name) = _contractTest.uint256ToMyStruct(10); 273 | assertEq(_value, 100); 274 | assertEq(_name, 'hundred'); 275 | } 276 | 277 | function test_MockCall_Uint256ToAddressArrayMappings() public { 278 | _contractTest.mock_call_uint256ToAddressArray(10, 0, _user); 279 | assertEq(_contractTest.uint256ToAddressArray(10, 0), _user); 280 | } 281 | 282 | function test_MockCall_Uint256ToMyStructArrayMappings() public { 283 | _contractTest.mock_call_uint256ToMyStructArray(10, 0, IContractTest.MyStruct(100, 'hundred')); 284 | (uint256 _value, string memory _name) = _contractTest.uint256ToMyStructArray(10, 0); 285 | assertEq(_value, 100); 286 | assertEq(_name, 'hundred'); 287 | } 288 | 289 | function test_MockCall_Uint256ToAddressToBytes32Mappings() public { 290 | _contractTest.mock_call_uint256ToAddressToBytes32(10, _owner, bytes32('40')); 291 | assertEq(_contractTest.uint256ToAddressToBytes32(10, _owner), bytes32('40')); 292 | } 293 | 294 | function test_MockCall_InternalUint256ToAddressMappings_Fail() public { 295 | // no mock calls for internal vars 296 | (bool _success,) = address(_contractTest).call( 297 | abi.encodeWithSignature('mock_call_internalUint256ToAddress(uint256,address)', 10, _owner) 298 | ); 299 | assertEq(_success, false); 300 | } 301 | } 302 | 303 | contract E2EMockContractTest_Mock_call_External_Func is CommonE2EBase { 304 | function test_MockCall_SetVariables1() public { 305 | _contractTest.mock_call_setVariables(10, false); 306 | assertEq(_contractTest.setVariables(10), false); 307 | assertEq(_contractTest.setVariables(11), true); 308 | } 309 | 310 | function test_MockCall_SetVariables2() public { 311 | _contractTest.mock_call_setVariables(20, false, false); 312 | assertEq(_contractTest.setVariables(20, false), false); 313 | assertEq(_contractTest.setVariables(20, true), true); 314 | } 315 | 316 | function test_MockCall_SetStructVariables() public { 317 | IContractTest.MyStruct memory _myStruct = IContractTest.MyStruct(30, 'test'); 318 | _contractTest.mock_call_setStructVariable(_myStruct, true); 319 | assertEq(_contractTest.setStructVariable(_myStruct), true); 320 | } 321 | 322 | function test_MockCall_TestFunc() public { 323 | _contractTest.mock_call_testFunc(2, false); 324 | assertEq(_contractTest.testFunc(2), false); 325 | assertEq(_contractTest.testFunc(3), true); 326 | } 327 | } 328 | 329 | contract E2EMockContractTest_Mock_call_Internal_Func is CommonE2EBase { 330 | function test_MockCall_InternalVirtualFunction() public { 331 | // Expect calls to internal functions 332 | _contractTest.expectCall_internalVirtualFunction(10); 333 | _contractTest.expectCall_internalVirtualFunction(11); 334 | 335 | _contractTest.mock_call_internalVirtualFunction(10, false, 12, 'TEST'); 336 | (bool _res1, uint256 _res2, string memory _res3) = _contractTest.callInternalVirtualFunction(10); 337 | assertEq(_res1, false); 338 | assertEq(_res2, 12); 339 | assertEq(_res3, 'TEST'); 340 | 341 | (_res1, _res2, _res3) = _contractTest.callInternalVirtualFunction(11); 342 | assertEq(_res1, true); 343 | assertEq(_res2, 1); 344 | assertEq(_res3, 'test'); 345 | } 346 | 347 | function test_call_InternalVirtualFunction() public { 348 | // Expect calls to internal functions 349 | _contractTest.expectCall_internalVirtualFunction(10); 350 | _contractTest.expectCall_internalVirtualFunction(11); 351 | 352 | _contractTest.mock_call_internalVirtualFunction(10, false, 12, 'TEST'); 353 | (bool _res1, uint256 _res2, string memory _res3) = _contractTest.call_internalVirtualFunction(10); 354 | assertEq(_res1, false); 355 | assertEq(_res2, 12); 356 | assertEq(_res3, 'TEST'); 357 | 358 | (_res1, _res2, _res3) = _contractTest.call_internalVirtualFunction(11); 359 | assertEq(_res1, true); 360 | assertEq(_res2, 1); 361 | assertEq(_res3, 'test'); 362 | } 363 | 364 | function test_MockCall_InternalViewVirtualFunction() public { 365 | // Expect calls to internal functions 366 | _contractTest.expectCall_internalViewVirtualFunction(10); 367 | _contractTest.expectCall_internalViewVirtualFunction(11); 368 | 369 | _contractTest.mock_call_internalViewVirtualFunction(10, false, 12, 'TEST'); 370 | (bool _res1, uint256 _res2, string memory _res3) = _contractTest.callInternalViewVirtualFunction(10); 371 | assertEq(_res1, false); 372 | assertEq(_res2, 12); 373 | assertEq(_res3, 'TEST'); 374 | 375 | (_res1, _res2, _res3) = _contractTest.callInternalViewVirtualFunction(11); 376 | assertEq(_res1, true); 377 | assertEq(_res2, 1); 378 | assertEq(_res3, 'test'); 379 | } 380 | 381 | function test_call_InternalViewVirtualFunction() public { 382 | // Expect calls to internal functions 383 | _contractTest.expectCall_internalViewVirtualFunction(10); 384 | _contractTest.expectCall_internalViewVirtualFunction(11); 385 | 386 | _contractTest.mock_call_internalViewVirtualFunction(10, false, 12, 'TEST'); 387 | (bool _res1, uint256 _res2, string memory _res3) = _contractTest.call_internalViewVirtualFunction(10); 388 | assertEq(_res1, false); 389 | assertEq(_res2, 12); 390 | assertEq(_res3, 'TEST'); 391 | 392 | (_res1, _res2, _res3) = _contractTest.call_internalViewVirtualFunction(11); 393 | assertEq(_res1, true); 394 | assertEq(_res2, 1); 395 | assertEq(_res3, 'test'); 396 | } 397 | 398 | function test_MockCall_InternalPureVirtualFunction() public { 399 | // Expect calls to internal functions 400 | _contractTest.expectCall_internalPureVirtualFunction(10); 401 | _contractTest.expectCall_internalPureVirtualFunction(11); 402 | 403 | _contractTest.mock_call_internalPureVirtualFunction(10, false, 12, 'TEST'); 404 | (bool _res1, uint256 _res2, string memory _res3) = _contractTest.callInternalPureVirtualFunction(10); 405 | assertEq(_res1, false); 406 | assertEq(_res2, 12); 407 | assertEq(_res3, 'TEST'); 408 | 409 | (_res1, _res2, _res3) = _contractTest.callInternalPureVirtualFunction(11); 410 | assertEq(_res1, true); 411 | assertEq(_res2, 11); 412 | assertEq(_res3, 'test'); 413 | } 414 | 415 | function test_call_InternalPureVirtualFunction() public { 416 | // Expect calls to internal functions 417 | _contractTest.expectCall_internalPureVirtualFunction(10); 418 | _contractTest.expectCall_internalPureVirtualFunction(11); 419 | 420 | _contractTest.mock_call_internalPureVirtualFunction(10, false, 12, 'TEST'); 421 | (bool _res1, uint256 _res2, string memory _res3) = _contractTest.call_internalPureVirtualFunction(10); 422 | assertEq(_res1, false); 423 | assertEq(_res2, 12); 424 | assertEq(_res3, 'TEST'); 425 | 426 | (_res1, _res2, _res3) = _contractTest.call_internalPureVirtualFunction(11); 427 | assertEq(_res1, true); 428 | assertEq(_res2, 11); 429 | assertEq(_res3, 'test'); 430 | } 431 | } 432 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import Handlebars from 'handlebars'; 2 | import path from 'path'; 3 | import { glob } from 'fast-glob'; 4 | import { 5 | VariableDeclaration, 6 | FunctionDefinition, 7 | ImportDirective, 8 | ASTNode, 9 | ASTKind, 10 | ASTReader, 11 | SourceUnit, 12 | compileSol, 13 | ContractDefinition, 14 | FunctionVisibility, 15 | TypeName, 16 | UserDefinedTypeName, 17 | ArrayTypeName, 18 | FunctionKind, 19 | UserDefinedValueTypeDefinition, 20 | } from 'solc-typed-ast'; 21 | import { userDefinedTypes, explicitTypes, FullFunctionDefinition, SelectorsMap } from './types'; 22 | import { readFileSync } from 'fs'; // TODO: Replace with fs/promises 23 | import { ensureDir, emptyDir } from 'fs-extra'; 24 | import fs from 'fs/promises'; 25 | import { 26 | importContext, 27 | mappingVariableContext, 28 | arrayVariableContext, 29 | stateVariableContext, 30 | constructorContext, 31 | externalOrPublicFunctionContext, 32 | internalFunctionContext, 33 | } from './context'; 34 | import { exec } from 'child_process'; 35 | 36 | /** 37 | * Fixes user-defined types 38 | * @param type The string of the type to fix 39 | * @returns The string with the type fixed 40 | */ 41 | export function sanitizeParameterType(type: string): string { 42 | const regExp = new RegExp(`^(${userDefinedTypes.join('|')}) `); 43 | return type.replace(regExp, ''); 44 | } 45 | 46 | /** 47 | * Explicits a type's storage location, if required 48 | * @param type The string of the type to explicit 49 | * @returns The string with the type explicit 50 | */ 51 | export function explicitTypeStorageLocation(type: string): string { 52 | const regExp = new RegExp(`^(${explicitTypes.join('|')})\\b`); 53 | if (regExp.test(type) || type.includes('[]')) { 54 | return `${type} memory`; 55 | } else { 56 | return type; 57 | } 58 | } 59 | 60 | /** 61 | * Reads a template file 62 | * @param templateName The name of the template 63 | * @param templatePath The path of the template (if it's nested) 64 | * @returns The content of the template 65 | */ 66 | export function readTemplate(templateName: string, templatePath: string[] = []): string { 67 | const fullPath = path.resolve(__dirname, 'templates', ...templatePath, `${templateName}.hbs`); 68 | return readFileSync(fullPath, { encoding: 'utf8' }); 69 | } 70 | 71 | /** 72 | * Compiles a template 73 | * @param templateName The name of the template 74 | * @param templatePath The path of the template (if it's nested) 75 | * @returns The compiled template 76 | */ 77 | export function compileTemplate(templateName: string, templatePath?: string[]): HandlebarsTemplateDelegate { 78 | const templateContent = readTemplate(templateName, templatePath); 79 | return Handlebars.compile(templateContent, { noEscape: true }); 80 | } 81 | 82 | /** 83 | * Gets the base contract template 84 | * @returns The contract template 85 | */ 86 | export function getContractTemplate(): HandlebarsTemplateDelegate { 87 | return compileTemplate('contract-template'); 88 | } 89 | 90 | /** 91 | * Gets the smock helper template 92 | * @returns The helper template 93 | */ 94 | export function getSmockHelperTemplate(): HandlebarsTemplateDelegate { 95 | return compileTemplate('helper-template'); 96 | } 97 | 98 | /** 99 | * Compiles the solidity files in the given directory calling forge build command 100 | * @param mockContractsDir The directory of the generated contracts 101 | */ 102 | export async function compileSolidityFilesFoundry(rootPath: string, mockContractsDir: string, remappings: string[]) { 103 | console.log('Compiling contracts...'); 104 | try { 105 | const solidityFiles: string[] = await getSolidityFilesAbsolutePaths(rootPath, [mockContractsDir]); 106 | 107 | await compileSol(solidityFiles, 'auto', { 108 | basePath: rootPath, 109 | remapping: remappings, 110 | includePath: [rootPath], 111 | }); 112 | } catch (e) { 113 | throw new Error(`Error while compiling contracts: ${e}`); 114 | } 115 | } 116 | 117 | export async function getSolidityFilesAbsolutePaths(cwd: string, directories: string[]): Promise { 118 | // Map each directory to a glob promise, searching for .sol files 119 | const promises = directories.map((directory) => glob(`${directory}/**/*.sol`, { cwd, ignore: [] })); 120 | const filesArrays = await Promise.all(promises); 121 | const files = filesArrays.flat(); 122 | 123 | return files; 124 | } 125 | 126 | export function extractParameters(parameters: VariableDeclaration[]): { 127 | functionParameters: string[]; 128 | parameterTypes: string[]; 129 | parameterNames: string[]; 130 | } { 131 | const functionParameters = parameters.map((parameter, index) => { 132 | const typeName: string = sanitizeParameterType(parameter.typeString); 133 | const paramName: string = parameter.name || `_param${index}`; 134 | const storageLocation = ['memory', 'calldata'].includes(parameter.storageLocation) ? `${parameter.storageLocation} ` : ''; 135 | return `${typeName} ${storageLocation}${paramName}`; 136 | }); 137 | 138 | const parameterNames = parameters.map((parameter, index) => parameter.name || `_param${index}`); 139 | 140 | const parameterTypes = parameters.map((parameter) => { 141 | if (parameter.vType instanceof UserDefinedTypeName) { 142 | if (parameter.vType.vReferencedDeclaration instanceof UserDefinedValueTypeDefinition) { 143 | return sanitizeParameterType(parameter.vType.vReferencedDeclaration.underlyingType.typeString); 144 | } 145 | } 146 | 147 | return sanitizeParameterType(parameter.typeString); 148 | }); 149 | 150 | return { 151 | functionParameters, 152 | parameterTypes, 153 | parameterNames, 154 | }; 155 | } 156 | 157 | export function extractReturnParameters(returnParameters: VariableDeclaration[]): { 158 | functionReturnParameters: string[]; 159 | returnParameterTypes: string[]; 160 | returnParameterNames: string[]; 161 | returnExplicitParameterTypes: string[]; 162 | } { 163 | const functionReturnParameters = returnParameters.map((parameter: VariableDeclaration, index: number) => { 164 | const typeName: string = sanitizeParameterType(parameter.typeString); 165 | const paramName: string = parameter.name || `_returnParam${index}`; 166 | const storageLocation = ['memory', 'calldata'].includes(parameter.storageLocation) ? `${parameter.storageLocation} ` : ''; 167 | return `${typeName} ${storageLocation}${paramName}`; 168 | }); 169 | 170 | const returnParameterTypes = returnParameters.map((parameter) => sanitizeParameterType(parameter.typeString)); 171 | const returnParameterNames = returnParameters.map((parameter, index) => parameter.name || `_returnParam${index}`); 172 | const returnExplicitParameterTypes = returnParameters.map((parameter) => 173 | explicitTypeStorageLocation(sanitizeParameterType(parameter.typeString)), 174 | ); 175 | 176 | return { 177 | functionReturnParameters, 178 | returnParameterTypes, 179 | returnParameterNames, 180 | returnExplicitParameterTypes, 181 | }; 182 | } 183 | 184 | export async function renderNodeMock(node: ASTNode): Promise { 185 | const partial = partialName(node); 186 | if (!partial) return ''; 187 | 188 | const CONTEXT_RETRIEVERS = { 189 | 'mapping-state-variable': mappingVariableContext, 190 | 'array-state-variable': arrayVariableContext, 191 | 'state-variable': stateVariableContext, 192 | constructor: constructorContext, 193 | 'external-or-public-function': externalOrPublicFunctionContext, 194 | 'internal-function': internalFunctionContext, 195 | import: importContext, 196 | }; 197 | 198 | const contextRetriever = CONTEXT_RETRIEVERS[partial]; 199 | if (!contextRetriever) return ''; 200 | 201 | const context = contextRetriever(node); 202 | // TODO: Handle a possible invalid partial name 203 | const template = compileTemplate(partial, ['partials']); 204 | return template(context); 205 | } 206 | 207 | export function partialName(node: ASTNode): string { 208 | if (node instanceof VariableDeclaration) { 209 | if (node.typeString.startsWith('mapping')) { 210 | return 'mapping-state-variable'; 211 | } else if (node.typeString.includes('[]')) { 212 | return 'array-state-variable'; 213 | } else { 214 | return 'state-variable'; 215 | } 216 | } else if (node instanceof FunctionDefinition) { 217 | if (node.isConstructor) { 218 | return 'constructor'; 219 | } else if (node.kind === FunctionKind.Fallback || node.kind === FunctionKind.Receive) { 220 | return null; 221 | } else if (node.visibility === 'external' || node.visibility === 'public') { 222 | return 'external-or-public-function'; 223 | } else if (node.visibility === 'internal' && node.virtual) { 224 | return 'internal-function'; 225 | } 226 | } else if (node instanceof ImportDirective) { 227 | return 'import'; 228 | } 229 | 230 | // TODO: Handle unknown nodes 231 | } 232 | 233 | export async function getRemappings(rootPath: string): Promise { 234 | // First try the remappings.txt file 235 | try { 236 | return await exports.getRemappingsFromFile(path.join(rootPath, 'remappings.txt')); 237 | } catch (e) { 238 | // If the remappings file does not exist, try foundry.toml 239 | try { 240 | return await exports.getRemappingsFromConfig(path.join(rootPath, 'foundry.toml')); 241 | } catch { 242 | // If neither file exists, try to generate the remappings using forge 243 | try { 244 | return await exports.getRemappingsFromForge(); 245 | } catch { 246 | return []; 247 | } 248 | } 249 | } 250 | } 251 | 252 | export async function getRemappingsFromFile(remappingsPath: string): Promise { 253 | const remappingsContent = await fs.readFile(remappingsPath, 'utf8'); 254 | 255 | return remappingsContent 256 | .split('\n') 257 | .map((line) => line.trim()) 258 | .filter((line) => line.length) 259 | .map((line) => sanitizeRemapping(line)); 260 | } 261 | 262 | export async function getRemappingsFromConfig(foundryConfigPath: string): Promise { 263 | const foundryConfigContent = await fs.readFile(foundryConfigPath, 'utf8'); 264 | const regex = /remappings[\s|\n]*=[\s\n]*\[(?[^\]]+)]/; 265 | const matches = foundryConfigContent.match(regex); 266 | if (matches) { 267 | return matches 268 | .groups!.remappings.split(',') 269 | .map((line) => line.trim()) 270 | .map((line) => line.replace(/["']/g, '')) 271 | .filter((line) => line.length) 272 | .map((line) => sanitizeRemapping(line)); 273 | } else { 274 | throw new Error('No remappings found in foundry.toml'); 275 | } 276 | } 277 | 278 | /** 279 | * Returns the remappings generated by forge 280 | * @returns {Promise} - The list of remappings 281 | */ 282 | export async function getRemappingsFromForge(): Promise { 283 | const remappingsContent = await new Promise((resolve, reject) => 284 | exec('forge remappings', { encoding: 'utf8' }, (error, stdout) => (error ? reject(error) : resolve(stdout))), 285 | ); 286 | return remappingsContent 287 | .split('\n') 288 | .map((line) => line.trim()) 289 | .filter((line) => line.length) 290 | .map((line) => sanitizeRemapping(line)); 291 | } 292 | 293 | export function sanitizeRemapping(line: string): string { 294 | // Make sure the key and the value both either have or don't have a trailing slash 295 | const [key, value] = line.split('='); 296 | const slashNeeded = key.endsWith('/'); 297 | 298 | if (slashNeeded) { 299 | return value.endsWith('/') ? line : `${line}/`; 300 | } else { 301 | return value.endsWith('/') ? line.slice(0, -1) : line; 302 | } 303 | } 304 | 305 | export async function emptySmockDirectory(mocksDirectory: string) { 306 | // Create the directory if it doesn't exist 307 | try { 308 | await ensureDir(mocksDirectory); 309 | } catch (error) { 310 | console.error('Error while creating the mock directory: ', error); 311 | } 312 | 313 | // Empty the directory, if it exists 314 | try { 315 | await emptyDir(mocksDirectory); 316 | } catch (error) { 317 | console.error('Error while trying to empty the mock directory: ', error); 318 | } 319 | } 320 | 321 | export function getTestImport(remappings: string[]): string { 322 | const module = 'forge-std'; 323 | 324 | for (const remapping of remappings) { 325 | const [alias, path] = remapping.split('='); // Split remapping into alias and path 326 | 327 | if (alias.startsWith(module) && path.includes(module)) { 328 | const srcPath = path.includes('/src/') ? '' : `src/`; 329 | return `${alias}${srcPath}Test.sol`; 330 | } 331 | } 332 | 333 | return 'forge-std/src/Test.sol'; 334 | } 335 | 336 | export async function getSourceUnits( 337 | rootPath: string, 338 | contractsDirectories: string[], 339 | ignoreDirectories: string[], 340 | remappings: string[], 341 | ): Promise { 342 | const files: string[] = await getSolidityFilesAbsolutePaths(rootPath, contractsDirectories); 343 | const solidityFiles = files.filter((file) => !ignoreDirectories.some((directory) => file.includes(directory))); 344 | 345 | const compiledFiles = await compileSol(solidityFiles, 'auto', { 346 | basePath: rootPath, 347 | remapping: remappings, 348 | includePath: [rootPath], 349 | }); 350 | 351 | const sourceUnits = new ASTReader() 352 | .read(compiledFiles.data, ASTKind.Any, compiledFiles.files) 353 | // Skip source units that are not in the contracts directories 354 | .filter((sourceUnit) => contractsDirectories.some((directory) => sourceUnit.absolutePath.includes(directory))); 355 | 356 | return sourceUnits; 357 | } 358 | 359 | export function smockableNode(node: ASTNode): boolean { 360 | if (node instanceof VariableDeclaration) { 361 | // If the state variable is constant then we don't need to mock it 362 | if (node.constant || node.mutability === 'immutable') return false; 363 | // If the state variable is private we don't mock it 364 | if (node.visibility === 'private') return false; 365 | } else if (node instanceof FunctionDefinition) { 366 | if (node.isConstructor && (node.parent as ContractDefinition)?.abstract) return false; 367 | } else if (!(node instanceof FunctionDefinition)) { 368 | // Only process variables and functions 369 | return false; 370 | } 371 | 372 | return true; 373 | } 374 | 375 | /** 376 | * Renders the abstract functions that are not implemented in the current contract 377 | * @param contract The contract to render the abstract functions from 378 | * @returns The content of the functions 379 | */ 380 | export async function renderAbstractUnimplementedFunctions(contract: ContractDefinition): Promise { 381 | let content = ''; 382 | 383 | const currentSelectors = [...contract.vStateVariables, ...contract.vFunctions].map((node) => node.raw?.functionSelector); 384 | const inheritedSelectors = getAllInheritedSelectors(contract); 385 | 386 | // If the abstract contract has a constructor, we need to add it to the selectors 387 | if (contract.vConstructor) { 388 | const constructors = inheritedSelectors['constructor']; 389 | inheritedSelectors['constructor'] = { 390 | implemented: constructors.implemented, 391 | function: contract.vConstructor, 392 | contracts: constructors.contracts ? constructors.contracts.add(contract.name) : new Set([contract.name]), 393 | constructors: constructors.constructors ? [...constructors.constructors, contract.vConstructor] : [contract.vConstructor], 394 | }; 395 | } 396 | 397 | for (const selector in inheritedSelectors) { 398 | // Skip the functions that are already implemented in the current contract 399 | if (currentSelectors.includes(selector)) continue; 400 | 401 | // Skip the functions that are already implemented in the inherited contracts 402 | if (inheritedSelectors[selector].implemented) continue; 403 | 404 | const func = inheritedSelectors[selector].function; 405 | 406 | injectSelectors(func, inheritedSelectors); 407 | content += await renderNodeMock(func); 408 | } 409 | 410 | return content; 411 | } 412 | 413 | /** 414 | * Gets all the inherited selectors of a contract 415 | * @dev This function is recursive, loops through all the base contracts and their base contracts 416 | * @param contract The contract to get the inherited selectors from 417 | * @param selectors The map of selectors to update 418 | * @returns The updated map of selectors 419 | */ 420 | export const getAllInheritedSelectors = (contract: ContractDefinition, selectors: SelectorsMap = {}): SelectorsMap => { 421 | for (const base of contract.vLinearizedBaseContracts) { 422 | if (base.id === contract.id) continue; 423 | 424 | for (const variable of base.vStateVariables) { 425 | const selector = variable.raw?.functionSelector; 426 | 427 | if (!selector) continue; 428 | 429 | selectors[selector] = { 430 | implemented: true, 431 | }; 432 | } 433 | 434 | for (const func of base.vFunctions) { 435 | let selector = func.raw?.functionSelector; 436 | selector = func.isConstructor ? 'constructor' : selector; 437 | 438 | if (!selector) continue; 439 | 440 | const contracts = selectors[selector]?.contracts; 441 | const isImplemented = selectors[selector]?.implemented; 442 | const constructors = selectors[selector]?.constructors || []; 443 | 444 | selectors[selector] = { 445 | implemented: isImplemented || (!func.isConstructor && func.implemented), 446 | contracts: contracts ? contracts.add(base.name) : new Set([base.name]), 447 | function: func, 448 | constructors: func.isConstructor ? constructors.concat(func) : constructors, 449 | }; 450 | } 451 | 452 | getAllInheritedSelectors(base, selectors); 453 | } 454 | 455 | return selectors; 456 | }; 457 | 458 | /** 459 | * Injects the selectors into the function definition 460 | * @param node The node to inject the selectors into 461 | * @param selectors The map of selectors to inject 462 | */ 463 | export const injectSelectors = (node: ASTNode, selectors: SelectorsMap): void => { 464 | if (node instanceof FunctionDefinition) { 465 | const nodeWithSelectors = node as FullFunctionDefinition; 466 | nodeWithSelectors.selectors = selectors; 467 | nodeWithSelectors.visibility = FunctionVisibility.Public; 468 | } 469 | }; 470 | 471 | /** 472 | * Extracts the function overrides to render 473 | * @param node The node to extract the overrides from 474 | * @returns The overrides string or null if there are no overrides 475 | */ 476 | export const extractOverrides = (node: FullFunctionDefinition): string | null => { 477 | if (!node.selectors) return null; 478 | 479 | const selector = node.raw?.functionSelector; 480 | const contractsSet = node.selectors[selector]; 481 | 482 | if (!contractsSet || contractsSet.contracts.size <= 1) return null; 483 | 484 | return `(${Array.from(contractsSet.contracts).join(', ')})`; 485 | }; 486 | 487 | /** 488 | * Returns the fields of a struct 489 | * @param node The struct to extract the fields from 490 | * @returns The fields of the struct 491 | */ 492 | const getStructFields = (node: TypeName) => { 493 | const isStruct = node.typeString?.startsWith('struct'); 494 | if (!isStruct) return []; 495 | 496 | const isArray = node.typeString.includes('[]'); 497 | const structTypeName = (isArray ? (node as ArrayTypeName).vBaseType : node) as UserDefinedTypeName; 498 | if (!structTypeName) return []; 499 | 500 | const struct = structTypeName?.vReferencedDeclaration; 501 | if (!struct) return []; 502 | 503 | return struct.children || []; 504 | }; 505 | 506 | /** 507 | * Returns if there are nested mappings in a struct 508 | * @dev This function is recursive, loops through all the fields of the struct and nested structs 509 | * @param node The struct to extract the mappings from 510 | * @returns True if there are nested mappings, false otherwise 511 | */ 512 | export const hasNestedMappings = (node: TypeName): boolean => { 513 | let result = false; 514 | 515 | const fields = getStructFields(node); 516 | 517 | for (const member of fields) { 518 | const field = member as TypeName; 519 | if (!field.typeString) continue; 520 | 521 | if (field.typeString.startsWith('mapping')) return true; 522 | 523 | if (field.typeString.startsWith('struct')) { 524 | const fieldType = (field as VariableDeclaration).vType; 525 | const isArray = field.typeString.includes('[]'); 526 | 527 | const nestedStruct = isArray ? (fieldType as ArrayTypeName).vBaseType : fieldType; 528 | 529 | result = result || hasNestedMappings(nestedStruct); 530 | } 531 | } 532 | 533 | return result; 534 | }; 535 | 536 | /** 537 | * Extracts the fields of a struct 538 | * @dev returns the fields names of the struct as a string array 539 | * @param node The struct to extract the fields from 540 | * @returns The fields names of the struct 541 | */ 542 | export const extractStructFieldsNames = (node: TypeName): string[] | null => { 543 | const fields = getStructFields(node); 544 | 545 | return fields.map((field) => (field as VariableDeclaration).name).filter((name) => name); 546 | }; 547 | 548 | /** 549 | * Extracts the parameters of the constructors of a contract 550 | * @param node The function to extract the constructors parameters from 551 | * @returns The parameters and contracts of the constructors 552 | */ 553 | export const extractConstructorsParameters = ( 554 | node: FullFunctionDefinition, 555 | ): { 556 | parameters: string[]; 557 | contracts: string[]; 558 | } => { 559 | let constructors: FunctionDefinition[]; 560 | 561 | if (node?.selectors?.['constructor']?.constructors?.length > 1) { 562 | constructors = node.selectors['constructor'].constructors; 563 | } else { 564 | constructors = [node]; 565 | } 566 | 567 | const allParameters: string[] = []; 568 | const allContracts: string[] = []; 569 | 570 | for (const func of constructors) { 571 | const { functionParameters: parameters, parameterNames } = extractParameters(func.vParameters.vParameters); 572 | const contractName = (func.vScope as ContractDefinition).name; 573 | const contractValue = `${contractName}(${parameterNames.join(', ')})`; 574 | 575 | if (allContracts.includes(contractValue)) continue; 576 | 577 | allParameters.push(...parameters); 578 | allContracts.push(contractValue); 579 | } 580 | 581 | return { 582 | parameters: allParameters, 583 | contracts: allContracts, 584 | }; 585 | }; 586 | --------------------------------------------------------------------------------