Skip to content
This repository has been archived by the owner on Mar 17, 2024. It is now read-only.

Delegate contract loading/parsing to artifactor & make optional #227

Merged
merged 1 commit into from
Oct 30, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 44 additions & 15 deletions lib/artifactor.js
Original file line number Diff line number Diff line change
@@ -1,31 +1,58 @@
const SyncRequest = require("./syncRequest");
const { parseSoliditySources } = require("./utils");

/**
* Extracts assets the reporter consumes (abi, bytecode) from supported artifact formats
* @example
* ```
* const artifactor = new Artifactor(config);
* const contract = artifactor.require('Example');
*
* > {
* > abi: [etc...],
* > bytecode: "0x" + contract.evm.bytecode.object, // (solc key name)
* > deployedBytecode: "0x" + contract.evm.deployedBytecode.object // (solc key name)
* > }
* Supplies contract artifact data to the reporter in a format it can use.
*/
class Artifactor {
constructor(config) {
this.config = config;
this.sync = new SyncRequest(config.url);
this.networkId = !config.provider ? this.sync.getNetworkId() : null;
}

/**
* Returns an array of contract info objects in the format consumed by ./gasData.js.
* @return {Object[]}
* @example
* ```
* const artifactor = new Artifactor(config);
* const contracts = artifactor.getContracts();
* > [
* > {
* > name: "Example",
* > artifact: {
* > abi: [etc...],
* > bytecode: "0x" + contract.evm.bytecode.object, // (solc key name)
* > deployedBytecode: "0x" + contract.evm.deployedBytecode.object // (solc key name)
* > },
* > ...
* > ]
*/
getContracts() {
if (typeof this.config.getContracts === "function") {
return this.config.getContracts();
}

const contracts = [];

for (const name of parseSoliditySources(this.config)) {
let artifact;

try {
artifact = this._require(name);
} catch (e) {
return;
}

contracts.push({ name: name, artifact: artifact });
}
return contracts;
}

/**
* Selects artifact translation strategy
* @param {String} contractName
* @return {Object} egr artifact
*/
require(contractName) {
_require(contractName) {
// User defined
if (typeof this.config.artifactType === "function")
return this.config.artifactType(contractName);
Expand Down Expand Up @@ -85,6 +112,7 @@ class Artifactor {
}

/**
* [DEPRECATED]
* Buidler artifact translator. Solc info (metadata) is attached to config
* at the buidler plugin
* @param {String} contractName
Expand All @@ -102,6 +130,7 @@ class Artifactor {
}

/**
* [EXPERIMENTAL]
* 0x artifact translator. Untested stub.
* @param {String} contractName
* @return {Object} egr artifact
Expand Down
1 change: 1 addition & 0 deletions lib/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class Config {
this.showTimeSpent = options.showTimeSpent || false;
this.srcPath = options.src || "contracts";
this.artifactType = options.artifactType || "truffle-v5";
this.getContracts = options.getContracts || null;
this.noColors = options.noColors;
this.proxyResolver = options.proxyResolver || null;
this.metadata = options.metadata || null;
Expand Down
141 changes: 64 additions & 77 deletions lib/gasData.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ class GasData {
}

/**
* + Parses the .sol files in the config.srcPath directory to obtain contract names.
* + Gets abis & bytecode for those assets via Artifactor.
* + Compiles pre-test gas usage (e.g. from `truffle migrate`)
* + Sets up data structures to store deployments and methods gas usage
* + Called in the mocha `start` hook to guarantee it's run later than pre-test deployments
Expand All @@ -31,8 +29,6 @@ class GasData {
this.provider = config.provider;
const artifactor = new Artifactor(config);

const files = utils.listSolidityFiles(config.srcPath);

// Get the current blockLimit;
// TODO: This shouldn't be here - should be on the config object &
// fetched when the table is written or something.
Expand All @@ -43,80 +39,71 @@ class GasData {
this.blockLimit = utils.gas(block.gasLimit);
}

files.forEach(file => {
utils
.getContractNames(file)
.filter(name => !config.excludeContracts.includes(name))
.forEach(name => {
let contract;
try {
contract = artifactor.require(name);
} catch (error) {
return;
}

const contractInfo = {
name: name,
bytecode: contract.bytecode,
deployedBytecode: contract.deployedBytecode,
gasData: []
for (const contract of artifactor.getContracts()) {
const contractInfo = {
name: contract.name,
bytecode: contract.artifact.bytecode,
deployedBytecode: contract.artifact.deployedBytecode,
gasData: []
};
this.deployments.push(contractInfo);

// Report gas used during pre-test deployments (ex: truffle migrate)
if (
contract.artifact.deployed &&
contract.artifact.deployed.transactionHash &&
!this.provider
) {
const receipt = this.sync.getTransactionReceipt(
contract.artifact.deployed.transactionHash
);
if (receipt) {
// Sync: only runs for Truffle atm...
this.trackNameByAddress(
contract.name,
contract.artifact.deployed.address
);
contractInfo.gasData.push(utils.gas(receipt.gasUsed));
}
}

// Decode, getMethodIDs
const methodIDs = {};

let methods;
try {
methods = new ethersABI.Interface(contract.artifact.abi).functions;
} catch (err) {
utils.warnEthers(contract.name, err);
return;
}

// Generate sighashes and remap ethers to something similar
// to abiDecoder.getMethodIDs
Object.keys(methods).forEach(key => {
const raw = ejsUtil.keccak256(key);
const sighash = ejsUtil.bufferToHex(raw).slice(2, 10);
methodIDs[sighash] = Object.assign({ fnSig: key }, methods[key]);
});

// Create Method Map;
Object.keys(methodIDs).forEach(key => {
const isInterface = contract.artifact.bytecode === "0x";
const isCall = methodIDs[key].type === "call";
const methodHasName = methodIDs[key].name !== undefined;

if (methodHasName && !isCall && !isInterface) {
this.methods[contract.name + "_" + key] = {
key: key,
contract: contract.name,
method: methodIDs[key].name,
fnSig: methodIDs[key].fnSig,
gasData: [],
numberOfCalls: 0
};
this.deployments.push(contractInfo);

// Report gas used during pre-test deployments (ex: truffle migrate)
if (
contract.deployed &&
contract.deployed.transactionHash &&
!this.provider
) {
const receipt = this.sync.getTransactionReceipt(
contract.deployed.transactionHash
);
if (receipt) {
// Sync: only runs for Truffle atm...
this.trackNameByAddress(name, contract.deployed.address);
contractInfo.gasData.push(utils.gas(receipt.gasUsed));
}
}

// Decode, getMethodIDs
const methodIDs = {};

let methods;
try {
methods = new ethersABI.Interface(contract.abi).functions;
} catch (err) {
utils.warnEthers(name, err);
return;
}

// Generate sighashes and remap ethers to something similar
// to abiDecoder.getMethodIDs
Object.keys(methods).forEach(key => {
const raw = ejsUtil.keccak256(key);
const sighash = ejsUtil.bufferToHex(raw).slice(2, 10);
methodIDs[sighash] = Object.assign({ fnSig: key }, methods[key]);
});

// Create Method Map;
Object.keys(methodIDs).forEach(key => {
const isInterface = contract.bytecode === "0x";
const isCall = methodIDs[key].type === "call";
const methodHasName = methodIDs[key].name !== undefined;

if (methodHasName && !isCall && !isInterface) {
this.methods[name + "_" + key] = {
key: key,
contract: name,
method: methodIDs[key].name,
fnSig: methodIDs[key].fnSig,
gasData: [],
numberOfCalls: 0
};
}
});
});
});
}
});
}
}

/**
Expand Down
17 changes: 17 additions & 0 deletions lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,23 @@ const utils = {
return paths;
},

/**
* Loads and parses Solidity files, returning a filtered array of contract names.
* @return {string[]}
*/
parseSoliditySources(config) {
const names = [];
const files = utils.listSolidityFiles(config.srcPath);
files.forEach(file => {
const namesForFile = utils.getContractNames(file);
const filtered = namesForFile.filter(
name => !config.excludeContracts.includes(name)
);
filtered.forEach(item => names.push(item));
});
return names;
},

// Debugging helper
pretty: function(msg, obj) {
console.log(`<------ ${msg} ------>\n` + JSON.stringify(obj, null, " "));
Expand Down
2 changes: 1 addition & 1 deletion mock/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "eth-gas-reporter",
"version": "0.2.17",
"version": "0.2.18",
"description": "Mocha reporter which shows gas used per unit test.",
"main": "index.js",
"scripts": {
Expand Down