Skip to content
This repository has been archived by the owner on Jul 9, 2021. It is now read-only.

Sol-cov artifact Adapters (truffle) #589

Merged
merged 41 commits into from
May 23, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
ae220c3
Add solcVersion to CompilerOpts
LogvinovLeon May 9, 2018
842f2ea
Fix a bug in FS resolver causing it to try reading directories
LogvinovLeon May 14, 2018
60b1fdd
Only look at *.sol files in NameResolver
LogvinovLeon May 14, 2018
974575b
Make sol-cov work with truffle and other artifact adapters
LogvinovLeon May 14, 2018
427a291
Support all opcodes in a trace parser
LogvinovLeon May 15, 2018
b86248f
Add CHANGELOG entries
LogvinovLeon May 15, 2018
1ff34bd
Remove web3Factory.create and remove dev-tools dependency on sol-cov
LogvinovLeon May 15, 2018
56d1b01
Introduce CONFIG_FILE
LogvinovLeon May 21, 2018
8267950
Assign then return
LogvinovLeon May 21, 2018
6aafda4
Assign then pass
LogvinovLeon May 21, 2018
86f17fb
Rename ZeroExArtifactAdapter to SolCompilerArtifactAdapter
LogvinovLeon May 21, 2018
ac925aa
Improve a CHANGELOG comment
LogvinovLeon May 21, 2018
334ef5c
Improve a CHANGELOG comment
LogvinovLeon May 21, 2018
08b08ef
Match class names with file names
LogvinovLeon May 21, 2018
d9907f2
Use a hidden directory for temp artifacts
LogvinovLeon May 22, 2018
5c9bde2
Remove a comment
LogvinovLeon May 22, 2018
e4fe497
Refactor ContractData lookup
LogvinovLeon May 22, 2018
253bada
Fix import paths
LogvinovLeon May 22, 2018
2f35e47
Change publish command name
LogvinovLeon May 22, 2018
f8c628b
Updated CHANGELOGS
LogvinovLeon May 22, 2018
fa4e694
Updated CHANGELOGS
LogvinovLeon May 22, 2018
84a1b56
Publish
LogvinovLeon May 22, 2018
ac52ad8
Use loglevel instead of verbose flag
LogvinovLeon May 22, 2018
83c37c6
Address feedback
LogvinovLeon May 22, 2018
0c53d27
Use BlockParamLiteral.Latest
LogvinovLeon May 22, 2018
447b305
Suppport subcalls in constructor
LogvinovLeon May 22, 2018
6540343
Remove trace.json
LogvinovLeon May 22, 2018
06be580
Fix a bug in CALL-like opcode handling
LogvinovLeon May 22, 2018
9740199
Fix sol-cov tests
LogvinovLeon May 22, 2018
127b3e7
Fix sol-compiler version
LogvinovLeon May 22, 2018
6e0aef5
Fix depth tracking in a tracer
LogvinovLeon May 22, 2018
f756003
Merge branch 'v2-prototype' into feature/truffle-sol-cov
LogvinovLeon May 22, 2018
8c7f090
Add a more verbose comment for self-destruct
LogvinovLeon May 22, 2018
d49f2c4
Parse compiler.json in SolCompilerArtifactsAdapter
LogvinovLeon May 22, 2018
ebc750d
Address feedback
LogvinovLeon May 23, 2018
48e6695
Fix NameResolver
LogvinovLeon May 23, 2018
0a72541
Merge branch 'v2-prototype' into feature/truffle-sol-cov
LogvinovLeon May 23, 2018
6a77e0f
Move contract utils
LogvinovLeon May 23, 2018
c9aef16
Fix linter issues
LogvinovLeon May 23, 2018
2ddd53b
Fix trace test
LogvinovLeon May 23, 2018
bf18a90
Upgrade solidity parser
LogvinovLeon May 23, 2018
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
2 changes: 1 addition & 1 deletion packages/dev-utils/CHANGELOG.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"version": "0.4.2",
"changes": [
{
"note": "Pass ZeroExArtifactsAdapter to CoverageSubprovider",
"note": "Pass SolCompilerArtifactAdapter to CoverageSubprovider",
"pr": 589
},
{
Expand Down
2 changes: 1 addition & 1 deletion packages/sol-cov/src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// tslint:disable:number-literal-format
export const constants = {
NEW_CONTRACT: 'NEW_CONTRACT',
NEW_CONTRACT: 'NEW_CONTRACT' as 'NEW_CONTRACT',
PUSH1: 0x60,
PUSH2: 0x61,
PUSH32: 0x7f,
Expand Down
4 changes: 3 additions & 1 deletion packages/sol-cov/src/coverage_manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,13 +125,15 @@ export class CoverageManager {
}
private static _getContractDataIfExists(contractsData: ContractData[], bytecode: string): ContractData | undefined {
if (!bytecode.startsWith('0x')) {
throw new Error('0x missing');
throw new Error(`0x hex prefix missing: ${bytecode}`);
}
const contractData = _.find(contractsData, contractDataCandidate => {
const bytecodeRegex = CoverageManager._bytecodeToBytecodeRegex(contractDataCandidate.bytecode);
const runtimeBytecodeRegex = CoverageManager._bytecodeToBytecodeRegex(
contractDataCandidate.runtimeBytecode,
);
// We use that function to find by bytecode or runtimeBytecode. Those are quasi-random strings so
// collisions are practically impossible and it allows us to reuse that code
return !_.isNull(bytecode.match(bytecodeRegex)) || !_.isNull(bytecode.match(runtimeBytecodeRegex));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So only one of either the bytecode or the runtimeBytecode needs to match? Can you leave a comment here about why that is?

});
return contractData;
Expand Down
24 changes: 13 additions & 11 deletions packages/sol-cov/src/coverage_subprovider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,13 @@ export class CoverageSubprovider extends Subprovider {
* Instantiates a CoverageSubprovider instance
* @param artifactAdapter Adapter for used artifacts format (0x, truffle, giveth, etc.)
* @param defaultFromAddress default from address to use when sending transactions
* @param verbose If true, we will log any unknown transactions. Otherwise we will ignore them
* @param isVerbose If true, we will log any unknown transactions. Otherwise we will ignore them
*/
constructor(artifactAdapter: AbstractArtifactAdapter, defaultFromAddress: string, verbose: boolean = true) {
constructor(artifactAdapter: AbstractArtifactAdapter, defaultFromAddress: string, isVerbose: boolean = true) {
super();
this._lock = new Lock();
this._defaultFromAddress = defaultFromAddress;
this._coverageManager = new CoverageManager(artifactAdapter, this._getContractCodeAsync.bind(this), verbose);
this._coverageManager = new CoverageManager(artifactAdapter, this._getContractCodeAsync.bind(this), isVerbose);
}
/**
* Write the test coverage results to a file in Istanbul format.
Expand Down Expand Up @@ -120,24 +120,26 @@ export class CoverageSubprovider extends Subprovider {
method: 'debug_traceTransaction',
params: [txHash, { disableMemory: true, disableStack: false, disableStorage: true }],
};
const jsonRPCResponsePayload = await this.emitPayloadAsync(payload);
let jsonRPCResponsePayload = await this.emitPayloadAsync(payload);
const trace: TransactionTrace = jsonRPCResponsePayload.result;
const tracesByContractAddress = getTracesByContractAddress(trace.structLogs, address);
const subcallAddresses = _.keys(tracesByContractAddress);
if (address === constants.NEW_CONTRACT) {
const tracesByContractAddress = getTracesByContractAddress(trace.structLogs, address);
for (const subcallAddress of _.keys(tracesByContractAddress)) {
for (const subcallAddress of subcallAddresses) {
let traceInfo: TraceInfoNewContract | TraceInfoExistingContract;
if (subcallAddress === 'NEW_CONTRACT') {
const traceForThatSubcall = tracesByContractAddress[subcallAddress];
const coveredPcs = _.map(traceForThatSubcall, log => log.pc);
traceInfo = {
coveredPcs,
txHash,
address: address as 'NEW_CONTRACT',
address: constants.NEW_CONTRACT,
bytecode: data as string,
};
} else {
payload = { method: 'eth_getCode', params: [subcallAddress, BlockParamLiteral.Latest] };
const runtimeBytecode = (await this.emitPayloadAsync(payload)).result;
jsonRPCResponsePayload = await this.emitPayloadAsync(payload);
const runtimeBytecode = jsonRPCResponsePayload.result;
const traceForThatSubcall = tracesByContractAddress[subcallAddress];
const coveredPcs = _.map(traceForThatSubcall, log => log.pc);
traceInfo = {
Expand All @@ -150,10 +152,10 @@ export class CoverageSubprovider extends Subprovider {
this._coverageManager.appendTraceInfo(traceInfo);
}
} else {
const tracesByContractAddress = getTracesByContractAddress(trace.structLogs, address);
for (const subcallAddress of _.keys(tracesByContractAddress)) {
for (const subcallAddress of subcallAddresses) {
payload = { method: 'eth_getCode', params: [subcallAddress, BlockParamLiteral.Latest] };
const runtimeBytecode = (await this.emitPayloadAsync(payload)).result;
jsonRPCResponsePayload = await this.emitPayloadAsync(payload);
const runtimeBytecode = jsonRPCResponsePayload.result;
const traceForThatSubcall = tracesByContractAddress[subcallAddress];
const coveredPcs = _.map(traceForThatSubcall, log => log.pc);
const traceInfo: TraceInfoExistingContract = {
Expand Down
34 changes: 21 additions & 13 deletions packages/sol-cov/src/trace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ export interface TraceByContractAddress {
[contractAddress: string]: StructLog[];
}

function getAddressFromStackEntry(stackEntry: string): string {
return addressUtils.padZeros(new BigNumber(addHexPrefix(stackEntry)).toString(16));
}

export function getTracesByContractAddress(structLogs: StructLog[], startAddress: string): TraceByContractAddress {
const traceByContractAddress: TraceByContractAddress = {};
let currentTraceSegment = [];
Expand All @@ -16,26 +20,32 @@ export function getTracesByContractAddress(structLogs: StructLog[], startAddress
for (let i = 0; i < structLogs.length; i++) {
const structLog = structLogs[i];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use for of here. We don't use the index.

if (structLog.depth !== callStack.length - 1) {
throw new Error("Malformed trace. trace depth doesn't match call stack depth");
throw new Error("Malformed trace. Trace depth doesn't match call stack depth");
}
// After that check we have a guarantee that call stack is never empty
// If it would: callStack.length - 1 === structLog.depth === -1
// That means that we can always safely pop from it
currentTraceSegment.push(structLog);

if (_.includes([OpCode.CallCode, OpCode.StaticCall, OpCode.Call, OpCode.DelegateCall], structLog.op)) {
const isCallLike = _.includes(
[OpCode.CallCode, OpCode.StaticCall, OpCode.Call, OpCode.DelegateCall],
structLog.op,
);
const isEndOpcode = _.includes(
[OpCode.Return, OpCode.Stop, OpCode.Revert, OpCode.Invalid, OpCode.SelfDestruct],
structLog.op,
);
if (isCallLike) {
const currentAddress = _.last(callStack) as string;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be nice to actually have a Stack class with .pop() method. Thoughts?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Array has a pop method. We're not poping here.

const jumpAddressOffset = 1;
const newAddress = addressUtils.padZeros(
new BigNumber(addHexPrefix(structLog.stack[structLog.stack.length - jumpAddressOffset - 1])).toString(
16,
),
const newAddress = getAddressFromStackEntry(
structLog.stack[structLog.stack.length - jumpAddressOffset - 1],
);
if (structLog === _.last(structLogs)) {
throw new Error('CALL-like opcode can not be the last one');
throw new Error('Malformed trace. CALL-like opcode can not be the last one');
}
// Sometimes calls don't change the execution context (current address). When we do a transfer to an
// externally owned account - it does the call and immidiately returns because there is no fallback
// externally owned account - it does the call and immediately returns because there is no fallback
// function. We manually check if the call depth had changed to handle that case.
const nextStructLog = structLogs[i + 1];
if (nextStructLog.depth !== structLog.depth) {
Expand All @@ -45,9 +55,7 @@ export function getTracesByContractAddress(structLogs: StructLog[], startAddress
);
currentTraceSegment = [];
}
} else if (
_.includes([OpCode.Return, OpCode.Stop, OpCode.Revert, OpCode.Invalid, OpCode.SelfDestruct], structLog.op)
) {
} else if (isEndOpcode) {
const currentAddress = callStack.pop() as string;
traceByContractAddress[currentAddress] = (traceByContractAddress[currentAddress] || []).concat(
currentTraceSegment,
Expand Down Expand Up @@ -81,7 +89,7 @@ export function getTracesByContractAddress(structLogs: StructLog[], startAddress
);
currentTraceSegment = [];
} else {
throw new Error('Shit broke');
throw new Error('Malformed trace. Unexpected call depth change');
}
}
}
Expand All @@ -90,7 +98,7 @@ export function getTracesByContractAddress(structLogs: StructLog[], startAddress
throw new Error('Malformed trace. Call stack non empty at the end');
}
if (currentTraceSegment.length !== 0) {
throw new Error('Malformed trace. currentTraceSegment non empty at the end');
throw new Error('Malformed trace. Current trace segment non empty at the end');
}
return traceByContractAddress;
}