Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow abstract contracts #7

Merged
merged 12 commits into from
Nov 2, 2023
2 changes: 1 addition & 1 deletion .mocharc.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
"require": ["ts-node/register"],
"extension": [".ts"],
"spec": "test/unit/**/*.spec.ts",
"timeout": "10000",
"timeout": "20000",
"exit": true
}
16 changes: 16 additions & 0 deletions solidity/contracts/utils/ContractAbstract.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
pragma solidity ^0.8.0;

abstract contract ContractAbstract {
uint256 public uintVariable;

constructor(uint256 _uintVariable) {
uintVariable = _uintVariable;
}

function setVariablesA(uint256 _newValue) public returns (bool _result) {
uintVariable = _newValue;
_result = true;
}

function undefinedFunc(string memory _someText) public virtual returns (bool _result);
}
2 changes: 2 additions & 0 deletions src/get-external-functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ export const getExternalMockFunctions = (contractNode: ContractDefinitionNode):
outputString: outputsString,
isInterface: contractKind === 'interface',
stateMutabilityString: stateMutabilityString,
abstractAndVirtual: contractNode.abstract && funcNode.virtual,
visibility: funcNode.visibility,
};

externalFunctions.push(externalMockFunction);
Expand Down
2 changes: 1 addition & 1 deletion src/mock-contract-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export const generateMockContracts = async (
) as ContractDefinitionNode;

// Skip unneeded contracts
if (!contractNode || contractNode.abstract || contractNode.contractKind === 'library') return;
if (!contractNode || contractNode.contractKind === 'library') return;

const functions: StateVariablesOptions = getStateVariables(contractNode);
// All data which will be use for create the template
Expand Down
5 changes: 5 additions & 0 deletions src/templates/mockExternalFunctionTemplate.hbs
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
{{#if isInterface}}
function {{functionName}}({{inputString}}) external{{stateMutabilityString}}returns ({{outputString}}) {}

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

{{/if}}
function mock_call_{{functionName}}({{arguments}}) public {
vm.mockCall(
Expand Down
6 changes: 4 additions & 2 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,17 +141,19 @@ export interface ExternalFunctionOptions {
outputString: string;
isInterface: boolean;
stateMutabilityString: string;
abstractAndVirtual: boolean;
visibility: string;
}

export interface InternalFunctionOptions {
functionName: string;
arguments: string;
signature: string;
inputsStringNames: string;
inputsString: string;
outputsStringNames: string;
outputsString: string;
outputsTypesString: string;
inputsString: string;
outputsString: string;
}

export const memoryTypes = ['string', 'bytes', 'mapping'];
Expand Down
1 change: 0 additions & 1 deletion src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,6 @@ export const registerHandlebarsTemplates = (): string => {
* @returns The names of the contracts in the given directory and its subdirectories
*/
export const getContractNamesAndFolders = (contractsDir: string[], ignoreDir: string[]): [string[], string[]] => {

const contractFileNames: string[] = [];
const contractFolders: string[] = [];
// Recursive function to traverse the directory and its subdirectories
Expand Down
33 changes: 32 additions & 1 deletion test/e2e/get-external-functions.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ describe('E2E: getExternalMockFunctions', () => {
const ignoreDir = [];
await generateMockContracts(contractsDir, compiledArtifactsDir, generatedContractsDir, ignoreDir);

const contractsNames = ['ContractTest', 'IContractTest'];
const contractsNames = ['ContractTest', 'IContractTest', 'ContractAbstract'];

contractsNames.forEach((contractName: string) => {
const mockName = `Mock${contractName}`;
Expand Down Expand Up @@ -126,4 +126,35 @@ describe('E2E: getExternalMockFunctions', () => {
expect(param2).to.not.be.undefined;
expect(param2?.typeDescriptions.typeString).to.equal('bool');
});

it('Should create mocks for abstract contracts', async () => {
let func: FunctionDefinitionNode;
const contractNode = contractNodes['MockContractAbstract'];

func = contractNode.nodes.find(
(node) => node.nodeType === 'FunctionDefinition' && node.name === 'mock_call_uintVariable',
) as FunctionDefinitionNode;
expect(func).to.not.be.undefined;

func = contractNode.nodes.find(
(node) => node.nodeType === 'FunctionDefinition' && node.name === 'mock_call_setVariablesA',
) as FunctionDefinitionNode;
expect(func).to.not.be.undefined;

func = contractNode.nodes.find(
(node) => node.nodeType === 'FunctionDefinition' && node.name === 'mock_call_undefinedFunc',
) as FunctionDefinitionNode;
expect(func).to.not.be.undefined;

func = contractNode.nodes.find((node) => node.nodeType === 'FunctionDefinition' && node.name === 'undefinedFunc') as FunctionDefinitionNode;
expect(func).to.not.be.undefined;

const param0 = func.parameters.parameters.find((param) => param.name === '_someText');
expect(param0).to.not.be.undefined;
expect(param0?.typeDescriptions.typeString).to.equal('string');

const param2 = func.returnParameters.parameters.find((param) => param.name === '_result');
expect(param2).to.not.be.undefined;
expect(param2?.typeDescriptions.typeString).to.equal('bool');
});
});
76 changes: 76 additions & 0 deletions test/unit/get-external-functions.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ describe('getExternalMockFunctions', () => {
outputString: '',
isInterface: false,
stateMutabilityString: ' view ',
abstractAndVirtual: false,
visibility: 'public',
},
];
expect(externalFunctions).to.be.an('array').that.is.not.empty;
Expand Down Expand Up @@ -187,6 +189,8 @@ describe('getExternalMockFunctions', () => {
outputString: '',
isInterface: false,
stateMutabilityString: ' ',
abstractAndVirtual: false,
visibility: 'public',
},
];
expect(externalFunctions).to.be.an('array').that.is.not.empty;
Expand Down Expand Up @@ -239,6 +243,8 @@ describe('getExternalMockFunctions', () => {
outputString: 'string memory _param, string calldata _param2',
isInterface: false,
stateMutabilityString: ' ',
abstractAndVirtual: false,
visibility: 'public',
},
];
expect(externalFunctions).to.be.an('array').that.is.not.empty;
Expand Down Expand Up @@ -299,6 +305,8 @@ describe('getExternalMockFunctions', () => {
outputString: 'IERC20 _param, MyStruct _param2, MyEnum _param3',
isInterface: false,
stateMutabilityString: ' ',
abstractAndVirtual: false,
visibility: 'public',
},
];
expect(externalFunctions).to.be.an('array').that.is.not.empty;
Expand Down Expand Up @@ -352,6 +360,8 @@ describe('getExternalMockFunctions', () => {
outputString: 'string memory _output',
isInterface: false,
stateMutabilityString: ' ',
abstractAndVirtual: false,
visibility: 'public',
},
];
expect(externalFunctions).to.be.an('array').that.is.not.empty;
Expand Down Expand Up @@ -414,6 +424,72 @@ describe('getExternalMockFunctions', () => {
outputString: 'string memory _output',
isInterface: true,
stateMutabilityString: ' ',
abstractAndVirtual: false,
visibility: 'public',
},
];
expect(externalFunctions).to.be.an('array').that.is.not.empty;
expect(externalFunctions).to.deep.equal(expectedData);
});

it('should rewrite abstract contracts virtual functions', async () => {
contractNode = {
nodeType: 'ContractDefinition',
canonicalName: 'MyContract',
nodes: [],
abstract: true,
contractKind: 'function',
name: 'MyContract',
};

contractNode.nodes = [
{
name: 'myFunction',
nodeType: 'FunctionDefinition',
kind: 'function',
parameters: {
parameters: [
{
name: '_param',
nodeType: 'VariableDeclaration',
typeDescriptions: {
typeString: 'string',
},
storageLocation: 'memory',
},
],
},
returnParameters: {
parameters: [
{
name: '_output',
nodeType: 'VariableDeclaration',
typeDescriptions: {
typeString: 'string',
},
storageLocation: 'memory',
},
],
},
virtual: true,
visibility: 'public',
stateMutability: 'nonpayable',
},
];
const externalFunctions = getExternalMockFunctions(contractNode);
const expectedData: ExternalFunctionOptions[] = [
{
functionName: 'myFunction',
arguments: 'string memory _param, string memory _output',
signature: 'myFunction(string)',
inputsStringNames: ', _param',
outputsStringNames: '_output',
inputString: 'string memory _param',
outputString: 'string memory _output',
isInterface: false,
stateMutabilityString: ' ',
abstractAndVirtual: true,
visibility: 'public',
},
];
expect(externalFunctions).to.be.an('array').that.is.not.empty;
Expand Down