Merge pull request #589 from 0xProject/feature/truffle-sol-cov
Sol-cov artifact Adapters (truffle)
This commit is contained in:
@@ -15,7 +15,7 @@ const TIMEOUT = 10000;
|
||||
describe('Artifacts', () => {
|
||||
describe('contracts are deployed on kovan', () => {
|
||||
const kovanRpcUrl = constants.KOVAN_RPC_URL;
|
||||
const provider = web3Factory.create({ rpcUrl: kovanRpcUrl }).currentProvider;
|
||||
const provider = web3Factory.getRpcProvider({ rpcUrl: kovanRpcUrl });
|
||||
const config = {
|
||||
networkId: constants.KOVAN_NETWORK_ID,
|
||||
};
|
||||
@@ -32,7 +32,7 @@ describe('Artifacts', () => {
|
||||
});
|
||||
describe('contracts are deployed on ropsten', () => {
|
||||
const ropstenRpcUrl = constants.ROPSTEN_RPC_URL;
|
||||
const provider = web3Factory.create({ rpcUrl: ropstenRpcUrl }).currentProvider;
|
||||
const provider = web3Factory.getRpcProvider({ rpcUrl: ropstenRpcUrl });
|
||||
const config = {
|
||||
networkId: constants.ROPSTEN_NETWORK_ID,
|
||||
};
|
||||
|
@@ -2,8 +2,7 @@ import { devConstants, web3Factory } from '@0xproject/dev-utils';
|
||||
import { Provider } from '@0xproject/types';
|
||||
import { Web3Wrapper } from '@0xproject/web3-wrapper';
|
||||
|
||||
const web3 = web3Factory.create({ shouldUseInProcessGanache: true });
|
||||
const provider: Provider = web3.currentProvider;
|
||||
const web3Wrapper = new Web3Wrapper(web3.currentProvider);
|
||||
const provider: Provider = web3Factory.getRpcProvider({ shouldUseInProcessGanache: true });
|
||||
const web3Wrapper = new Web3Wrapper(provider);
|
||||
|
||||
export { provider, web3Wrapper };
|
||||
|
@@ -15,7 +15,7 @@ const TIMEOUT = 10000;
|
||||
describe('Artifacts', () => {
|
||||
describe('contracts are deployed on kovan', () => {
|
||||
const kovanRpcUrl = constants.KOVAN_RPC_URL;
|
||||
const provider = web3Factory.create({ rpcUrl: kovanRpcUrl }).currentProvider;
|
||||
const provider = web3Factory.getRpcProvider({ rpcUrl: kovanRpcUrl });
|
||||
const config = {
|
||||
networkId: constants.KOVAN_NETWORK_ID,
|
||||
};
|
||||
@@ -32,7 +32,7 @@ describe('Artifacts', () => {
|
||||
});
|
||||
describe('contracts are deployed on ropsten', () => {
|
||||
const ropstenRpcUrl = constants.ROPSTEN_RPC_URL;
|
||||
const provider = web3Factory.create({ rpcUrl: ropstenRpcUrl }).currentProvider;
|
||||
const provider = web3Factory.getRpcProvider({ rpcUrl: ropstenRpcUrl });
|
||||
const config = {
|
||||
networkId: constants.ROPSTEN_NETWORK_ID,
|
||||
};
|
||||
|
@@ -2,8 +2,7 @@ import { devConstants, web3Factory } from '@0xproject/dev-utils';
|
||||
import { Provider } from '@0xproject/types';
|
||||
import { Web3Wrapper } from '@0xproject/web3-wrapper';
|
||||
|
||||
const web3 = web3Factory.create({ shouldUseInProcessGanache: true });
|
||||
const provider: Provider = web3.currentProvider;
|
||||
const web3Wrapper = new Web3Wrapper(web3.currentProvider);
|
||||
const provider: Provider = web3Factory.getRpcProvider({ shouldUseInProcessGanache: true });
|
||||
const web3Wrapper = new Web3Wrapper(provider);
|
||||
|
||||
export { provider, web3Wrapper };
|
||||
|
@@ -44,6 +44,8 @@
|
||||
"@0xproject/abi-gen": "^0.3.0",
|
||||
"@0xproject/dev-utils": "^0.4.2",
|
||||
"@0xproject/tslint-config": "^0.4.18",
|
||||
"@0xproject/subproviders": "^0.10.1",
|
||||
"@0xproject/sol-cov": "^0.0.11",
|
||||
"@types/lodash": "4.14.104",
|
||||
"@types/node": "^8.0.53",
|
||||
"@types/yargs": "^10.0.0",
|
||||
|
21
packages/contracts/src/utils/coverage.ts
Normal file
21
packages/contracts/src/utils/coverage.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { devConstants } from '@0xproject/dev-utils';
|
||||
import { CoverageSubprovider, SolCompilerArtifactAdapter } from '@0xproject/sol-cov';
|
||||
import * as fs from 'fs';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
let coverageSubprovider: CoverageSubprovider;
|
||||
|
||||
export const coverage = {
|
||||
getCoverageSubproviderSingleton(): CoverageSubprovider {
|
||||
if (_.isUndefined(coverageSubprovider)) {
|
||||
coverageSubprovider = coverage._getCoverageSubprovider();
|
||||
}
|
||||
return coverageSubprovider;
|
||||
},
|
||||
_getCoverageSubprovider(): CoverageSubprovider {
|
||||
const defaultFromAddress = devConstants.TESTRPC_FIRST_ADDRESS;
|
||||
const solCompilerArtifactAdapter = new SolCompilerArtifactAdapter();
|
||||
const subprovider = new CoverageSubprovider(solCompilerArtifactAdapter, defaultFromAddress);
|
||||
return subprovider;
|
||||
},
|
||||
};
|
@@ -5,24 +5,24 @@ import * as chai from 'chai';
|
||||
import ethUtil = require('ethereumjs-util');
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { DummyERC20TokenContract } from '../../src/contract_wrappers/generated/dummy_e_r_c20_token';
|
||||
import { DummyERC721TokenContract } from '../../src/contract_wrappers/generated/dummy_e_r_c721_token';
|
||||
import { ERC20ProxyContract } from '../../src/contract_wrappers/generated/e_r_c20_proxy';
|
||||
import { ERC721ProxyContract } from '../../src/contract_wrappers/generated/e_r_c721_proxy';
|
||||
import { DummyERC20TokenContract } from '../contract_wrappers/generated/dummy_e_r_c20_token';
|
||||
import { DummyERC721TokenContract } from '../contract_wrappers/generated/dummy_e_r_c721_token';
|
||||
import { ERC20ProxyContract } from '../contract_wrappers/generated/e_r_c20_proxy';
|
||||
import { ERC721ProxyContract } from '../contract_wrappers/generated/e_r_c721_proxy';
|
||||
import {
|
||||
CancelContractEventArgs,
|
||||
ExchangeContract,
|
||||
FillContractEventArgs,
|
||||
} from '../../src/contract_wrappers/generated/exchange';
|
||||
import { assetProxyUtils } from '../../src/utils/asset_proxy_utils';
|
||||
import { chaiSetup } from '../../src/utils/chai_setup';
|
||||
import { constants } from '../../src/utils/constants';
|
||||
import { crypto } from '../../src/utils/crypto';
|
||||
import { ERC20Wrapper } from '../../src/utils/erc20_wrapper';
|
||||
import { ERC721Wrapper } from '../../src/utils/erc721_wrapper';
|
||||
import { ExchangeWrapper } from '../../src/utils/exchange_wrapper';
|
||||
import { OrderFactory } from '../../src/utils/order_factory';
|
||||
import { orderUtils } from '../../src/utils/order_utils';
|
||||
} from '../contract_wrappers/generated/exchange';
|
||||
import { assetProxyUtils } from '../utils/asset_proxy_utils';
|
||||
import { chaiSetup } from '../utils/chai_setup';
|
||||
import { constants } from '../utils/constants';
|
||||
import { crypto } from '../utils/crypto';
|
||||
import { ERC20Wrapper } from '../utils/erc20_wrapper';
|
||||
import { ERC721Wrapper } from '../utils/erc721_wrapper';
|
||||
import { ExchangeWrapper } from '../utils/exchange_wrapper';
|
||||
import { OrderFactory } from '../utils/order_factory';
|
||||
import { orderUtils } from '../utils/order_utils';
|
||||
import {
|
||||
AssetProxyId,
|
||||
ContractName,
|
||||
@@ -31,8 +31,8 @@ import {
|
||||
ExchangeStatus,
|
||||
SignedOrder,
|
||||
TransferAmountsByMatchOrders as TransferAmounts,
|
||||
} from '../../src/utils/types';
|
||||
import { provider, web3Wrapper } from '../../src/utils/web3_wrapper';
|
||||
} from '../utils/types';
|
||||
import { provider, web3Wrapper } from '../utils/web3_wrapper';
|
||||
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
@@ -1,12 +1,19 @@
|
||||
import { devConstants, web3Factory } from '@0xproject/dev-utils';
|
||||
import { devConstants, env, EnvVars, web3Factory } from '@0xproject/dev-utils';
|
||||
import { prependSubprovider } from '@0xproject/subproviders';
|
||||
import { Provider } from '@0xproject/types';
|
||||
import { Web3Wrapper } from '@0xproject/web3-wrapper';
|
||||
|
||||
import { coverage } from './coverage';
|
||||
|
||||
export const txDefaults = {
|
||||
from: devConstants.TESTRPC_FIRST_ADDRESS,
|
||||
gas: devConstants.GAS_ESTIMATE,
|
||||
};
|
||||
const providerConfigs = { shouldUseInProcessGanache: true };
|
||||
export const web3 = web3Factory.create(providerConfigs);
|
||||
export const provider = web3.currentProvider;
|
||||
export const provider = web3Factory.getRpcProvider(providerConfigs);
|
||||
const isCoverageEnabled = env.parseBoolean(EnvVars.SolidityCoverage);
|
||||
if (isCoverageEnabled) {
|
||||
const coverageSubprovider = coverage.getCoverageSubproviderSingleton();
|
||||
prependSubprovider(provider, coverageSubprovider);
|
||||
}
|
||||
export const web3Wrapper = new Web3Wrapper(provider);
|
||||
|
@@ -37,7 +37,7 @@ import {
|
||||
} from '../../src/utils/types';
|
||||
import { provider, txDefaults, web3Wrapper } from '../../src/utils/web3_wrapper';
|
||||
|
||||
import { MatchOrderTester } from '../utils/match_order_tester';
|
||||
import { MatchOrderTester } from '../../src/utils/match_order_tester';
|
||||
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
|
@@ -1,4 +1,6 @@
|
||||
import { coverage, env, EnvVars } from '@0xproject/dev-utils';
|
||||
import { env, EnvVars } from '@0xproject/dev-utils';
|
||||
|
||||
import { coverage } from '../src/utils/coverage';
|
||||
|
||||
after('generate coverage report', async () => {
|
||||
if (env.parseBoolean(EnvVars.SolidityCoverage)) {
|
||||
|
@@ -2,6 +2,10 @@
|
||||
{
|
||||
"version": "0.4.2",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Pass SolCompilerArtifactAdapter to CoverageSubprovider",
|
||||
"pr": 589
|
||||
},
|
||||
{
|
||||
"note": "Move callbackErrorReporter over from 0x.js",
|
||||
"pr": 579
|
||||
|
@@ -44,7 +44,6 @@
|
||||
"typescript": "2.7.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@0xproject/sol-cov": "^0.0.11",
|
||||
"@0xproject/subproviders": "^0.10.2",
|
||||
"@0xproject/types": "^0.7.0",
|
||||
"@0xproject/typescript-typings": "^0.3.2",
|
||||
|
@@ -1,21 +0,0 @@
|
||||
import { CoverageSubprovider } from '@0xproject/sol-cov';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { constants } from './constants';
|
||||
|
||||
let coverageSubprovider: CoverageSubprovider;
|
||||
|
||||
export const coverage = {
|
||||
getCoverageSubproviderSingleton(): CoverageSubprovider {
|
||||
if (_.isUndefined(coverageSubprovider)) {
|
||||
coverageSubprovider = coverage._getCoverageSubprovider();
|
||||
}
|
||||
return coverageSubprovider;
|
||||
},
|
||||
_getCoverageSubprovider(): CoverageSubprovider {
|
||||
const artifactsPath = '../migrations/artifacts/1.0.0';
|
||||
const contractsPath = 'src/contracts';
|
||||
const defaultFromAddress = constants.TESTRPC_FIRST_ADDRESS;
|
||||
return new CoverageSubprovider(artifactsPath, contractsPath, defaultFromAddress);
|
||||
},
|
||||
};
|
@@ -1,6 +1,5 @@
|
||||
export { BlockchainLifecycle } from './blockchain_lifecycle';
|
||||
export { web3Factory } from './web3_factory';
|
||||
export { constants as devConstants } from './constants';
|
||||
export { coverage } from './coverage';
|
||||
export { env, EnvVars } from './env';
|
||||
export { callbackErrorReporter } from './callback_error_reporter';
|
||||
|
@@ -13,16 +13,8 @@ import * as _ from 'lodash';
|
||||
import * as process from 'process';
|
||||
|
||||
import { constants } from './constants';
|
||||
import { coverage } from './coverage';
|
||||
import { env, EnvVars } from './env';
|
||||
|
||||
// HACK: web3 leaks XMLHttpRequest into the global scope and causes requests to hang
|
||||
// because they are using the wrong XHR package.
|
||||
// importing web3 after subproviders fixes this issue
|
||||
// Filed issue: https://github.com/ethereum/web3.js/issues/844
|
||||
// tslint:disable-next-line:ordered-imports
|
||||
import * as Web3 from 'web3';
|
||||
|
||||
export interface Web3Config {
|
||||
hasAddresses?: boolean; // default: true
|
||||
shouldUseInProcessGanache?: boolean; // default: false
|
||||
@@ -30,18 +22,8 @@ export interface Web3Config {
|
||||
}
|
||||
|
||||
export const web3Factory = {
|
||||
create(config: Web3Config = {}): Web3 {
|
||||
const provider = this.getRpcProvider(config);
|
||||
const web3 = new Web3();
|
||||
web3.setProvider(provider);
|
||||
return web3;
|
||||
},
|
||||
getRpcProvider(config: Web3Config = {}): Provider {
|
||||
getRpcProvider(config: Web3Config = {}): ProviderEngine {
|
||||
const provider = new ProviderEngine();
|
||||
const isCoverageEnabled = env.parseBoolean(EnvVars.SolidityCoverage);
|
||||
if (isCoverageEnabled) {
|
||||
provider.addProvider(coverage.getCoverageSubproviderSingleton());
|
||||
}
|
||||
const hasAddresses = _.isUndefined(config.hasAddresses) || config.hasAddresses;
|
||||
if (!hasAddresses) {
|
||||
provider.addProvider(new EmptyWalletSubprovider());
|
||||
|
@@ -3,8 +3,8 @@ import * as path from 'path';
|
||||
|
||||
export const config = {
|
||||
networkId: 50,
|
||||
artifactsDir: path.resolve(__dirname, '../../artifacts'),
|
||||
contractsDir: path.resolve(__dirname, '../../contracts'),
|
||||
artifactsDir: 'artifacts',
|
||||
contractsDir: 'contracts',
|
||||
ganacheLogFile: 'ganache.log',
|
||||
txDefaults: {
|
||||
from: devConstants.TESTRPC_FIRST_ADDRESS,
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { devConstants } from '@0xproject/dev-utils';
|
||||
import { CoverageSubprovider } from '@0xproject/sol-cov';
|
||||
import { CoverageSubprovider, SolCompilerArtifactAdapter } from '@0xproject/sol-cov';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { config } from './config';
|
||||
@@ -15,6 +15,7 @@ export const coverage = {
|
||||
},
|
||||
_getCoverageSubprovider(): CoverageSubprovider {
|
||||
const defaultFromAddress = devConstants.TESTRPC_FIRST_ADDRESS;
|
||||
return new CoverageSubprovider(config.artifactsDir, config.contractsDir, defaultFromAddress);
|
||||
const zeroExArtifactsAdapter = new SolCompilerArtifactAdapter(config.artifactsDir, config.contractsDir);
|
||||
return new CoverageSubprovider(zeroExArtifactsAdapter, defaultFromAddress);
|
||||
},
|
||||
};
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { env, EnvVars } from '@0xproject/dev-utils';
|
||||
import { GanacheSubprovider } from '@0xproject/subproviders';
|
||||
import { GanacheSubprovider, prependSubprovider } from '@0xproject/subproviders';
|
||||
import { Web3Wrapper } from '@0xproject/web3-wrapper';
|
||||
import * as fs from 'fs';
|
||||
import * as _ from 'lodash';
|
||||
@@ -9,10 +9,6 @@ import { config } from './config';
|
||||
import { coverage } from './coverage';
|
||||
|
||||
export const provider = new ProviderEngine();
|
||||
const isCoverageEnabled = env.parseBoolean(EnvVars.SolidityCoverage);
|
||||
if (isCoverageEnabled) {
|
||||
provider.addProvider(coverage.getCoverageSubproviderSingleton());
|
||||
}
|
||||
provider.addProvider(
|
||||
new GanacheSubprovider({
|
||||
logger: {
|
||||
@@ -27,4 +23,10 @@ provider.addProvider(
|
||||
);
|
||||
provider.start();
|
||||
|
||||
const isCoverageEnabled = env.parseBoolean(EnvVars.SolidityCoverage);
|
||||
if (isCoverageEnabled) {
|
||||
const coverageSubprovider = coverage.getCoverageSubproviderSingleton();
|
||||
prependSubprovider(provider, coverageSubprovider);
|
||||
}
|
||||
|
||||
export const web3Wrapper = new Web3Wrapper(provider);
|
||||
|
@@ -9,7 +9,7 @@
|
||||
"types": "lib/index.d.ts",
|
||||
"scripts": {
|
||||
"watch": "tsc -w",
|
||||
"prebuild": "run-s clean compile copy_artifacts generate_contract_wrappers",
|
||||
"prebuild": "run-s clean copy_artifacts generate_contract_wrappers",
|
||||
"copy_artifacts": "copyfiles 'artifacts/1.0.0/**/*' ./lib",
|
||||
"build": "tsc",
|
||||
"clean": "shx rm -rf lib src/contract_wrappers",
|
||||
|
@@ -11,8 +11,7 @@ import { runMigrationsAsync } from './migration';
|
||||
from: devConstants.TESTRPC_FIRST_ADDRESS,
|
||||
};
|
||||
const providerConfigs = { shouldUseInProcessGanache: false };
|
||||
const web3 = web3Factory.create(providerConfigs);
|
||||
const provider = web3.currentProvider;
|
||||
const provider: Provider = web3Factory.getRpcProvider(providerConfigs);
|
||||
const artifactsDir = 'artifacts/1.0.0';
|
||||
await runMigrationsAsync(provider, artifactsDir, txDefaults);
|
||||
process.exit(0);
|
||||
|
@@ -2,8 +2,7 @@ import { devConstants, web3Factory } from '@0xproject/dev-utils';
|
||||
import { Provider } from '@0xproject/types';
|
||||
import { Web3Wrapper } from '@0xproject/web3-wrapper';
|
||||
|
||||
const web3 = web3Factory.create({ shouldUseInProcessGanache: true });
|
||||
const provider: Provider = web3.currentProvider;
|
||||
const web3Wrapper = new Web3Wrapper(web3.currentProvider);
|
||||
const provider: Provider = web3Factory.getRpcProvider({ shouldUseInProcessGanache: true });
|
||||
const web3Wrapper = new Web3Wrapper(provider);
|
||||
|
||||
export { provider, web3Wrapper };
|
||||
|
@@ -2,8 +2,7 @@ import { devConstants, web3Factory } from '@0xproject/dev-utils';
|
||||
import { Provider } from '@0xproject/types';
|
||||
import { Web3Wrapper } from '@0xproject/web3-wrapper';
|
||||
|
||||
const web3 = web3Factory.create({ shouldUseInProcessGanache: true });
|
||||
const provider: Provider = web3.currentProvider;
|
||||
const web3Wrapper = new Web3Wrapper(web3.currentProvider);
|
||||
const provider: Provider = web3Factory.getRpcProvider({ shouldUseInProcessGanache: true });
|
||||
const web3Wrapper = new Web3Wrapper(provider);
|
||||
|
||||
export { provider, web3Wrapper };
|
||||
|
@@ -5,6 +5,10 @@
|
||||
{
|
||||
"note": "Properly export the executable binary",
|
||||
"pr": 588
|
||||
},
|
||||
{
|
||||
"note": "Add the ability to define a specific solidity version",
|
||||
"pr": 589
|
||||
}
|
||||
],
|
||||
"timestamp": 1527009133
|
||||
|
@@ -74,6 +74,7 @@ export class Compiler {
|
||||
private _contractsDir: string;
|
||||
private _compilerSettings: solc.CompilerSettings;
|
||||
private _artifactsDir: string;
|
||||
private _solcVersionIfExists: string | undefined;
|
||||
private _specifiedContracts: string[] | TYPE_ALL_FILES_IDENTIFIER;
|
||||
/**
|
||||
* Instantiates a new instance of the Compiler class.
|
||||
@@ -85,6 +86,7 @@ export class Compiler {
|
||||
? JSON.parse(fs.readFileSync(CONFIG_FILE).toString())
|
||||
: {};
|
||||
this._contractsDir = opts.contractsDir || config.contractsDir || DEFAULT_CONTRACTS_DIR;
|
||||
this._solcVersionIfExists = opts.solcVersion || config.solcVersion;
|
||||
this._compilerSettings = opts.compilerSettings || config.compilerSettings || DEFAULT_COMPILER_SETTINGS;
|
||||
this._artifactsDir = opts.artifactsDir || config.artifactsDir || DEFAULT_ARTIFACTS_DIR;
|
||||
this._specifiedContracts = opts.contracts || config.contracts || ALL_CONTRACTS_IDENTIFIER;
|
||||
@@ -139,9 +141,12 @@ export class Compiler {
|
||||
if (!shouldCompile) {
|
||||
return;
|
||||
}
|
||||
const solcVersionRange = parseSolidityVersionRange(contractSource.source);
|
||||
const availableCompilerVersions = _.keys(binPaths);
|
||||
const solcVersion = semver.maxSatisfying(availableCompilerVersions, solcVersionRange);
|
||||
let solcVersion = this._solcVersionIfExists;
|
||||
if (_.isUndefined(solcVersion)) {
|
||||
const solcVersionRange = parseSolidityVersionRange(contractSource.source);
|
||||
const availableCompilerVersions = _.keys(binPaths);
|
||||
solcVersion = semver.maxSatisfying(availableCompilerVersions, solcVersionRange);
|
||||
}
|
||||
const fullSolcVersion = binPaths[solcVersion];
|
||||
const compilerBinFilename = path.join(SOLC_BIN_DIR, fullSolcVersion);
|
||||
let solcjs: string;
|
||||
@@ -229,7 +234,7 @@ export class Compiler {
|
||||
sourceTreeHashHex,
|
||||
compiler: {
|
||||
name: 'solc',
|
||||
version: solcVersion,
|
||||
version: fullSolcVersion,
|
||||
settings: this._compilerSettings,
|
||||
},
|
||||
};
|
||||
|
@@ -1,2 +1,3 @@
|
||||
export { Compiler } from './compiler';
|
||||
export { CompilerOptions } from './utils/types';
|
||||
export { ContractArtifact, ContractNetworks } from './utils/types';
|
||||
|
@@ -55,6 +55,7 @@ export interface CompilerOptions {
|
||||
artifactsDir?: string;
|
||||
compilerSettings?: solc.CompilerSettings;
|
||||
contracts?: string[] | '*';
|
||||
solcVersion?: string;
|
||||
}
|
||||
|
||||
export interface ContractSourceData {
|
||||
|
@@ -3,7 +3,6 @@ import { Provider } from '@0xproject/types';
|
||||
import * as Web3 from 'web3';
|
||||
|
||||
const providerConfigs = { shouldUseInProcessGanache: true };
|
||||
const web3Instance = web3Factory.create(providerConfigs);
|
||||
const provider: Provider = web3Instance.currentProvider;
|
||||
const provider: Provider = web3Factory.getRpcProvider(providerConfigs);
|
||||
|
||||
export { provider };
|
||||
|
@@ -1,6 +1,28 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1527009134,
|
||||
"version": "0.1.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Add artifact adapter as a parameter for CoverageSubprovider. Export AbstractArtifactAdapter",
|
||||
"pr": 589
|
||||
},
|
||||
{
|
||||
"note": "Implement SolCompilerArtifactAdapter and TruffleArtifactAdapter",
|
||||
"pr": 589
|
||||
},
|
||||
{
|
||||
"note": "Properly parse multi-level traces",
|
||||
"pr": 589
|
||||
},
|
||||
{
|
||||
"note": "Add support for solidity libraries",
|
||||
"pr": 589
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1527009133,
|
||||
"version": "0.0.11",
|
||||
"changes": [
|
||||
{
|
||||
|
@@ -46,6 +46,7 @@
|
||||
},
|
||||
"homepage": "https://github.com/0xProject/0x.js/packages/sol-cov/README.md",
|
||||
"dependencies": {
|
||||
"@0xproject/sol-compiler": "^0.5.0",
|
||||
"@0xproject/subproviders": "^0.10.2",
|
||||
"@0xproject/types": "^0.7.0",
|
||||
"@0xproject/typescript-typings": "^0.3.2",
|
||||
@@ -54,17 +55,21 @@
|
||||
"glob": "^7.1.2",
|
||||
"istanbul": "^0.4.5",
|
||||
"lodash": "^4.17.4",
|
||||
"loglevel": "^1.6.1",
|
||||
"mkdirp": "^0.5.1",
|
||||
"rimraf": "^2.6.2",
|
||||
"semaphore-async-await": "^1.5.1",
|
||||
"solidity-parser-antlr": "^0.2.8"
|
||||
"solidity-parser-antlr": "^0.2.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@0xproject/monorepo-scripts": "^0.1.20",
|
||||
"@0xproject/tslint-config": "^0.4.18",
|
||||
"@types/istanbul": "^0.4.30",
|
||||
"@types/loglevel": "^1.5.3",
|
||||
"@types/mkdirp": "^0.5.1",
|
||||
"@types/mocha": "^2.2.42",
|
||||
"@types/node": "^8.0.53",
|
||||
"@types/rimraf": "^2.0.2",
|
||||
"chai": "^4.0.1",
|
||||
"copyfiles": "^1.2.0",
|
||||
"dirty-chai": "^2.0.1",
|
||||
|
@@ -0,0 +1,5 @@
|
||||
import { ContractData } from '../types';
|
||||
|
||||
export abstract class AbstractArtifactAdapter {
|
||||
public abstract async collectContractsDataAsync(): Promise<ContractData[]>;
|
||||
}
|
@@ -0,0 +1,49 @@
|
||||
import * as fs from 'fs';
|
||||
import * as glob from 'glob';
|
||||
import * as _ from 'lodash';
|
||||
import * as path from 'path';
|
||||
|
||||
import { ContractData } from '../types';
|
||||
|
||||
import { AbstractArtifactAdapter } from './abstract_artifact_adapter';
|
||||
|
||||
const CONFIG_FILE = 'compiler.json';
|
||||
|
||||
export class SolCompilerArtifactAdapter extends AbstractArtifactAdapter {
|
||||
private _artifactsPath: string;
|
||||
private _sourcesPath: string;
|
||||
constructor(artifactsPath?: string, sourcesPath?: string) {
|
||||
super();
|
||||
const config = JSON.parse(fs.readFileSync(CONFIG_FILE).toString());
|
||||
if (_.isUndefined(artifactsPath) && _.isUndefined(config.artifactsDir)) {
|
||||
throw new Error(`artifactsDir not found in ${CONFIG_FILE}`);
|
||||
}
|
||||
this._artifactsPath = config.artifactsDir;
|
||||
if (_.isUndefined(sourcesPath) && _.isUndefined(config.contractsDir)) {
|
||||
throw new Error(`contractsDir not found in ${CONFIG_FILE}`);
|
||||
}
|
||||
this._sourcesPath = config.contractsDir;
|
||||
}
|
||||
public async collectContractsDataAsync(): Promise<ContractData[]> {
|
||||
const artifactsGlob = `${this._artifactsPath}/**/*.json`;
|
||||
const artifactFileNames = glob.sync(artifactsGlob, { absolute: true });
|
||||
const contractsData: ContractData[] = [];
|
||||
for (const artifactFileName of artifactFileNames) {
|
||||
const artifact = JSON.parse(fs.readFileSync(artifactFileName).toString());
|
||||
let sources = _.keys(artifact.sources);
|
||||
sources = _.map(sources, relativeFilePath => path.resolve(this._sourcesPath, relativeFilePath));
|
||||
const contractName = artifact.contractName;
|
||||
const sourceCodes = _.map(sources, (source: string) => fs.readFileSync(source).toString());
|
||||
const contractData = {
|
||||
sourceCodes,
|
||||
sources,
|
||||
bytecode: artifact.compilerOutput.evm.bytecode.object,
|
||||
sourceMap: artifact.compilerOutput.evm.bytecode.sourceMap,
|
||||
runtimeBytecode: artifact.compilerOutput.evm.deployedBytecode.object,
|
||||
sourceMapRuntime: artifact.compilerOutput.evm.deployedBytecode.sourceMap,
|
||||
};
|
||||
contractsData.push(contractData);
|
||||
}
|
||||
return contractsData;
|
||||
}
|
||||
}
|
@@ -0,0 +1,43 @@
|
||||
import { Compiler, CompilerOptions } from '@0xproject/sol-compiler';
|
||||
import * as fs from 'fs';
|
||||
import * as glob from 'glob';
|
||||
import * as _ from 'lodash';
|
||||
import * as path from 'path';
|
||||
import * as rimraf from 'rimraf';
|
||||
|
||||
import { ContractData } from '../types';
|
||||
|
||||
import { AbstractArtifactAdapter } from './abstract_artifact_adapter';
|
||||
import { SolCompilerArtifactAdapter } from './sol_compiler_artifact_adapter';
|
||||
|
||||
export class TruffleArtifactAdapter extends AbstractArtifactAdapter {
|
||||
private _solcVersion: string;
|
||||
private _sourcesPath: string;
|
||||
constructor(sourcesPath: string, solcVersion: string) {
|
||||
super();
|
||||
this._solcVersion = solcVersion;
|
||||
this._sourcesPath = sourcesPath;
|
||||
}
|
||||
public async collectContractsDataAsync(): Promise<ContractData[]> {
|
||||
const artifactsDir = '.0x-artifacts';
|
||||
const compilerOptions: CompilerOptions = {
|
||||
contractsDir: this._sourcesPath,
|
||||
artifactsDir,
|
||||
compilerSettings: {
|
||||
outputSelection: {
|
||||
['*']: {
|
||||
['*']: ['abi', 'evm.bytecode.object', 'evm.deployedBytecode.object'],
|
||||
},
|
||||
},
|
||||
},
|
||||
contracts: '*',
|
||||
solcVersion: this._solcVersion,
|
||||
};
|
||||
const compiler = new Compiler(compilerOptions);
|
||||
await compiler.compileAsync();
|
||||
const solCompilerArtifactAdapter = new SolCompilerArtifactAdapter(artifactsDir, this._sourcesPath);
|
||||
const contractsDataFrom0xArtifacts = await solCompilerArtifactAdapter.collectContractsDataAsync();
|
||||
rimraf.sync(artifactsDir);
|
||||
return contractsDataFrom0xArtifacts;
|
||||
}
|
||||
}
|
@@ -1,31 +0,0 @@
|
||||
import * as fs from 'fs';
|
||||
import * as glob from 'glob';
|
||||
import * as _ from 'lodash';
|
||||
import * as path from 'path';
|
||||
|
||||
import { ContractData } from './types';
|
||||
|
||||
export const collectContractsData = (artifactsPath: string, sourcesPath: string) => {
|
||||
const artifactsGlob = `${artifactsPath}/**/*.json`;
|
||||
const artifactFileNames = glob.sync(artifactsGlob, { absolute: true });
|
||||
const contractsData: ContractData[] = [];
|
||||
_.forEach(artifactFileNames, artifactFileName => {
|
||||
const artifact = JSON.parse(fs.readFileSync(artifactFileName).toString());
|
||||
const sources = _.keys(artifact.sources);
|
||||
const contractName = artifact.contractName;
|
||||
// We don't compute coverage for dependencies
|
||||
const sourceCodes = _.map(sources, (source: string) =>
|
||||
fs.readFileSync(path.join(sourcesPath, source)).toString(),
|
||||
);
|
||||
const contractData = {
|
||||
sourceCodes,
|
||||
sources,
|
||||
bytecode: artifact.compilerOutput.evm.bytecode.object,
|
||||
sourceMap: artifact.compilerOutput.evm.bytecode.sourceMap,
|
||||
runtimeBytecode: artifact.compilerOutput.evm.deployedBytecode.object,
|
||||
sourceMapRuntime: artifact.compilerOutput.evm.deployedBytecode.sourceMap,
|
||||
};
|
||||
contractsData.push(contractData);
|
||||
});
|
||||
return contractsData;
|
||||
};
|
@@ -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,
|
||||
|
@@ -1,12 +1,13 @@
|
||||
import { promisify } from '@0xproject/utils';
|
||||
import { addHexPrefix } from 'ethereumjs-util';
|
||||
import { addHexPrefix, stripHexPrefix } from 'ethereumjs-util';
|
||||
import * as fs from 'fs';
|
||||
import { Collector } from 'istanbul';
|
||||
import * as _ from 'lodash';
|
||||
import { getLogger, levels, Logger, LogLevel } from 'loglevel';
|
||||
import * as mkdirp from 'mkdirp';
|
||||
import * as path from 'path';
|
||||
|
||||
import { collectContractsData } from './collect_contract_data';
|
||||
import { AbstractArtifactAdapter } from './artifact_adapters/abstract_artifact_adapter';
|
||||
import { collectCoverageEntries } from './collect_coverage_entries';
|
||||
import { constants } from './constants';
|
||||
import { parseSourceMap } from './source_maps';
|
||||
@@ -34,41 +35,23 @@ import { utils } from './utils';
|
||||
const mkdirpAsync = promisify<undefined>(mkdirp);
|
||||
|
||||
export class CoverageManager {
|
||||
private _sourcesPath: string;
|
||||
private _artifactAdapter: AbstractArtifactAdapter;
|
||||
private _logger: Logger;
|
||||
private _traceInfos: TraceInfo[] = [];
|
||||
private _contractsData: ContractData[] = [];
|
||||
private _getContractCodeAsync: (address: string) => Promise<string>;
|
||||
constructor(
|
||||
artifactsPath: string,
|
||||
sourcesPath: string,
|
||||
getContractCodeAsync: (address: string) => Promise<string>,
|
||||
) {
|
||||
this._getContractCodeAsync = getContractCodeAsync;
|
||||
this._sourcesPath = sourcesPath;
|
||||
this._contractsData = collectContractsData(artifactsPath, this._sourcesPath);
|
||||
}
|
||||
public appendTraceInfo(traceInfo: TraceInfo): void {
|
||||
this._traceInfos.push(traceInfo);
|
||||
}
|
||||
public async writeCoverageAsync(): Promise<void> {
|
||||
const finalCoverage = await this._computeCoverageAsync();
|
||||
const stringifiedCoverage = JSON.stringify(finalCoverage, null, '\t');
|
||||
await mkdirpAsync('coverage');
|
||||
fs.writeFileSync('coverage/coverage.json', stringifiedCoverage);
|
||||
}
|
||||
private _getSingleFileCoverageForTrace(
|
||||
private static _getSingleFileCoverageForTrace(
|
||||
contractData: ContractData,
|
||||
coveredPcs: number[],
|
||||
pcToSourceRange: { [programCounter: number]: SourceRange },
|
||||
fileIndex: number,
|
||||
): Coverage {
|
||||
const fileName = contractData.sources[fileIndex];
|
||||
const absoluteFileName = contractData.sources[fileIndex];
|
||||
const coverageEntriesDescription = collectCoverageEntries(contractData.sourceCodes[fileIndex]);
|
||||
let sourceRanges = _.map(coveredPcs, coveredPc => pcToSourceRange[coveredPc]);
|
||||
sourceRanges = _.compact(sourceRanges); // Some PC's don't map to a source range and we just ignore them.
|
||||
// By default lodash does a shallow object comparasion. We JSON.stringify them and compare as strings.
|
||||
sourceRanges = _.uniqBy(sourceRanges, s => JSON.stringify(s)); // We don't care if one PC was covered multiple times within a single transaction
|
||||
sourceRanges = _.filter(sourceRanges, sourceRange => sourceRange.fileName === fileName);
|
||||
sourceRanges = _.filter(sourceRanges, sourceRange => sourceRange.fileName === absoluteFileName);
|
||||
const branchCoverage: BranchCoverage = {};
|
||||
const branchIds = _.keys(coverageEntriesDescription.branchMap);
|
||||
for (const branchId of branchIds) {
|
||||
@@ -118,7 +101,6 @@ export class CoverageManager {
|
||||
);
|
||||
statementCoverage[modifierStatementId] = isModifierCovered;
|
||||
}
|
||||
const absoluteFileName = path.join(this._sourcesPath, fileName);
|
||||
const partialCoverage = {
|
||||
[absoluteFileName]: {
|
||||
...coverageEntriesDescription,
|
||||
@@ -131,18 +113,63 @@ export class CoverageManager {
|
||||
};
|
||||
return partialCoverage;
|
||||
}
|
||||
private static _bytecodeToBytecodeRegex(bytecode: string): string {
|
||||
const bytecodeRegex = bytecode
|
||||
// Library linking placeholder: __ConvertLib____________________________
|
||||
.replace(/_.*_/, '.*')
|
||||
// Last 86 characters is solidity compiler metadata that's different between compilations
|
||||
.replace(/.{86}$/, '')
|
||||
// Libraries contain their own address at the beginning of the code and it's impossible to know it in advance
|
||||
.replace(/^0x730000000000000000000000000000000000000000/, '0x73........................................');
|
||||
return bytecodeRegex;
|
||||
}
|
||||
private static _getContractDataIfExists(contractsData: ContractData[], bytecode: string): ContractData | undefined {
|
||||
if (!bytecode.startsWith('0x')) {
|
||||
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));
|
||||
});
|
||||
return contractData;
|
||||
}
|
||||
constructor(
|
||||
artifactAdapter: AbstractArtifactAdapter,
|
||||
getContractCodeAsync: (address: string) => Promise<string>,
|
||||
isVerbose: boolean,
|
||||
) {
|
||||
this._getContractCodeAsync = getContractCodeAsync;
|
||||
this._artifactAdapter = artifactAdapter;
|
||||
this._logger = getLogger('sol-cov');
|
||||
this._logger.setLevel(isVerbose ? levels.TRACE : levels.ERROR);
|
||||
}
|
||||
public appendTraceInfo(traceInfo: TraceInfo): void {
|
||||
this._traceInfos.push(traceInfo);
|
||||
}
|
||||
public async writeCoverageAsync(): Promise<void> {
|
||||
const finalCoverage = await this._computeCoverageAsync();
|
||||
const stringifiedCoverage = JSON.stringify(finalCoverage, null, '\t');
|
||||
await mkdirpAsync('coverage');
|
||||
fs.writeFileSync('coverage/coverage.json', stringifiedCoverage);
|
||||
}
|
||||
private async _computeCoverageAsync(): Promise<Coverage> {
|
||||
const contractsData = await this._artifactAdapter.collectContractsDataAsync();
|
||||
const collector = new Collector();
|
||||
for (const traceInfo of this._traceInfos) {
|
||||
if (traceInfo.address !== constants.NEW_CONTRACT) {
|
||||
// Runtime transaction
|
||||
let runtimeBytecode = (traceInfo as TraceInfoExistingContract).runtimeBytecode;
|
||||
runtimeBytecode = addHexPrefix(runtimeBytecode);
|
||||
const contractData = _.find(this._contractsData, { runtimeBytecode }) as ContractData;
|
||||
const runtimeBytecode = (traceInfo as TraceInfoExistingContract).runtimeBytecode;
|
||||
const contractData = CoverageManager._getContractDataIfExists(contractsData, runtimeBytecode);
|
||||
if (_.isUndefined(contractData)) {
|
||||
throw new Error(`Transaction to an unknown address: ${traceInfo.address}`);
|
||||
this._logger.warn(`Transaction to an unknown address: ${traceInfo.address}`);
|
||||
continue;
|
||||
}
|
||||
const bytecodeHex = contractData.runtimeBytecode.slice(2);
|
||||
const bytecodeHex = stripHexPrefix(runtimeBytecode);
|
||||
const sourceMap = contractData.sourceMapRuntime;
|
||||
const pcToSourceRange = parseSourceMap(
|
||||
contractData.sourceCodes,
|
||||
@@ -151,7 +178,7 @@ export class CoverageManager {
|
||||
contractData.sources,
|
||||
);
|
||||
for (let fileIndex = 0; fileIndex < contractData.sources.length; fileIndex++) {
|
||||
const singleFileCoverageForTrace = this._getSingleFileCoverageForTrace(
|
||||
const singleFileCoverageForTrace = CoverageManager._getSingleFileCoverageForTrace(
|
||||
contractData,
|
||||
traceInfo.coveredPcs,
|
||||
pcToSourceRange,
|
||||
@@ -161,15 +188,13 @@ export class CoverageManager {
|
||||
}
|
||||
} else {
|
||||
// Contract creation transaction
|
||||
let bytecode = (traceInfo as TraceInfoNewContract).bytecode;
|
||||
bytecode = addHexPrefix(bytecode);
|
||||
const contractData = _.find(this._contractsData, contractDataCandidate =>
|
||||
bytecode.startsWith(contractDataCandidate.bytecode),
|
||||
) as ContractData;
|
||||
const bytecode = (traceInfo as TraceInfoNewContract).bytecode;
|
||||
const contractData = CoverageManager._getContractDataIfExists(contractsData, bytecode);
|
||||
if (_.isUndefined(contractData)) {
|
||||
throw new Error(`Unknown contract creation transaction`);
|
||||
this._logger.warn(`Unknown contract creation transaction`);
|
||||
continue;
|
||||
}
|
||||
const bytecodeHex = contractData.bytecode.slice(2);
|
||||
const bytecodeHex = stripHexPrefix(bytecode);
|
||||
const sourceMap = contractData.sourceMap;
|
||||
const pcToSourceRange = parseSourceMap(
|
||||
contractData.sourceCodes,
|
||||
@@ -178,7 +203,7 @@ export class CoverageManager {
|
||||
contractData.sources,
|
||||
);
|
||||
for (let fileIndex = 0; fileIndex < contractData.sources.length; fileIndex++) {
|
||||
const singleFileCoverageForTrace = this._getSingleFileCoverageForTrace(
|
||||
const singleFileCoverageForTrace = CoverageManager._getSingleFileCoverageForTrace(
|
||||
contractData,
|
||||
traceInfo.coveredPcs,
|
||||
pcToSourceRange,
|
||||
|
@@ -1,11 +1,14 @@
|
||||
import { Callback, ErrorCallback, NextCallback, Subprovider } from '@0xproject/subproviders';
|
||||
import { BlockParam, CallData, JSONRPCRequestPayload, TransactionTrace, TxData } from '@0xproject/types';
|
||||
import * as fs from 'fs';
|
||||
import * as _ from 'lodash';
|
||||
import { Lock } from 'semaphore-async-await';
|
||||
|
||||
import { AbstractArtifactAdapter } from './artifact_adapters/abstract_artifact_adapter';
|
||||
import { constants } from './constants';
|
||||
import { CoverageManager } from './coverage_manager';
|
||||
import { TraceInfoExistingContract, TraceInfoNewContract } from './types';
|
||||
import { getTracesByContractAddress } from './trace';
|
||||
import { BlockParamLiteral, TraceInfoExistingContract, TraceInfoNewContract } from './types';
|
||||
|
||||
interface MaybeFakeTxData extends TxData {
|
||||
isFakeTransaction?: boolean;
|
||||
@@ -26,15 +29,15 @@ export class CoverageSubprovider extends Subprovider {
|
||||
private _defaultFromAddress: string;
|
||||
/**
|
||||
* Instantiates a CoverageSubprovider instance
|
||||
* @param artifactsPath Path to the smart contract artifacts
|
||||
* @param sourcesPath Path to the smart contract source files
|
||||
* @param artifactAdapter Adapter for used artifacts format (0x, truffle, giveth, etc.)
|
||||
* @param defaultFromAddress default from address to use when sending transactions
|
||||
* @param isVerbose If true, we will log any unknown transactions. Otherwise we will ignore them
|
||||
*/
|
||||
constructor(artifactsPath: string, sourcesPath: string, defaultFromAddress: string) {
|
||||
constructor(artifactAdapter: AbstractArtifactAdapter, defaultFromAddress: string, isVerbose: boolean = true) {
|
||||
super();
|
||||
this._lock = new Lock();
|
||||
this._defaultFromAddress = defaultFromAddress;
|
||||
this._coverageManager = new CoverageManager(artifactsPath, sourcesPath, this._getContractCodeAsync.bind(this));
|
||||
this._coverageManager = new CoverageManager(artifactAdapter, this._getContractCodeAsync.bind(this), isVerbose);
|
||||
}
|
||||
/**
|
||||
* Write the test coverage results to a file in Istanbul format.
|
||||
@@ -86,7 +89,7 @@ export class CoverageSubprovider extends Subprovider {
|
||||
} else {
|
||||
const payload = {
|
||||
method: 'eth_getBlockByNumber',
|
||||
params: ['latest', true],
|
||||
params: [BlockParamLiteral.Latest, true],
|
||||
};
|
||||
const jsonRPCResponsePayload = await this.emitPayloadAsync(payload);
|
||||
const transactions = jsonRPCResponsePayload.result.transactions;
|
||||
@@ -115,29 +118,54 @@ export class CoverageSubprovider extends Subprovider {
|
||||
private async _recordTxTraceAsync(address: string, data: string | undefined, txHash: string): Promise<void> {
|
||||
let payload = {
|
||||
method: 'debug_traceTransaction',
|
||||
params: [txHash, { disableMemory: true, disableStack: true, disableStorage: true }], // TODO For now testrpc just ignores those parameters https://github.com/trufflesuite/ganache-cli/issues/489
|
||||
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 coveredPcs = _.map(trace.structLogs, log => log.pc);
|
||||
const tracesByContractAddress = getTracesByContractAddress(trace.structLogs, address);
|
||||
const subcallAddresses = _.keys(tracesByContractAddress);
|
||||
if (address === constants.NEW_CONTRACT) {
|
||||
const traceInfo: TraceInfoNewContract = {
|
||||
coveredPcs,
|
||||
txHash,
|
||||
address: address as 'NEW_CONTRACT',
|
||||
bytecode: data as string,
|
||||
};
|
||||
this._coverageManager.appendTraceInfo(traceInfo);
|
||||
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: constants.NEW_CONTRACT,
|
||||
bytecode: data as string,
|
||||
};
|
||||
} else {
|
||||
payload = { method: 'eth_getCode', params: [subcallAddress, BlockParamLiteral.Latest] };
|
||||
jsonRPCResponsePayload = await this.emitPayloadAsync(payload);
|
||||
const runtimeBytecode = jsonRPCResponsePayload.result;
|
||||
const traceForThatSubcall = tracesByContractAddress[subcallAddress];
|
||||
const coveredPcs = _.map(traceForThatSubcall, log => log.pc);
|
||||
traceInfo = {
|
||||
coveredPcs,
|
||||
txHash,
|
||||
address: subcallAddress,
|
||||
runtimeBytecode,
|
||||
};
|
||||
}
|
||||
this._coverageManager.appendTraceInfo(traceInfo);
|
||||
}
|
||||
} else {
|
||||
payload = { method: 'eth_getCode', params: [address, 'latest'] };
|
||||
const runtimeBytecode = (await this.emitPayloadAsync(payload)).result;
|
||||
const traceInfo: TraceInfoExistingContract = {
|
||||
coveredPcs,
|
||||
txHash,
|
||||
address,
|
||||
runtimeBytecode,
|
||||
};
|
||||
this._coverageManager.appendTraceInfo(traceInfo);
|
||||
for (const subcallAddress of subcallAddresses) {
|
||||
payload = { method: 'eth_getCode', params: [subcallAddress, BlockParamLiteral.Latest] };
|
||||
jsonRPCResponsePayload = await this.emitPayloadAsync(payload);
|
||||
const runtimeBytecode = jsonRPCResponsePayload.result;
|
||||
const traceForThatSubcall = tracesByContractAddress[subcallAddress];
|
||||
const coveredPcs = _.map(traceForThatSubcall, log => log.pc);
|
||||
const traceInfo: TraceInfoExistingContract = {
|
||||
coveredPcs,
|
||||
txHash,
|
||||
address: subcallAddress,
|
||||
runtimeBytecode,
|
||||
};
|
||||
this._coverageManager.appendTraceInfo(traceInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
private async _recordCallTraceAsync(callData: Partial<CallData>, blockNumber: BlockParam): Promise<void> {
|
||||
@@ -168,7 +196,7 @@ export class CoverageSubprovider extends Subprovider {
|
||||
private async _getContractCodeAsync(address: string): Promise<string> {
|
||||
const payload = {
|
||||
method: 'eth_getCode',
|
||||
params: [address, 'latest'],
|
||||
params: [address, BlockParamLiteral.Latest],
|
||||
};
|
||||
const jsonRPCResponsePayload = await this.emitPayloadAsync(payload);
|
||||
const contractCode: string = jsonRPCResponsePayload.result;
|
||||
|
@@ -1 +1,5 @@
|
||||
export { CoverageSubprovider } from './coverage_subprovider';
|
||||
export { SolCompilerArtifactAdapter } from './artifact_adapters/sol_compiler_artifact_adapter';
|
||||
export { TruffleArtifactAdapter } from './artifact_adapters/truffle_artifact_adapter';
|
||||
export { AbstractArtifactAdapter } from './artifact_adapters/abstract_artifact_adapter';
|
||||
export { ContractData } from './types';
|
||||
|
105
packages/sol-cov/src/trace.ts
Normal file
105
packages/sol-cov/src/trace.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
import { OpCode, StructLog, TransactionTrace } from '@0xproject/types';
|
||||
import { addressUtils, BigNumber, logUtils } from '@0xproject/utils';
|
||||
import { addHexPrefix, stripHexPrefix } from 'ethereumjs-util';
|
||||
import * as fs from 'fs';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
export interface TraceByContractAddress {
|
||||
[contractAddress: string]: StructLog[];
|
||||
}
|
||||
|
||||
function getAddressFromStackEntry(stackEntry: string): string {
|
||||
const hexBase = 16;
|
||||
return addressUtils.padZeros(new BigNumber(addHexPrefix(stackEntry)).toString(hexBase));
|
||||
}
|
||||
|
||||
export function getTracesByContractAddress(structLogs: StructLog[], startAddress: string): TraceByContractAddress {
|
||||
const traceByContractAddress: TraceByContractAddress = {};
|
||||
let currentTraceSegment = [];
|
||||
const callStack = [startAddress];
|
||||
// tslint:disable-next-line:prefer-for-of
|
||||
for (let i = 0; i < structLogs.length; i++) {
|
||||
const structLog = structLogs[i];
|
||||
if (structLog.depth !== callStack.length - 1) {
|
||||
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);
|
||||
|
||||
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;
|
||||
const jumpAddressOffset = 1;
|
||||
const newAddress = getAddressFromStackEntry(
|
||||
structLog.stack[structLog.stack.length - jumpAddressOffset - 1],
|
||||
);
|
||||
if (structLog === _.last(structLogs)) {
|
||||
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 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) {
|
||||
callStack.push(newAddress);
|
||||
traceByContractAddress[currentAddress] = (traceByContractAddress[currentAddress] || []).concat(
|
||||
currentTraceSegment,
|
||||
);
|
||||
currentTraceSegment = [];
|
||||
}
|
||||
} else if (isEndOpcode) {
|
||||
const currentAddress = callStack.pop() as string;
|
||||
traceByContractAddress[currentAddress] = (traceByContractAddress[currentAddress] || []).concat(
|
||||
currentTraceSegment,
|
||||
);
|
||||
currentTraceSegment = [];
|
||||
if (structLog.op === OpCode.SelfDestruct) {
|
||||
// After contract execution, we look at all sub-calls to external contracts, and for each one, fetch
|
||||
// the bytecode and compute the coverage for the call. If the contract is destroyed with a call
|
||||
// to `selfdestruct`, we are unable to fetch it's bytecode and compute coverage.
|
||||
// TODO: Refactor this logic to fetch the sub-called contract bytecode before the selfdestruct is called
|
||||
// in order to handle this edge-case.
|
||||
logUtils.warn(
|
||||
"Detected a selfdestruct. Sol-cov currently doesn't support that scenario. We'll just skip the trace part for a destructed contract",
|
||||
);
|
||||
}
|
||||
} else if (structLog.op === OpCode.Create) {
|
||||
// TODO: Extract the new contract address from the stack and handle that scenario
|
||||
logUtils.warn(
|
||||
"Detected a contract created from within another contract. Sol-cov currently doesn't support that scenario. We'll just skip that trace",
|
||||
);
|
||||
return traceByContractAddress;
|
||||
} else {
|
||||
if (structLog !== _.last(structLogs)) {
|
||||
const nextStructLog = structLogs[i + 1];
|
||||
if (nextStructLog.depth === structLog.depth) {
|
||||
continue;
|
||||
} else if (nextStructLog.depth === structLog.depth - 1) {
|
||||
const currentAddress = callStack.pop() as string;
|
||||
traceByContractAddress[currentAddress] = (traceByContractAddress[currentAddress] || []).concat(
|
||||
currentTraceSegment,
|
||||
);
|
||||
currentTraceSegment = [];
|
||||
} else {
|
||||
throw new Error('Malformed trace. Unexpected call depth change');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (callStack.length !== 0) {
|
||||
throw new Error('Malformed trace. Call stack non empty at the end');
|
||||
}
|
||||
if (currentTraceSegment.length !== 0) {
|
||||
throw new Error('Malformed trace. Current trace segment non empty at the end');
|
||||
}
|
||||
return traceByContractAddress;
|
||||
}
|
@@ -98,3 +98,7 @@ export interface TraceInfoExistingContract extends TraceInfoBase {
|
||||
}
|
||||
|
||||
export type TraceInfo = TraceInfoNewContract | TraceInfoExistingContract;
|
||||
|
||||
export enum BlockParamLiteral {
|
||||
Latest = 'latest',
|
||||
}
|
||||
|
@@ -4,16 +4,17 @@ import 'make-promises-safe';
|
||||
import 'mocha';
|
||||
import * as path from 'path';
|
||||
|
||||
import { collectContractsData } from '../src/collect_contract_data';
|
||||
import { SolCompilerArtifactAdapter } from '../src/artifact_adapters/sol_compiler_artifact_adapter';
|
||||
|
||||
const expect = chai.expect;
|
||||
|
||||
describe('Collect contracts data', () => {
|
||||
describe('SolCompilerArtifactAdapter', () => {
|
||||
describe('#collectContractsData', () => {
|
||||
it('correctly collects contracts data', () => {
|
||||
it('correctly collects contracts data', async () => {
|
||||
const artifactsPath = path.resolve(__dirname, 'fixtures/artifacts');
|
||||
const sourcesPath = path.resolve(__dirname, 'fixtures/contracts');
|
||||
const contractsData = collectContractsData(artifactsPath, sourcesPath);
|
||||
const zeroExArtifactsAdapter = new SolCompilerArtifactAdapter(artifactsPath, sourcesPath);
|
||||
const contractsData = await zeroExArtifactsAdapter.collectContractsDataAsync();
|
||||
_.forEach(contractsData, contractData => {
|
||||
expect(contractData).to.have.keys([
|
||||
'sourceCodes',
|
57
packages/sol-cov/test/trace_test.ts
Normal file
57
packages/sol-cov/test/trace_test.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { OpCode, StructLog } from '@0xproject/types';
|
||||
import * as chai from 'chai';
|
||||
import * as fs from 'fs';
|
||||
import * as _ from 'lodash';
|
||||
import 'mocha';
|
||||
import * as path from 'path';
|
||||
|
||||
import { getTracesByContractAddress } from '../src/trace';
|
||||
|
||||
const expect = chai.expect;
|
||||
|
||||
const DEFAULT_STRUCT_LOG: StructLog = {
|
||||
depth: 0,
|
||||
error: '',
|
||||
gas: 0,
|
||||
gasCost: 0,
|
||||
memory: [],
|
||||
op: OpCode.Invalid,
|
||||
pc: 0,
|
||||
stack: [],
|
||||
storage: {},
|
||||
};
|
||||
|
||||
function addDefaultStructLogFields(compactStructLog: Partial<StructLog> & { op: OpCode; depth: number }): StructLog {
|
||||
return { ...DEFAULT_STRUCT_LOG, ...compactStructLog };
|
||||
}
|
||||
|
||||
describe('Trace', () => {
|
||||
describe('#getTracesByContractAddress', () => {
|
||||
it('correctly splits trace by contract address', () => {
|
||||
const delegateCallAddress = '0x0000000000000000000000000000000000000002';
|
||||
const trace = [
|
||||
{
|
||||
op: OpCode.DelegateCall,
|
||||
stack: [delegateCallAddress, '0x'],
|
||||
depth: 0,
|
||||
},
|
||||
{
|
||||
op: OpCode.Return,
|
||||
depth: 1,
|
||||
},
|
||||
{
|
||||
op: OpCode.Return,
|
||||
depth: 0,
|
||||
},
|
||||
];
|
||||
const fullTrace = _.map(trace, compactStructLog => addDefaultStructLogFields(compactStructLog));
|
||||
const startAddress = '0x0000000000000000000000000000000000000001';
|
||||
const traceByContractAddress = getTracesByContractAddress(fullTrace, startAddress);
|
||||
const expectedTraceByContractAddress = {
|
||||
[startAddress]: [fullTrace[0], fullTrace[2]],
|
||||
[delegateCallAddress]: [fullTrace[1]],
|
||||
};
|
||||
expect(traceByContractAddress).to.be.deep.equal(expectedTraceByContractAddress);
|
||||
});
|
||||
});
|
||||
});
|
@@ -1,4 +1,17 @@
|
||||
[
|
||||
{
|
||||
"version": "0.0.5",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Fix a bug in FsResolver where it tries to read directories as files",
|
||||
"pr": 589
|
||||
},
|
||||
{
|
||||
"note": "Fix a bug in NameResolver where it is not ignoring .sol files",
|
||||
"pr": 589
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1527009133,
|
||||
"version": "0.0.5",
|
||||
|
@@ -7,7 +7,7 @@ import { Resolver } from './resolver';
|
||||
export class FSResolver extends Resolver {
|
||||
// tslint:disable-next-line:prefer-function-over-method
|
||||
public resolveIfExists(importPath: string): ContractSource | undefined {
|
||||
if (fs.existsSync(importPath)) {
|
||||
if (fs.existsSync(importPath) && fs.lstatSync(importPath).isFile()) {
|
||||
const fileContent = fs.readFileSync(importPath).toString();
|
||||
return {
|
||||
source: fileContent,
|
||||
|
@@ -6,6 +6,8 @@ import { ContractSource } from '../types';
|
||||
|
||||
import { EnumerableResolver } from './enumerable_resolver';
|
||||
|
||||
const SOLIDITY_FILE_EXTENSION = '.sol';
|
||||
|
||||
export class NameResolver extends EnumerableResolver {
|
||||
private _contractsDir: string;
|
||||
constructor(contractsDir: string) {
|
||||
@@ -13,7 +15,6 @@ export class NameResolver extends EnumerableResolver {
|
||||
this._contractsDir = contractsDir;
|
||||
}
|
||||
public resolveIfExists(lookupContractName: string): ContractSource | undefined {
|
||||
const SOLIDITY_FILE_EXTENSION = '.sol';
|
||||
let contractSource: ContractSource | undefined;
|
||||
const onFile = (filePath: string) => {
|
||||
const contractName = path.basename(filePath, SOLIDITY_FILE_EXTENSION);
|
||||
@@ -32,7 +33,6 @@ export class NameResolver extends EnumerableResolver {
|
||||
return contractSource;
|
||||
}
|
||||
public getAll(): ContractSource[] {
|
||||
const SOLIDITY_FILE_EXTENSION = '.sol';
|
||||
const contractSources: ContractSource[] = [];
|
||||
const onFile = (filePath: string) => {
|
||||
const contractName = path.basename(filePath, SOLIDITY_FILE_EXTENSION);
|
||||
@@ -59,7 +59,12 @@ export class NameResolver extends EnumerableResolver {
|
||||
const absoluteEntryPath = path.join(dirPath, fileName);
|
||||
const isDirectory = fs.lstatSync(absoluteEntryPath).isDirectory();
|
||||
const entryPath = path.relative(this._contractsDir, absoluteEntryPath);
|
||||
const isComplete = isDirectory ? this._traverseContractsDir(absoluteEntryPath, onFile) : onFile(entryPath);
|
||||
let isComplete;
|
||||
if (isDirectory) {
|
||||
isComplete = this._traverseContractsDir(absoluteEntryPath, onFile);
|
||||
} else if (fileName.endsWith(SOLIDITY_FILE_EXTENSION)) {
|
||||
isComplete = onFile(entryPath);
|
||||
}
|
||||
if (isComplete) {
|
||||
return isComplete;
|
||||
}
|
||||
|
@@ -4,6 +4,7 @@ export { ECSignature } from '@0xproject/types';
|
||||
|
||||
import { LedgerEthereumClient } from './types';
|
||||
|
||||
export { prependSubprovider } from './utils/subprovider_utils';
|
||||
export { EmptyWalletSubprovider } from './subproviders/empty_wallet_subprovider';
|
||||
export { FakeGasEstimateSubprovider } from './subproviders/fake_gas_estimate_subprovider';
|
||||
export { InjectedWeb3Subprovider } from './subproviders/injected_web3';
|
||||
|
15
packages/subproviders/src/utils/subprovider_utils.ts
Normal file
15
packages/subproviders/src/utils/subprovider_utils.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import ProviderEngine = require('web3-provider-engine');
|
||||
|
||||
import { Subprovider } from '../subproviders/subprovider';
|
||||
|
||||
/**
|
||||
* Prepends a subprovider to a provider
|
||||
* @param provider Given provider
|
||||
* @param subprovider Subprovider to prepend
|
||||
*/
|
||||
export function prependSubprovider(provider: ProviderEngine, subprovider: Subprovider): void {
|
||||
subprovider.setEngine(provider);
|
||||
// HACK: We use implementation details of provider engine here
|
||||
// https://github.com/MetaMask/provider-engine/blob/master/index.js#L68
|
||||
(provider as any)._providers = [subprovider, ...(provider as any)._providers];
|
||||
}
|
@@ -2,6 +2,10 @@
|
||||
{
|
||||
"version": "0.7.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Make OpCode type an enum",
|
||||
"pr": 589
|
||||
},
|
||||
{
|
||||
"note":
|
||||
"Moved ExchangeContractErrs, DoneCallback, Token, OrderRelevantState, OrderStateValid, OrderStateInvalid, OrderState, OrderAddresses and OrderValues types from 0x.js",
|
||||
|
@@ -58,7 +58,18 @@ export interface DataItem {
|
||||
components?: DataItem[];
|
||||
}
|
||||
|
||||
export type OpCode = string;
|
||||
export enum OpCode {
|
||||
DelegateCall = 'DELEGATECALL',
|
||||
Revert = 'REVERT',
|
||||
Create = 'CREATE',
|
||||
Stop = 'STOP',
|
||||
Invalid = 'INVALID',
|
||||
CallCode = 'CALLCODE',
|
||||
StaticCall = 'STATICCALL',
|
||||
Return = 'RETURN',
|
||||
Call = 'CALL',
|
||||
SelfDestruct = 'SELFDESTRUCT',
|
||||
}
|
||||
|
||||
export interface StructLog {
|
||||
depth: number;
|
||||
|
@@ -1,8 +1,12 @@
|
||||
declare module 'web3-provider-engine' {
|
||||
class Web3ProviderEngine {
|
||||
import { Provider, JSONRPCRequestPayload, JSONRPCResponsePayload } from '@0xproject/types';
|
||||
class Web3ProviderEngine implements Provider {
|
||||
public on(event: string, handler: () => void): void;
|
||||
public send(payload: any): void;
|
||||
public sendAsync(payload: any, callback: (error: any, response: any) => void): void;
|
||||
public send(payload: JSONRPCRequestPayload): void;
|
||||
public sendAsync(
|
||||
payload: JSONRPCRequestPayload,
|
||||
callback: (error: null | Error, response: JSONRPCResponsePayload) => void,
|
||||
): void;
|
||||
public addProvider(provider: any): void;
|
||||
public start(): void;
|
||||
public stop(): void;
|
||||
@@ -19,13 +23,13 @@ declare module 'web3-provider-engine/subproviders/subprovider' {
|
||||
export = Subprovider;
|
||||
}
|
||||
declare module 'web3-provider-engine/subproviders/rpc' {
|
||||
import { JSONRPCRequestPayload } from '@0xproject/types';
|
||||
import { JSONRPCRequestPayload, JSONRPCResponsePayload } from '@0xproject/types';
|
||||
class RpcSubprovider {
|
||||
constructor(options: { rpcUrl: string });
|
||||
public handleRequest(
|
||||
payload: JSONRPCRequestPayload,
|
||||
next: () => void,
|
||||
end: (err: Error | null, data?: any) => void,
|
||||
end: (err: Error | null, data?: JSONRPCResponsePayload) => void,
|
||||
): void;
|
||||
}
|
||||
export = RpcSubprovider;
|
||||
@@ -37,13 +41,13 @@ declare module 'web3-provider-engine/util/rpc-cache-utils' {
|
||||
export = ProviderEngineRpcUtils;
|
||||
}
|
||||
declare module 'web3-provider-engine/subproviders/fixture' {
|
||||
import { JSONRPCRequestPayload } from '@0xproject/types';
|
||||
import { JSONRPCRequestPayload, JSONRPCResponsePayload } from '@0xproject/types';
|
||||
class FixtureSubprovider {
|
||||
constructor(staticResponses: any);
|
||||
public handleRequest(
|
||||
payload: JSONRPCRequestPayload,
|
||||
next: () => void,
|
||||
end: (err: Error | null, data?: any) => void,
|
||||
end: (err: Error | null, data?: JSONRPCResponsePayload) => void,
|
||||
): void;
|
||||
}
|
||||
export = FixtureSubprovider;
|
||||
|
@@ -8,6 +8,15 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"version": "0.7.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Add logUtils.warn",
|
||||
"pr": 589
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1525477860,
|
||||
"version": "0.6.1",
|
||||
|
@@ -38,6 +38,7 @@
|
||||
"@0xproject/types": "^0.7.0",
|
||||
"@0xproject/typescript-typings": "^0.3.2",
|
||||
"@types/node": "^8.0.53",
|
||||
"ethereumjs-util": "^5.1.1",
|
||||
"bignumber.js": "~4.1.0",
|
||||
"ethers": "^3.0.15",
|
||||
"js-sha3": "^0.7.0",
|
||||
|
@@ -12,21 +12,12 @@ import {
|
||||
import * as ethers from 'ethers';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { addressUtils } from './address_utils';
|
||||
import { BigNumber } from './configured_bignumber';
|
||||
|
||||
export class AbiDecoder {
|
||||
private _savedABIs: AbiDefinition[] = [];
|
||||
private _methodIds: { [signatureHash: string]: EventAbi } = {};
|
||||
private static _padZeros(address: string): string {
|
||||
let formatted = address;
|
||||
if (_.startsWith(formatted, '0x')) {
|
||||
formatted = formatted.slice(2);
|
||||
}
|
||||
|
||||
const addressLength = 40;
|
||||
formatted = _.padStart(formatted, addressLength, '0');
|
||||
return `0x${formatted}`;
|
||||
}
|
||||
constructor(abiArrays: AbiDefinition[][]) {
|
||||
_.forEach(abiArrays, this.addABI.bind(this));
|
||||
}
|
||||
@@ -56,7 +47,7 @@ export class AbiDecoder {
|
||||
}
|
||||
if (param.type === SolidityTypes.Address) {
|
||||
const baseHex = 16;
|
||||
value = AbiDecoder._padZeros(new BigNumber(value).toString(baseHex));
|
||||
value = addressUtils.padZeros(new BigNumber(value).toString(baseHex));
|
||||
} else if (param.type === SolidityTypes.Uint256 || param.type === SolidityTypes.Uint) {
|
||||
value = new BigNumber(value);
|
||||
} else if (param.type === SolidityTypes.Uint8) {
|
||||
|
@@ -1,7 +1,10 @@
|
||||
import { addHexPrefix, stripHexPrefix } from 'ethereumjs-util';
|
||||
import * as jsSHA3 from 'js-sha3';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
const BASIC_ADDRESS_REGEX = /^(0x)?[0-9a-f]{40}$/i;
|
||||
const SAME_CASE_ADDRESS_REGEX = /^(0x)?([0-9a-f]{40}|[0-9A-F]{40})$/;
|
||||
const ADDRESS_LENGTH = 40;
|
||||
|
||||
export const addressUtils = {
|
||||
isChecksumAddress(address: string): boolean {
|
||||
@@ -9,8 +12,7 @@ export const addressUtils = {
|
||||
const unprefixedAddress = address.replace('0x', '');
|
||||
const addressHash = jsSHA3.keccak256(unprefixedAddress.toLowerCase());
|
||||
|
||||
const addressLength = 40;
|
||||
for (let i = 0; i < addressLength; i++) {
|
||||
for (let i = 0; i < ADDRESS_LENGTH; i++) {
|
||||
// The nth letter should be uppercase if the nth digit of casemap is 1
|
||||
const hexBase = 16;
|
||||
const lowercaseRange = 7;
|
||||
@@ -38,4 +40,7 @@ export const addressUtils = {
|
||||
return isValidChecksummedAddress;
|
||||
}
|
||||
},
|
||||
padZeros(address: string): string {
|
||||
return addHexPrefix(_.padStart(stripHexPrefix(address), ADDRESS_LENGTH, '0'));
|
||||
},
|
||||
};
|
||||
|
@@ -2,4 +2,7 @@ export const logUtils = {
|
||||
log(...args: any[]): void {
|
||||
console.log(...args); // tslint:disable-line:no-console
|
||||
},
|
||||
warn(...args: any[]): void {
|
||||
console.warn(...args); // tslint:disable-line:no-console
|
||||
},
|
||||
};
|
||||
|
12
yarn.lock
12
yarn.lock
@@ -184,6 +184,10 @@
|
||||
version "4.14.99"
|
||||
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.99.tgz#e6e10c0a4cc16c7409b3181f1e66880d2fb7d4dc"
|
||||
|
||||
"@types/loglevel@^1.5.3":
|
||||
version "1.5.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/loglevel/-/loglevel-1.5.3.tgz#adfce55383edc5998a2170ad581b3e23d6adb5b8"
|
||||
|
||||
"@types/marked@0.0.28":
|
||||
version "0.0.28"
|
||||
resolved "https://registry.yarnpkg.com/@types/marked/-/marked-0.0.28.tgz#44ba754e9fa51432583e8eb30a7c4dd249b52faa"
|
||||
@@ -7081,7 +7085,7 @@ log-update@^1.0.2:
|
||||
ansi-escapes "^1.0.0"
|
||||
cli-cursor "^1.0.2"
|
||||
|
||||
loglevel@^1.4.1:
|
||||
loglevel@^1.4.1, loglevel@^1.6.1:
|
||||
version "1.6.1"
|
||||
resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.1.tgz#e0fc95133b6ef276cdc8887cdaf24aa6f156f8fa"
|
||||
|
||||
@@ -10503,9 +10507,9 @@ solc@^0.4.24:
|
||||
semver "^5.3.0"
|
||||
yargs "^4.7.1"
|
||||
|
||||
solidity-parser-antlr@^0.2.8:
|
||||
version "0.2.8"
|
||||
resolved "https://registry.yarnpkg.com/solidity-parser-antlr/-/solidity-parser-antlr-0.2.8.tgz#8eb8547a88dfeaf6cf4c7811e3824084214244d4"
|
||||
solidity-parser-antlr@^0.2.11:
|
||||
version "0.2.11"
|
||||
resolved "https://registry.yarnpkg.com/solidity-parser-antlr/-/solidity-parser-antlr-0.2.11.tgz#b3e4d0aca0d15796e9b59da53a49137aab51c298"
|
||||
|
||||
sort-keys@^1.0.0:
|
||||
version "1.1.2"
|
||||
|
Reference in New Issue
Block a user