Skip to content

Commit

Permalink
Enable human readable abi output (#114)
Browse files Browse the repository at this point in the history
* Enable human readable abi output
  • Loading branch information
sz-piotr authored and marekkirejczyk committed Jul 16, 2019
1 parent 5adbb3a commit 999e1e2
Show file tree
Hide file tree
Showing 9 changed files with 451 additions and 5 deletions.
29 changes: 29 additions & 0 deletions docs/source/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -183,3 +183,32 @@ Add to waffle configuration in YourProjectDapp:
That should solve a problem.

Currently Waffle does not support similar feature for dockerized solc.

Human Readable Abi
------------------

Waffle supports `Human Readable Abi <https://blog.ricmoo.com/human-readable-contract-abis-in-ethers-js-141902f4d917>`.

In order to enable its output you need to specify a special flag in your config file:
::

{
...
outputHumanReadableAbi: true
}

You will now see the following in your output:
::

{
...
"humanReadableAbi": [
"constructor(uint256 argOne)",
"event Bar(bool argOne, uint256 indexed argTwo)",
"event FooEvent()",
"function noArgs() view returns(uint200)",
"function oneArg(bool argOne)",
"function threeArgs(string argOne, bool argTwo, uint256[] argThree) view returns(bool, uint256)",
"function twoReturns(bool argOne) view returns(bool, uint256)"
]
}
57 changes: 57 additions & 0 deletions lib/compiler/getHumanReadableAbi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
export function getHumanReadableAbi(abi: any[]) {
return abi
.filter((entry) => ['function', 'constructor', 'event'].includes(entry.type))
.map((entry) => {
switch (entry.type) {
case 'function': return encodeFunction(entry);
case 'constructor': return encodeConstructor(entry);
case 'event': return encodeEvent(entry);
default: throw new TypeError('Unrecognized entry type');
}
});
}

function encodeFunction(entry: any) {
let declaration = `function ${entry.name}(${encodeInputs(entry.inputs)})`;
if (entry.stateMutability !== 'nonpayable') {
declaration += ` ${entry.stateMutability}`;
}
return declaration + encodeOutputs(entry.outputs);
}

function encodeConstructor(entry: any) {
return `constructor(${encodeInputs(entry.inputs)})`;
}

function encodeEvent(entry: any) {
return `event ${entry.name}(${encodeInputs(entry.inputs)})`;
}

function encodeInputs(inputs: any[] | undefined) {
if (!inputs || inputs.length === 0) {
return '';
}
return inputs
.map((input) => {
if (input.indexed) {
return `${input.type} indexed ${input.name}`;
}
return `${input.type} ${input.name}`;
})
.join(', ');
}

function encodeOutputs(outputs: any[] | undefined) {
if (!outputs || outputs.length === 0) {
return '';
}
const returns = outputs
.map((output) => {
if (output.name !== '') {
return `${output.type} ${output.name}`;
}
return output.type;
})
.join(', ');
return ` returns(${returns})`;
}
16 changes: 11 additions & 5 deletions lib/compiler/saveOutput.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {join, dirname} from 'path';
import fs from 'fs';
import {Config} from '../config/config';
import { getHumanReadableAbi } from './getHumanReadableAbi';

export interface BytecodeJson {
linkReferences: object;
Expand All @@ -22,6 +23,7 @@ export interface ContractJson {
interface?: object[];
abi: object[];
bytecode?: string;
humanReadableAbi?: string[];
evm: EvmJson;
}

Expand Down Expand Up @@ -66,15 +68,16 @@ export async function saveOutputCombined(output: any, config: Config, filesystem
const allSources: string[] = [];

for (const [key, value] of Object.entries(output.sources) as any) {
value.AST = value.ast;
delete value.ast;
allSources.push(key);
value.AST = value.ast;
delete value.ast;
allSources.push(key);
}

output.sourceList = allSources;

filesystem.writeFileSync(join(config.targetPath, `Combined-Json.json`),
JSON.stringify(output, null, 2));
filesystem.writeFileSync(
join(config.targetPath, `Combined-Json.json`), JSON.stringify(output, null, 2)
);

}

Expand All @@ -83,5 +86,8 @@ function getContent(contractJson: ContractJson, config: Config) {
contractJson.interface = contractJson.abi;
contractJson.bytecode = contractJson.evm.bytecode.object;
}
if (config.outputHumanReadableAbi) {
contractJson.humanReadableAbi = getHumanReadableAbi(contractJson.abi);
}
return JSON.stringify(contractJson, null, 2);
}
1 change: 1 addition & 0 deletions lib/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export interface Config {
allowedPaths?: string[];
compilerOptions?: Record<string, any>;
outputType?: 'multiple' | 'combined' | 'all';
outputHumanReadableAbi?: boolean;
}

const defaultConfig: Config = {
Expand Down
47 changes: 47 additions & 0 deletions test/compiler/humanReadableAbi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import fsx from 'fs-extra';
import {join} from 'path';
import {expect} from 'chai';
import {compileProject} from '../../lib/compiler/compiler';
import {readFileContent} from '../../lib/utils';
import { utils } from 'ethers';

describe('E2E: humanReadableAbi', () => {
before(() => {
process.chdir('test/projects/humanReadableAbi');
});

beforeEach(() => {
fsx.removeSync('build');
});

it(`"outputHumanReadableAbi" makes output contain humanReadableAbi`, async () => {
await compileProject('config.json');

const filePath = join('./build', 'MyContract.json');
const { humanReadableAbi } = JSON.parse(readFileContent(filePath));

expect(humanReadableAbi.sort()).to.deep.equal([
'constructor(uint256 argOne)',
'event Bar(bool argOne, uint256 indexed argTwo)',
'event FooEvent()',
'function noArgs() view returns(uint200)',
'function oneArg(bool argOne)',
'function threeArgs(string argOne, bool argTwo, uint256[] argThree) view returns(bool, uint256)',
'function twoReturns(bool argOne) view returns(bool, uint256)'
]);
expect(() => new utils.Interface(humanReadableAbi)).not.to.throw();
});

it(`By default output does not contain humanReadableAbi`, async () => {
await compileProject('config2.json');

const filePath = join('./build', 'MyContract.json');
const { humanReadableAbi } = JSON.parse(readFileContent(filePath));

expect(humanReadableAbi).to.equal(undefined);
});

after(async () => {
process.chdir('../../..');
});
});
263 changes: 263 additions & 0 deletions test/projects/humanReadableAbi/build/MyContract.json

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions test/projects/humanReadableAbi/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"outputHumanReadableAbi": true
}
2 changes: 2 additions & 0 deletions test/projects/humanReadableAbi/config2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{
}
38 changes: 38 additions & 0 deletions test/projects/humanReadableAbi/contracts/MyContract.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
pragma solidity ^0.5.1;



contract MyContract {
event FooEvent ();
event Bar (bool argOne, uint indexed argTwo);

uint200 boo;
constructor (uint argOne) public {
boo = uint200(argOne);
}

function () external {
}

function noArgs() public view returns (uint200) {
return boo;
}

function oneArg(bool argOne) public {
boo = argOne ? 1 : 0;
}

function twoReturns(bool argOne) public view returns (bool, uint) {
return (argOne, boo);
}

function threeArgs(string memory argOne, bool argTwo, uint[] memory argThree) public view returns (bool, uint) {
argOne;
argTwo;
return (boo > 1, argThree[1]);
}

function bar() private returns (uint200) {
return boo++;
}
}

0 comments on commit 999e1e2

Please sign in to comment.