Skip to content

Commit

Permalink
chore: merge dev
Browse files Browse the repository at this point in the history
  • Loading branch information
0xJabberwock committed Jan 3, 2024
2 parents 6468d49 + a885213 commit 54f86ee
Show file tree
Hide file tree
Showing 22 changed files with 394 additions and 221 deletions.
14 changes: 14 additions & 0 deletions .github/issue_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Description
Describe the feature you'd like to see implemented or the bug you've encountered in as much details as possible.
If it's a bug, specify the version you're using and attach screenshots, logs, etc.

## Steps to reproduce
1. `git clone [reproduction-repo-url]`
2. `yarn smock-foundry --contracts solidity/contracts`
3. [and so on...]

## Expected behavior
What you expected to happen

## Actual behavior
What actually happened
3 changes: 3 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# 🤖 Linear

Closes BES-XXX
19 changes: 10 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ Option | Default | Notes
`mocks ` | `./solidity/test/smock` | The path to the generated mock contracts
`ignore` | [] | A list of directories to ignore, e.g. `--ignore libraries`

Be sure to `gitignore` the generated smock directory.

### Using mocks

Let's say you have a `Greeter` contract in your project at `contracts/Greeter.sol`:
Expand Down Expand Up @@ -83,14 +85,13 @@ contract BaseTest is Test, SmockHelper {
function setUp() public {
// The `deployMock` call is equivalent to
// greeter = new MockGreeter('Hello');
// label(address(greeter, 'Greeter');
// vm.allowCheatcodes(address(greeter);
greeter = deployMock(
'Greeter',
type(Greeter).creationCode,
abi.encode('Hello')
// address _greeterAddress = address(new MockGreeter('Hello'));
// vm.label(_greeterAddress, 'Greeter');
// vm.allowCheatcodes(_greeterAddress);
// return _greeterAddress;
greeter = MockGreeter(
deployMock('Greeter', type(Greeter).creationCode, abi.encode('Hello'))
);
}
}
Expand All @@ -109,7 +110,7 @@ greeter.set__greeting('Hola');
### Gotchas

- 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.
- Cannot `set` private variables and mock private functions.
- Cannot set `private` variables and mock `private` functions.
- Mocking of structs containing mappings is not supported.

# Licensing
Expand Down
1 change: 1 addition & 0 deletions solidity/contracts/ContractTest.sol
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {IContractTest, MyContract} from '../interfaces/IContractTest.sol';
Expand Down
1 change: 1 addition & 0 deletions solidity/contracts/Lib.sol
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

library Lib {
Expand Down
11 changes: 10 additions & 1 deletion solidity/contracts/utils/ContractAbstract.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,14 @@ abstract contract ContractAbstract {
}

function undefinedFunc(string memory _someText) public virtual returns (bool _result);
function undefinedFuncNoReturn(string memory _someText) public virtual;
function undefinedFuncNoInputNoOutput() public virtual;

function undefinedViewFunc(string memory _someText) public view virtual returns (bool _result);
function undefinedViewFuncNoInputNoOutput() public view virtual;

function _undefinedInternalFunc(string memory _someText) internal virtual returns (bool _result);
function _undefinedInternalFuncNoInputNoOutput() internal virtual;

function _undefinedInternalViewFunc(string memory _someText) internal view virtual returns (bool _result);
function _undefinedInternalViewFuncNoInputNoOutput() internal view virtual;
}
4 changes: 2 additions & 2 deletions solidity/contracts/utils/ContractD.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ contract ContractD {
return (true, 111, 'test');
}

function _internalNoInputNoOutput() internal virtual {
function _internalFuncNoInputNoOutput() internal virtual {
_internalUintVar = 11;
}

function _internalViewNoInputNoOutput() internal view virtual {
function _internalViewFuncNoInputNoOutput() internal view virtual {
uint256 __internalUintVar = _internalUintVar;
}
}
1 change: 1 addition & 0 deletions solidity/interfaces/IContractTest.sol
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract MyContract {}
Expand Down
52 changes: 22 additions & 30 deletions src/get-external-functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,6 @@ export const getExternalMockFunctions = (contractNode: ContractDefinitionNode):
// Filter the nodes and keep only the FunctionDefinition related ones
const functionNodes = contractNode.nodes.filter((node) => node.nodeType === 'FunctionDefinition') as FunctionDefinitionNode[];

// Get contract kind
const contractKind = contractNode.contractKind;

const externalFunctions: ExternalFunctionOptions[] = [];
// Loop through the function nodes
functionNodes.forEach((funcNode: FunctionDefinitionNode) => {
Expand All @@ -22,8 +19,7 @@ export const getExternalMockFunctions = (contractNode: ContractDefinitionNode):
if (funcNode.visibility != 'external' && funcNode.visibility != 'public') return;

// Save state mutability
const stateMutability = funcNode.stateMutability;
const stateMutabilityString = stateMutability == 'nonpayable' ? ' ' : ` ${stateMutability} `;
const stateMutability = funcNode.stateMutability === 'nonpayable' ? ' ' : ` ${funcNode.stateMutability} `;

// Get the parameters of the function, if there are no parameters then we use an empty array
const parameters: VariableDeclarationNode[] = funcNode.parameters.parameters ? funcNode.parameters.parameters : [];
Expand Down Expand Up @@ -57,61 +53,57 @@ export const getExternalMockFunctions = (contractNode: ContractDefinitionNode):

const signature = parameterTypes ? `${funcNode.name}(${parameterTypes.join(',')})` : `${funcNode.name}()`;

// Get the return parameters of the function, if there are no return parameters then we use an empty array
const returnParameters: VariableDeclarationNode[] = funcNode.returnParameters.parameters ? funcNode.returnParameters.parameters : [];

// We save the return parameters in an array with their types and storage location
const functionReturnParameters: string[] = [];
// We save the return parameters names in an other array
// We save the return parameters names in another array
const returnParameterNames: string[] = [];

parameterIndex = 0;
returnParameters.forEach((parameter: VariableDeclarationNode) => {
// We remove the 'contract ' string from the type name if it exists
const typeName: string = typeFix(parameter.typeDescriptions.typeString);
const returnName: string = parameter.name == '' ? `_return${parameterIndex}` : parameter.name;
const paramName: string = parameter.name == '' ? `_returnParam${parameterIndex}` : parameter.name;

// If the storage location is memory or calldata then we keep it
const storageLocation =
parameter.storageLocation === 'memory' || parameter.storageLocation === 'calldata' ? `${parameter.storageLocation} ` : '';

// We create the string that will be used in the constructor signature
const parameterString = `${typeName} ${storageLocation}${returnName}`;
const parameterString = `${typeName} ${storageLocation}${paramName}`;

functionReturnParameters.push(parameterString);
returnParameterNames.push(returnName);
returnParameterNames.push(paramName);
parameterIndex++;
});

// We create the string that will be used in the mock function signature
const inputsString: string = functionParameters.length ? functionParameters.join(', ') : '';
const outputsString: string = functionReturnParameters.length ? functionReturnParameters.join(', ') : '';

// We create the strings that will be used in the mock call arguments and returns
const inputsStringNames: string = parameterNames.length ? `, ${parameterNames.join(', ')}` : '';
const outputsStringNames: string = returnParameterNames.length ? returnParameterNames.join(', ') : '';
let args: string;

if (!inputsString) {
args = outputsString;
} else if (!outputsString) {
args = inputsString;
const inputs: string = functionParameters.length ? functionParameters.join(', ') : '';
const outputs: string = functionReturnParameters.length ? functionReturnParameters.join(', ') : '';

let params: string;
if (!inputs) {
params = outputs;
} else if (!outputs) {
params = inputs;
} else {
args = `${inputsString}, ${outputsString}`;
params = `${inputs}, ${outputs}`;
}

// Save the external function information
const externalMockFunction: ExternalFunctionOptions = {
functionName: funcNode.name,
arguments: args,
signature: signature,
inputsStringNames: inputsStringNames,
outputsStringNames: outputsStringNames,
inputString: inputsString,
outputString: outputsString,
isInterface: contractKind === 'interface',
stateMutabilityString: stateMutabilityString,
abstractAndVirtual: contractNode.abstract && funcNode.virtual,
parameters: params,
inputs: inputs,
outputs: outputs,
inputNames: parameterNames,
outputNames: returnParameterNames,
visibility: funcNode.visibility,
stateMutability: stateMutability,
implemented: funcNode.implemented,
};

externalFunctions.push(externalMockFunction);
Expand Down
2 changes: 2 additions & 0 deletions src/get-internal-functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,9 @@ export const getInternalMockFunctions = (contractNode: ContractDefinitionNode):
inputNames: parameterNames,
outputNames: returnParameterNames,
isView: isView,
implemented: funcNode.implemented,
};

internalFunctions.push(internalMockFunction);
});

Expand Down
8 changes: 6 additions & 2 deletions src/smock-foundry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,10 @@ export const generateMockContracts = async (
// Check if the abi and ast exist
if (!ast) return;

// Get the absolute path from the ast
// Get the license from the ast
const license: string = ast.license;

// Get the absolute path
const contractImport: string = ast.absolutePath;
if (!contractImport) return;

Expand All @@ -97,10 +100,11 @@ export const generateMockContracts = async (
if (!contractNode || contractNode.contractKind === 'library') return;

const functions: StateVariablesOptions = getStateVariables(contractNode);
// All data which will be use for create the template

// All data which will be used for creating the template
const data = {
contractName: contractName,
license: license,
contractImport: contractImport,
exportedSymbols: exportedSymbols.join(', '),
import: getImports(ast),
Expand Down
2 changes: 1 addition & 1 deletion src/templates/mockContractTemplate.hbs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/// SPDX-License-Identifier: UNLICENSED
// SPDX-License-Identifier: {{#if license}}{{license}}{{else}}UNLICENSED{{/if}}
pragma solidity ^0.8.0;

import {Test} from 'forge-std/Test.sol';
Expand Down
16 changes: 6 additions & 10 deletions src/templates/mockExternalFunctionTemplate.hbs
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
{{#if isInterface}}
function {{functionName}}({{inputString}}) external{{stateMutabilityString}}{{#if outputString}}returns ({{outputString}}){{/if}} {}
{{#unless implemented}}
function {{functionName}}({{inputs}}) {{visibility}}{{stateMutability}}override {{#if outputs}}returns ({{outputs}}){{/if}} {}

{{/if}}
{{#if abstractAndVirtual}}
function {{functionName}}({{inputString}}) {{visibility}}{{stateMutabilityString}}override {{#if outputString}}returns ({{outputString}}){{/if}} {}

{{/if}}
function mock_call_{{functionName}}({{arguments}}) public {
{{/unless}}
function mock_call_{{functionName}}({{parameters}}) public {
vm.mockCall(
address(this),
abi.encodeWithSignature('{{signature}}'{{inputsStringNames}}),
abi.encode({{outputsStringNames}})
abi.encodeWithSignature('{{signature}}'{{#if inputs}}, {{#each inputNames}}{{this}}{{#unless @last}}, {{/unless}}{{/each}}{{/if}}),
abi.encode({{#each outputNames}}{{this}}{{#unless @last}}, {{/unless}}{{/each}})
);
}
8 changes: 5 additions & 3 deletions src/templates/mockInternalFunctionTemplate.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,7 @@ function mock_call_{{functionName}}({{parameters}}) public {
{{functionName}}Outputs[_key] = {{functionName}}Output({{#each outputNames}}{{this}}{{#unless @last}}, {{/unless}}{{/each}});
{{/if}}
for (uint256 _i; _i < {{functionName}}InputHashes.length; ++_i) {
if (_key == {{functionName}}InputHashes[_i]) {
return;
}
if (_key == {{functionName}}InputHashes[_i]) return;
}
{{functionName}}InputHashes.push(_key);
{{else}}
Expand All @@ -43,10 +41,14 @@ function {{functionName}}({{inputs}}) internal {{#if isView}}view {{/if}}overrid
return ({{#each outputNames}}_output.{{this}}{{#unless @last}}, {{/unless}}{{/each}});
}
}
{{#if implemented}}
return super.{{functionName}}({{#each inputNames}}{{this}}{{#unless @last}}, {{/unless}}{{/each}});
{{/if}}
{{else}}
(bool _success, bytes memory _data) = address(this).call(abi.encodeWithSignature('{{signature}}'{{#if inputs}}, {{#each inputNames}}{{this}}{{#unless @last}}, {{/unless}}{{/each}}{{/if}}));
if (_success) return abi.decode(_data, ({{#each outputTypes}}{{this}}{{#unless @last}}, {{/unless}}{{/each}}));
{{#if implemented}}
else return super.{{functionName}}({{#each inputNames}}{{this}}{{#unless @last}}, {{/unless}}{{/each}});
{{/if}}
{{/if}}
}
25 changes: 13 additions & 12 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,27 +36,28 @@ export interface ContractDefinitionNode {
}

export interface FunctionDefinitionNode {
name: string;
nodeType: 'FunctionDefinition';
name: string;
kind: string;
parameters: {
parameters: VariableDeclarationNode[];
};
returnParameters: {
parameters: VariableDeclarationNode[];
};
virtual: boolean;
visibility: string;
stateMutability: string;
virtual: boolean;
implemented: boolean;
}

export interface Ast {
absolutePath: string;
nodeType: 'SourceUnit';
id?: number;
nodeType: string;
src: string;
nodes: AstNode[];
license: string;
absolutePath: string;
exportedSymbols: { [key: string]: number[] };
}

Expand Down Expand Up @@ -134,16 +135,15 @@ export interface StateVariablesOptions {

export interface ExternalFunctionOptions {
functionName: string;
arguments: string;
signature: string;
inputsStringNames: string;
outputsStringNames: string;
inputString: string;
outputString: string;
isInterface: boolean;
stateMutabilityString: string;
abstractAndVirtual: boolean;
parameters: string;
inputs: string;
outputs: string;
inputNames: string[];
outputNames: string[];
visibility: string;
stateMutability: string;
implemented: boolean;
}

export interface InternalFunctionOptions {
Expand All @@ -157,6 +157,7 @@ export interface InternalFunctionOptions {
inputNames: string[];
outputNames: string[];
isView: boolean;
implemented: boolean;
}

export const userDefinedTypes = ['contract', 'enum', 'struct'];
Expand Down
Loading

0 comments on commit 54f86ee

Please sign in to comment.