Merge branch 'development' into convertScriptsToTs

* development: (71 commits)
  Transform input data before encoding for callAsync and getABIEncodedTransactionData
  Update coverage badge to show development coverage
  Configure post build hook
  Notify coveralls after all tasks have finished
  Address feedback
  Revert "Report all coverage reports together"
  Separate published packages and typescript typings on README
  Report all coverage reports together
  Add other statement types
  Properly and consistently parse ENV vars
  Add forgotten file
  Start using solidity-parser-antlr
  Fix the default always overriding to address
  Submit a TD PR
  Add an explanatory comment for making ranges unique
  Fix a typo in handling env variables
  Introduce TESTRPC_FIRST_ADDRESS
  Make BlockchainLifecycle accept only web3Wrapper
  Fix comments
  Fix deployer CHANGELOG
  ...

# Conflicts:
#	README.md
#	packages/deployer/package.json
#	packages/subproviders/src/globals.d.ts
#	yarn.lock
This commit is contained in:
Fabio Berger 2018-03-14 14:16:08 +01:00
commit 009b70f5b2
119 changed files with 4343 additions and 1647 deletions

View File

@ -120,3 +120,6 @@ workflows:
- lint:
requires:
- build
notify:
webhooks:
- url: https://coveralls.io/webhook?repo_token=ZYESXQbPiGidXzn2qkajycWa42uZHZsMX

View File

@ -10,7 +10,7 @@ This repository is a monorepo including the 0x protocol smart contracts and nume
[whitepaper-url]: https://0xproject.com/pdfs/0x_white_paper.pdf
[![CircleCI](https://circleci.com/gh/0xProject/0x-monorepo.svg?style=svg&circle-token=61bf7cd8c9b4e11b132089dfcffdd1be277d1e0c)](https://circleci.com/gh/0xProject/0x-monorepo)
[![Coverage Status](https://coveralls.io/repos/github/0xProject/0x-monorepo/badge.svg?branch=master&t=fp0cXD)](https://coveralls.io/github/0xProject/0x-monorepo?branch=master)
[![Coverage Status](https://coveralls.io/repos/github/0xProject/0x-monorepo/badge.svg?branch=development)](https://coveralls.io/github/0xProject/0x-monorepo?branch=development)
[![Discord](https://img.shields.io/badge/chat-rocket.chat-yellow.svg?style=flat)](https://chat.0xproject.com)
[![Join the chat at https://gitter.im/0xProject/Lobby](https://badges.gitter.im/0xProject/Lobby.svg)](https://gitter.im/0xProject/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
@ -18,29 +18,33 @@ This repository is a monorepo including the 0x protocol smart contracts and nume
### Published Packages
| Package | Version | Description |
| -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- |
| [`0x.js`](/packages/0x.js) | [![npm](https://img.shields.io/npm/v/0x.js.svg)](https://www.npmjs.com/package/0x.js) | A Javascript library for interacting with the 0x protocol |
| [`@0xproject/abi-gen`](/packages/abi-gen) | [![npm](https://img.shields.io/npm/v/@0xproject/abi-gen.svg)](https://www.npmjs.com/package/@0xproject/abi-gen) | Tool to generate TS wrappers from smart contract ABIs |
| [`@0xproject/assert`](/packages/assert) | [![npm](https://img.shields.io/npm/v/@0xproject/assert.svg)](https://www.npmjs.com/package/@0xproject/assert) | Type and schema assertions used by our packages |
| [`@0xproject/base-contract`](/packages/base-contract) | [![npm](https://img.shields.io/npm/v/@0xproject/base-contract.svg)](https://www.npmjs.com/package/@0xproject/base-contract) | BaseContract used by auto-generated `abi-gen` wrapper contracts |
| [`chai-as-promised-typescript-typings`](/packages/chai-as-promised-typescript-typings) | [![npm](https://img.shields.io/npm/v/chai-as-promised-typescript-typings.svg)](https://www.npmjs.com/package/chai-as-promised-typescript-typings) | Chai as promised typescript typings |
| [`chai-typescript-typings`](/packages/chai-typescript-typings) | [![npm](https://img.shields.io/npm/v/chai-typescript-typings.svg)](https://www.npmjs.com/package/chai-typescript-typings) | Chai typescript typings |
| [`@0xproject/connect`](/packages/connect) | [![npm](https://img.shields.io/npm/v/@0xproject/connect.svg)](https://www.npmjs.com/package/@0xproject/connect) | A Javascript library for interacting with the Standard Relayer API |
| [`@0xproject/deployer`](/packages/deployer) | [![npm](https://img.shields.io/npm/v/@0xproject/deployer.svg)](https://www.npmjs.com/package/@0xproject/deployer) | Solidity project compiler and deployer framework |
| [`@0xproject/dev-utils`](/packages/dev-utils) | [![npm](https://img.shields.io/npm/v/@0xproject/dev-utils.svg)](https://www.npmjs.com/package/@0xproject/dev-utils) | Dev utils to be shared across 0x projects and packages |
| [`ethers-typescript-typings`](/packages/ethers-typescript-typings) | [![npm](https://img.shields.io/npm/v/@0xproject/deployer.svg)](https://www.npmjs.com/package/ethers-typescript-typings) | [Ethers.js](https://github.com/ethers-io/ethers.js/) typescript typings |
| [`@0xproject/json-schemas`](/packages/json-schemas) | [![npm](https://img.shields.io/npm/v/@0xproject/json-schemas.svg)](https://www.npmjs.com/package/@0xproject/json-schemas) | 0x-related json schemas |
| [`@0xproject/monorepo-scripts`](/packages/monorepo-scripts) | [![npm](https://img.shields.io/npm/v/@0xproject/monorepo-scripts.svg)](https://www.npmjs.com/package/@0xproject/monorepo-scripts) | Scripts for managing a monorepo |
| [`@0xproject/react-docs`](/packages/react-docs) | [![npm](https://img.shields.io/npm/v/@0xproject/react-docs.svg)](https://www.npmjs.com/package/@0xproject/react-docs) | React documentation component for rendering TypeDoc & Doxity generated JSON |
| [`@0xproject/react-shared`](/packages/react-shared) | [![npm](https://img.shields.io/npm/v/@0xproject/react-shared.svg)](https://www.npmjs.com/package/@0xproject/react-shared) | 0x shared react components |
| [`@0xproject/sra-report`](/packages/sra-report) | [![npm](https://img.shields.io/npm/v/@0xproject/sra-report.svg)](https://www.npmjs.com/package/@0xproject/sra-report) | Generate reports for standard relayer API compliance |
| [`@0xproject/subproviders`](/packages/subproviders) | [![npm](https://img.shields.io/npm/v/@0xproject/subproviders.svg)](https://www.npmjs.com/package/@0xproject/subproviders) | Useful web3 subproviders (e.g LedgerSubprovider) |
| [`@0xproject/tslint-config`](/packages/tslint-config) | [![npm](https://img.shields.io/npm/v/@0xproject/tslint-config.svg)](https://www.npmjs.com/package/@0xproject/tslint-config) | Custom 0x development TSLint rules |
| [`@0xproject/types`](/packages/types) | [![npm](https://img.shields.io/npm/v/@0xproject/types.svg)](https://www.npmjs.com/package/@0xproject/types) | Shared type declarations |
| [`@0xproject/utils`](/packages/utils) | [![npm](https://img.shields.io/npm/v/@0xproject/utils.svg)](https://www.npmjs.com/package/@0xproject/utils) | Shared utilities |
| [`web3-typescript-typings`](/packages/web3-typescript-typings) | [![npm](https://img.shields.io/npm/v/web3-typescript-typings.svg)](https://www.npmjs.com/package/web3-typescript-typings) | Web3 typescript typings |
| [`@0xproject/web3-wrapper`](/packages/web3-wrapper) | [![npm](https://img.shields.io/npm/v/@0xproject/web3-wrapper.svg)](https://www.npmjs.com/package/@0xproject/web3-wrapper) | Web3 wrapper |
| Package | Version | Description |
| ----------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- |
| [`0x.js`](/packages/0x.js) | [![npm](https://img.shields.io/npm/v/0x.js.svg)](https://www.npmjs.com/package/0x.js) | A Javascript library for interacting with the 0x protocol |
| [`@0xproject/abi-gen`](/packages/abi-gen) | [![npm](https://img.shields.io/npm/v/@0xproject/abi-gen.svg)](https://www.npmjs.com/package/@0xproject/abi-gen) | Tool to generate TS wrappers from smart contract ABIs |
| [`@0xproject/assert`](/packages/assert) | [![npm](https://img.shields.io/npm/v/@0xproject/assert.svg)](https://www.npmjs.com/package/@0xproject/assert) | Type and schema assertions used by our packages |
| [`@0xproject/base-contract`](/packages/base-contract) | [![npm](https://img.shields.io/npm/v/@0xproject/base-contract.svg)](https://www.npmjs.com/package/@0xproject/base-contract) | BaseContract used by auto-generated `abi-gen` wrapper contracts |
| [`@0xproject/connect`](/packages/connect) | [![npm](https://img.shields.io/npm/v/@0xproject/connect.svg)](https://www.npmjs.com/package/@0xproject/connect) | A Javascript library for interacting with the Standard Relayer API |
| [`@0xproject/deployer`](/packages/deployer) | [![npm](https://img.shields.io/npm/v/@0xproject/deployer.svg)](https://www.npmjs.com/package/@0xproject/deployer) | Solidity project compiler and deployer framework |
| [`@0xproject/dev-utils`](/packages/dev-utils) | [![npm](https://img.shields.io/npm/v/@0xproject/dev-utils.svg)](https://www.npmjs.com/package/@0xproject/dev-utils) | Dev utils to be shared across 0x projects and packages |
| [`@0xproject/json-schemas`](/packages/json-schemas) | [![npm](https://img.shields.io/npm/v/@0xproject/json-schemas.svg)](https://www.npmjs.com/package/@0xproject/json-schemas) | 0x-related json schemas |
| [`@0xproject/react-docs`](/packages/react-docs) | [![npm](https://img.shields.io/npm/v/@0xproject/react-docs.svg)](https://www.npmjs.com/package/@0xproject/react-docs) | React documentation component for rendering TypeDoc & Doxity generated JSON |
| [`@0xproject/react-shared`](/packages/react-shared) | [![npm](https://img.shields.io/npm/v/@0xproject/react-shared.svg)](https://www.npmjs.com/package/@0xproject/react-shared) | 0x shared react components |
| [`@0xproject/sra-report`](/packages/sra-report) | [![npm](https://img.shields.io/npm/v/@0xproject/sra-report.svg)](https://www.npmjs.com/package/@0xproject/sra-report) | Generate reports for standard relayer API compliance |
| [`@0xproject/subproviders`](/packages/subproviders) | [![npm](https://img.shields.io/npm/v/@0xproject/subproviders.svg)](https://www.npmjs.com/package/@0xproject/subproviders) | Useful web3 subproviders (e.g LedgerSubprovider) |
| [`@0xproject/tslint-config`](/packages/tslint-config) | [![npm](https://img.shields.io/npm/v/@0xproject/tslint-config.svg)](https://www.npmjs.com/package/@0xproject/tslint-config) | Custom 0x development TSLint rules |
| [`@0xproject/types`](/packages/types) | [![npm](https://img.shields.io/npm/v/@0xproject/types.svg)](https://www.npmjs.com/package/@0xproject/types) | Shared type declarations |
| [`@0xproject/utils`](/packages/utils) | [![npm](https://img.shields.io/npm/v/@0xproject/utils.svg)](https://www.npmjs.com/package/@0xproject/utils) | Shared utilities |
| [`@0xproject/web3-wrapper`](/packages/web3-wrapper) | [![npm](https://img.shields.io/npm/v/@0xproject/web3-wrapper.svg)](https://www.npmjs.com/package/@0xproject/web3-wrapper) | Web3 wrapper |
### TypeScript Typings
| Package | Version | Description |
| -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
| [`chai-as-promised-typescript-typings`](/packages/chai-as-promised-typescript-typings) | [![npm](https://img.shields.io/npm/v/chai-as-promised-typescript-typings.svg)](https://www.npmjs.com/package/chai-as-promised-typescript-typings) | Chai as promised typescript typings |
| [`chai-typescript-typings`](/packages/chai-typescript-typings) | [![npm](https://img.shields.io/npm/v/chai-typescript-typings.svg)](https://www.npmjs.com/package/chai-typescript-typings) | Chai typescript typings |
| [`ethers-typescript-typings`](/packages/ethers-typescript-typings) | [![npm](https://img.shields.io/npm/v/@0xproject/deployer.svg)](https://www.npmjs.com/package/ethers-typescript-typings) | [Ethers.js](https://github.com/ethers-io/ethers.js/) typescript typings |
| [`web3-typescript-typings`](/packages/web3-typescript-typings) | [![npm](https://img.shields.io/npm/v/web3-typescript-typings.svg)](https://www.npmjs.com/package/web3-typescript-typings) | Web3 typescript typings |
### Private Packages

View File

@ -10,15 +10,15 @@ import { ApprovalContractEventArgs, LogWithDecodedArgs, Order, TokenEvents, Zero
import { chaiSetup } from './utils/chai_setup';
import { constants } from './utils/constants';
import { TokenUtils } from './utils/token_utils';
import { web3, web3Wrapper } from './utils/web3_wrapper';
const blockchainLifecycle = new BlockchainLifecycle();
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
chaiSetup.configure();
const expect = chai.expect;
const SHOULD_ADD_PERSONAL_MESSAGE_PREFIX = false;
describe('ZeroEx library', () => {
const web3 = web3Factory.create();
const config = {
networkId: constants.TESTRPC_NETWORK_ID,
};

View File

@ -23,10 +23,11 @@ import { chaiSetup } from './utils/chai_setup';
import { constants } from './utils/constants';
import { reportNodeCallbackErrors } from './utils/report_callback_errors';
import { TokenUtils } from './utils/token_utils';
import { web3, web3Wrapper } from './utils/web3_wrapper';
chaiSetup.configure();
const expect = chai.expect;
const blockchainLifecycle = new BlockchainLifecycle();
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
// Since the address depositing/withdrawing ETH/WETH also needs to pay gas costs for the transaction,
// a small amount of ETH will be used to pay this gas cost. We therefore check that the difference between
@ -35,7 +36,6 @@ const blockchainLifecycle = new BlockchainLifecycle();
const MAX_REASONABLE_GAS_COST_IN_WEI = 62517;
describe('EtherTokenWrapper', () => {
let web3: Web3;
let zeroEx: ZeroEx;
let tokens: Token[];
let userAddresses: string[];
@ -54,7 +54,6 @@ describe('EtherTokenWrapper', () => {
const depositAmount = new BigNumber(42);
const withdrawalAmount = new BigNumber(42);
before(async () => {
web3 = web3Factory.create();
zeroEx = new ZeroEx(web3.currentProvider, zeroExConfig);
tokens = await zeroEx.tokenRegistry.getTokensAsync();
userAddresses = await zeroEx.getAvailableAddressesAsync();

View File

@ -1,4 +1,4 @@
import { BlockchainLifecycle, devConstants, web3Factory } from '@0xproject/dev-utils';
import { BlockchainLifecycle, devConstants } from '@0xproject/dev-utils';
import { BlockParamLiteral } from '@0xproject/types';
import { BigNumber } from '@0xproject/utils';
import * as chai from 'chai';
@ -9,13 +9,13 @@ import { ExchangeTransferSimulator } from '../src/utils/exchange_transfer_simula
import { chaiSetup } from './utils/chai_setup';
import { constants } from './utils/constants';
import { web3, web3Wrapper } from './utils/web3_wrapper';
chaiSetup.configure();
const expect = chai.expect;
const blockchainLifecycle = new BlockchainLifecycle();
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
describe('ExchangeTransferSimulator', () => {
const web3 = web3Factory.create();
const config = {
networkId: constants.TESTRPC_NETWORK_ID,
};

View File

@ -26,15 +26,15 @@ import { constants } from './utils/constants';
import { FillScenarios } from './utils/fill_scenarios';
import { reportNodeCallbackErrors } from './utils/report_callback_errors';
import { TokenUtils } from './utils/token_utils';
import { web3, web3Wrapper } from './utils/web3_wrapper';
chaiSetup.configure();
const expect = chai.expect;
const blockchainLifecycle = new BlockchainLifecycle();
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
const NON_EXISTENT_ORDER_HASH = '0x79370342234e7acd6bbeac335bd3bb1d368383294b64b8160a00f4060e4d3777';
describe('ExchangeWrapper', () => {
let web3: Web3;
let zeroEx: ZeroEx;
let tokenUtils: TokenUtils;
let tokens: Token[];
@ -46,7 +46,6 @@ describe('ExchangeWrapper', () => {
networkId: constants.TESTRPC_NETWORK_ID,
};
before(async () => {
web3 = web3Factory.create();
zeroEx = new ZeroEx(web3.currentProvider, config);
exchangeContractAddress = zeroEx.exchange.getContractAddress();
userAddresses = await zeroEx.getAvailableAddressesAsync();

View File

@ -1,4 +1,4 @@
import { BlockchainLifecycle, devConstants, web3Factory } from '@0xproject/dev-utils';
import { BlockchainLifecycle, devConstants } from '@0xproject/dev-utils';
import { BigNumber } from '@0xproject/utils';
import * as chai from 'chai';
import * as _ from 'lodash';
@ -16,13 +16,13 @@ import { constants } from './utils/constants';
import { FillScenarios } from './utils/fill_scenarios';
import { reportNoErrorCallbackErrors } from './utils/report_callback_errors';
import { TokenUtils } from './utils/token_utils';
import { web3, web3Wrapper } from './utils/web3_wrapper';
chaiSetup.configure();
const expect = chai.expect;
const blockchainLifecycle = new BlockchainLifecycle();
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
describe('ExpirationWatcher', () => {
let web3: Web3;
let zeroEx: ZeroEx;
let tokenUtils: TokenUtils;
let tokens: Token[];
@ -41,7 +41,6 @@ describe('ExpirationWatcher', () => {
let timer: Sinon.SinonFakeTimers;
let expirationWatcher: ExpirationWatcher;
before(async () => {
web3 = web3Factory.create();
const config = {
networkId: constants.TESTRPC_NETWORK_ID,
};

View File

@ -1,4 +1,4 @@
import { BlockchainLifecycle, devConstants, web3Factory } from '@0xproject/dev-utils';
import { BlockchainLifecycle, devConstants } from '@0xproject/dev-utils';
import { BigNumber } from '@0xproject/utils';
import * as chai from 'chai';
import * as _ from 'lodash';
@ -22,15 +22,15 @@ import { constants } from './utils/constants';
import { FillScenarios } from './utils/fill_scenarios';
import { reportNodeCallbackErrors } from './utils/report_callback_errors';
import { TokenUtils } from './utils/token_utils';
import { web3, web3Wrapper } from './utils/web3_wrapper';
const TIMEOUT_MS = 150;
chaiSetup.configure();
const expect = chai.expect;
const blockchainLifecycle = new BlockchainLifecycle();
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
describe('OrderStateWatcher', () => {
let web3: Web3;
let zeroEx: ZeroEx;
let tokens: Token[];
let tokenUtils: TokenUtils;
@ -49,7 +49,6 @@ describe('OrderStateWatcher', () => {
const decimals = constants.ZRX_DECIMALS;
const fillableAmount = ZeroEx.toBaseUnitAmount(new BigNumber(5), decimals);
before(async () => {
web3 = web3Factory.create();
zeroEx = new ZeroEx(web3.currentProvider, config);
exchangeContractAddress = zeroEx.exchange.getContractAddress();
userAddresses = await zeroEx.getAvailableAddressesAsync();

View File

@ -1,4 +1,4 @@
import { BlockchainLifecycle, devConstants, web3Factory } from '@0xproject/dev-utils';
import { BlockchainLifecycle, devConstants } from '@0xproject/dev-utils';
import { BlockParamLiteral } from '@0xproject/types';
import { BigNumber } from '@0xproject/utils';
import * as chai from 'chai';
@ -14,13 +14,13 @@ import { chaiSetup } from './utils/chai_setup';
import { constants } from './utils/constants';
import { FillScenarios } from './utils/fill_scenarios';
import { TokenUtils } from './utils/token_utils';
import { web3, web3Wrapper } from './utils/web3_wrapper';
chaiSetup.configure();
const expect = chai.expect;
const blockchainLifecycle = new BlockchainLifecycle();
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
describe('OrderValidation', () => {
let web3: Web3;
let zeroEx: ZeroEx;
let userAddresses: string[];
let tokens: Token[];
@ -40,7 +40,6 @@ describe('OrderValidation', () => {
networkId: constants.TESTRPC_NETWORK_ID,
};
before(async () => {
web3 = web3Factory.create();
zeroEx = new ZeroEx(web3.currentProvider, config);
exchangeContractAddress = zeroEx.exchange.getContractAddress();
userAddresses = await zeroEx.getAvailableAddressesAsync();

View File

@ -1,4 +1,4 @@
import { BlockchainLifecycle, devConstants, web3Factory } from '@0xproject/dev-utils';
import { BlockchainLifecycle, devConstants } from '@0xproject/dev-utils';
import { BigNumber } from '@0xproject/utils';
import * as _ from 'lodash';
import 'mocha';
@ -11,12 +11,12 @@ import { DoneCallback } from '../src/types';
import { chaiSetup } from './utils/chai_setup';
import { constants } from './utils/constants';
import { assertNodeCallbackError } from './utils/report_callback_errors';
import { web3, web3Wrapper } from './utils/web3_wrapper';
chaiSetup.configure();
const blockchainLifecycle = new BlockchainLifecycle();
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
describe('SubscriptionTest', () => {
let web3: Web3;
let zeroEx: ZeroEx;
let userAddresses: string[];
let tokens: Token[];
@ -26,7 +26,6 @@ describe('SubscriptionTest', () => {
networkId: constants.TESTRPC_NETWORK_ID,
};
before(async () => {
web3 = web3Factory.create();
zeroEx = new ZeroEx(web3.currentProvider, config);
userAddresses = await zeroEx.getAvailableAddressesAsync();
tokens = await zeroEx.tokenRegistry.getTokensAsync();

View File

@ -1,4 +1,4 @@
import { BlockchainLifecycle, devConstants, web3Factory } from '@0xproject/dev-utils';
import { BlockchainLifecycle, devConstants } from '@0xproject/dev-utils';
import { schemas, SchemaValidator } from '@0xproject/json-schemas';
import * as chai from 'chai';
import * as _ from 'lodash';
@ -8,10 +8,11 @@ import { Token, ZeroEx } from '../src';
import { chaiSetup } from './utils/chai_setup';
import { constants } from './utils/constants';
import { web3, web3Wrapper } from './utils/web3_wrapper';
chaiSetup.configure();
const expect = chai.expect;
const blockchainLifecycle = new BlockchainLifecycle();
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
const TOKEN_REGISTRY_SIZE_AFTER_MIGRATION = 7;
@ -30,7 +31,6 @@ describe('TokenRegistryWrapper', () => {
networkId: constants.TESTRPC_NETWORK_ID,
};
before(async () => {
const web3 = web3Factory.create();
zeroEx = new ZeroEx(web3.currentProvider, config);
tokens = await zeroEx.tokenRegistry.getTokensAsync();
_.map(tokens, token => {

View File

@ -8,6 +8,7 @@ import { constants } from './utils/constants';
chaiSetup.configure();
const expect = chai.expect;
const web3 = web3Factory.create();
describe('TokenTransferProxyWrapper', () => {
let zeroEx: ZeroEx;
@ -15,7 +16,6 @@ describe('TokenTransferProxyWrapper', () => {
networkId: constants.TESTRPC_NETWORK_ID,
};
before(async () => {
const web3 = web3Factory.create();
zeroEx = new ZeroEx(web3.currentProvider, config);
});
describe('#isAuthorizedAsync', () => {

View File

@ -22,27 +22,24 @@ import { chaiSetup } from './utils/chai_setup';
import { constants } from './utils/constants';
import { reportNodeCallbackErrors } from './utils/report_callback_errors';
import { TokenUtils } from './utils/token_utils';
import { web3, web3Wrapper } from './utils/web3_wrapper';
chaiSetup.configure();
const expect = chai.expect;
const blockchainLifecycle = new BlockchainLifecycle();
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
describe('TokenWrapper', () => {
let web3: Web3;
let zeroEx: ZeroEx;
let userAddresses: string[];
let tokens: Token[];
let tokenUtils: TokenUtils;
let coinbase: string;
let addressWithoutFunds: string;
let web3Wrapper: Web3Wrapper;
const config = {
networkId: constants.TESTRPC_NETWORK_ID,
};
before(async () => {
web3 = web3Factory.create();
zeroEx = new ZeroEx(web3.currentProvider, config);
web3Wrapper = new Web3Wrapper(web3.currentProvider);
userAddresses = await zeroEx.getAvailableAddressesAsync();
tokens = await zeroEx.tokenRegistry.getTokensAsync();
tokenUtils = new TokenUtils(tokens);
@ -194,7 +191,7 @@ describe('TokenWrapper', () => {
let zeroExWithoutAccounts: ZeroEx;
before(async () => {
const hasAddresses = false;
const web3WithoutAccounts = web3Factory.create(hasAddresses);
const web3WithoutAccounts = web3Factory.create({ hasAddresses });
zeroExWithoutAccounts = new ZeroEx(web3WithoutAccounts.currentProvider, config);
});
it('should return balance even when called with Web3 provider instance without addresses', async () => {
@ -306,7 +303,7 @@ describe('TokenWrapper', () => {
let zeroExWithoutAccounts: ZeroEx;
before(async () => {
const hasAddresses = false;
const web3WithoutAccounts = web3Factory.create(hasAddresses);
const web3WithoutAccounts = web3Factory.create({ hasAddresses });
zeroExWithoutAccounts = new ZeroEx(web3WithoutAccounts.currentProvider, config);
});
it('should get the proxy allowance', async () => {

View File

@ -0,0 +1,6 @@
import { web3Factory } from '@0xproject/dev-utils';
import { Web3Wrapper } from '@0xproject/web3-wrapper';
import * as Web3 from 'web3';
export const web3 = web3Factory.create();
export const web3Wrapper = new Web3Wrapper(web3.currentProvider);

View File

@ -32,6 +32,8 @@ public {{this.name}} = {
txData: TxData = {},
): Promise<number> {
const self = this as {{contractName}}Contract;
const inputAbi = _.find(this.abi, {name: '{{this.name}}'}).inputs;
[{{> params inputs=inputs}}] = BaseContract._transformABIData(inputAbi, [{{> params inputs=inputs}}], BaseContract._bigNumberToString.bind(this));
const encodedData = this._ethersInterface.functions.{{this.name}}(
{{> params inputs=inputs}}
).data
@ -49,6 +51,8 @@ public {{this.name}} = {
txData: TxData = {},
): string {
const self = this as {{contractName}}Contract;
const inputAbi = _.find(this.abi, {name: '{{this.name}}'}).inputs;
[{{> params inputs=inputs}}] = BaseContract._transformABIData(inputAbi, [{{> params inputs=inputs}}], BaseContract._bigNumberToString.bind(this));
const abiEncodedTransactionData = this._ethersInterface.functions.{{this.name}}(
{{> params inputs=inputs}}
).data

View File

View File

@ -1,4 +1,4 @@
import { MultiSigConfigByNetwork } from '../../types';
import { MultiSigConfigByNetwork } from '../types';
// Make a copy of this file named `multisig.js` and input custom params as needed
export const multiSig: MultiSigConfigByNetwork = {

View File

@ -1,5 +1,5 @@
import { Token } from '../../types';
import { constants } from '../../utils/constants';
import { constants } from '../../util/constants';
import { Token } from '../types';
export const tokenInfo: Token[] = [
{

View File

@ -0,0 +1,20 @@
import { Deployer } from '@0xproject/deployer';
import { devConstants } from '@0xproject/dev-utils';
import * as path from 'path';
import { constants } from '../util/constants';
import { runMigrationsAsync } from './migrate';
const deployerOpts = {
artifactsDir: path.resolve('src', 'artifacts'),
jsonrpcUrl: devConstants.RPC_URL,
networkId: constants.TESTRPC_NETWORK_ID,
defaults: {
gas: devConstants.GAS_ESTIMATE,
},
};
export const deployer = new Deployer(deployerOpts);
runMigrationsAsync(deployer).catch(console.log);

View File

@ -0,0 +1,90 @@
import { Deployer } from '@0xproject/deployer';
import { BigNumber } from '@0xproject/utils';
import { Web3Wrapper } from '@0xproject/web3-wrapper';
import * as _ from 'lodash';
import { constants } from '../util/constants';
import { ContractName } from '../util/types';
import { tokenInfo } from './config/token_info';
/**
* Custom migrations should be defined in this function. This will be called with the CLI 'migrate' command.
* Migrations could be written to run in parallel, but if you want contract addresses to be created deterministically,
* the migration should be written to run synchronously.
* @param deployer Deployer instance.
*/
export const runMigrationsAsync = async (deployer: Deployer) => {
const web3Wrapper: Web3Wrapper = deployer.web3Wrapper;
const accounts: string[] = await web3Wrapper.getAvailableAddressesAsync();
const tokenTransferProxy = await deployer.deployAndSaveAsync(ContractName.TokenTransferProxy);
const zrxToken = await deployer.deployAndSaveAsync(ContractName.ZRXToken);
const etherToken = await deployer.deployAndSaveAsync(ContractName.EtherToken);
const tokenReg = await deployer.deployAndSaveAsync(ContractName.TokenRegistry);
const exchangeArgs = [zrxToken.address, tokenTransferProxy.address];
const owners = [accounts[0], accounts[1]];
const confirmationsRequired = new BigNumber(2);
const secondsRequired = new BigNumber(0);
const multiSigArgs = [owners, confirmationsRequired, secondsRequired, tokenTransferProxy.address];
const exchange = await deployer.deployAndSaveAsync(ContractName.Exchange, exchangeArgs);
const multiSig = await deployer.deployAndSaveAsync(
ContractName.MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress,
multiSigArgs,
);
const owner = accounts[0];
await tokenTransferProxy.addAuthorizedAddress.sendTransactionAsync(exchange.address, { from: owner });
await tokenTransferProxy.transferOwnership.sendTransactionAsync(multiSig.address, { from: owner });
const addTokenGasEstimate = await tokenReg.addToken.estimateGasAsync(
zrxToken.address,
tokenInfo[0].name,
tokenInfo[0].symbol,
tokenInfo[0].decimals,
tokenInfo[0].ipfsHash,
tokenInfo[0].swarmHash,
{ from: owner },
);
await tokenReg.addToken.sendTransactionAsync(
zrxToken.address,
'0x Protocol Token',
'ZRX',
18,
constants.NULL_BYTES,
constants.NULL_BYTES,
{
from: owner,
gas: addTokenGasEstimate,
},
);
await tokenReg.addToken.sendTransactionAsync(
etherToken.address,
'Ether Token',
'WETH',
18,
constants.NULL_BYTES,
constants.NULL_BYTES,
{
from: owner,
gas: addTokenGasEstimate,
},
);
for (const token of tokenInfo) {
const totalSupply = new BigNumber(0);
const args = [token.name, token.symbol, token.decimals, totalSupply];
const dummyToken = await deployer.deployAsync(ContractName.DummyToken, args);
await tokenReg.addToken.sendTransactionAsync(
dummyToken.address,
token.name,
token.symbol,
token.decimals,
token.ipfsHash,
token.swarmHash,
{
from: owner,
gas: addTokenGasEstimate,
},
);
}
};

View File

@ -13,14 +13,19 @@
"copy_artifacts": "copyfiles './src/artifacts/**/*' ./lib",
"build": "tsc",
"test": "run-s build run_mocha",
"run_mocha": "mocha 'lib/test/**/*.js' --timeout 10000 --bail --exit",
"test:coverage": "SOLIDITY_COVERAGE=true run-s build run_mocha coverage:report:text",
"run_mocha": "mocha 'lib/test/**/*.js' --timeout 100000 --bail --exit",
"compile:comment": "Yarn workspaces do not link binaries correctly so we need to reference them directly https://github.com/yarnpkg/yarn/issues/3846",
"compile": "node ../deployer/lib/src/cli.js compile --contracts ${npm_package_config_contracts} --contracts-dir src/contracts --artifacts-dir src/artifacts",
"clean": "shx rm -rf ./lib",
"generate_contract_wrappers": "node ../abi-gen/lib/index.js --abis 'src/artifacts/@(DummyToken|TokenTransferProxy|Exchange|TokenRegistry|MultiSigWallet|MultiSigWalletWithTimeLock|MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress|TokenRegistry|ZRXToken|Arbitrage|EtherDelta|AccountLevels).json' --template ../contract_templates/contract.handlebars --partials '../contract_templates/partials/**/*.handlebars' --output src/contract_wrappers/generated --backend ethers && prettier --write 'src/contract_wrappers/generated/**.ts'",
"migrate": "node ../deployer/lib/src/cli.js migrate",
"migrate": "yarn build && yarn compile && node ./lib/migrations/index.js",
"lint": "tslint --project . 'migrations/**/*.ts' 'test/**/*.ts' 'util/**/*.ts' 'deploy/**/*.ts'",
"test:circleci": "yarn test"
"coverage:report:text": "istanbul report text",
"coverage:report:html": "istanbul report html && open coverage/index.html",
"coverage:report:lcov": "istanbul report lcov",
"coverage:report:coveralls": "yarn coverage:report:lcov && cat coverage/lcov.info | coveralls",
"test:circleci": "yarn test:coverage && yarn coverage:report:coveralls"
},
"config": {
"contracts": "Exchange,DummyToken,ZRXToken,Token,WETH9,TokenTransferProxy,MultiSigWallet,MultiSigWalletWithTimeLock,MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress,MaliciousToken,TokenRegistry,Arbitrage,EtherDelta,AccountLevels"
@ -49,6 +54,7 @@
"chai-bignumber": "^2.0.1",
"chai-typescript-typings": "^0.0.4",
"copyfiles": "^1.2.0",
"coveralls": "^3.0.0",
"dirty-chai": "^2.0.1",
"ethers-typescript-typings": "^0.0.2",
"mocha": "^4.0.1",

View File

@ -363,4 +363,3 @@ contract MultiSigWallet {
_transactionIds[i - from] = transactionIdsTemp[i];
}
}

View File

@ -9,12 +9,11 @@ import { ContractName } from '../util/types';
import { chaiSetup } from './utils/chai_setup';
import { deployer } from './utils/deployer';
import { web3, web3Wrapper } from './utils/web3_wrapper';
chaiSetup.configure();
const expect = chai.expect;
const web3 = web3Factory.create();
const web3Wrapper = new Web3Wrapper(web3.currentProvider);
const blockchainLifecycle = new BlockchainLifecycle();
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
describe('EtherToken', () => {
let account: string;

View File

@ -22,12 +22,11 @@ import { OrderFactory } from '../../util/order_factory';
import { BalancesByOwner, ContractName, ExchangeContractErrs } from '../../util/types';
import { chaiSetup } from '../utils/chai_setup';
import { deployer } from '../utils/deployer';
import { web3, web3Wrapper } from '../utils/web3_wrapper';
chaiSetup.configure();
const expect = chai.expect;
const web3 = web3Factory.create();
const web3Wrapper = new Web3Wrapper(web3.currentProvider);
const blockchainLifecycle = new BlockchainLifecycle();
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
describe('Exchange', () => {
let maker: string;

View File

@ -17,13 +17,12 @@ import { OrderFactory } from '../../util/order_factory';
import { ContractName } from '../../util/types';
import { chaiSetup } from '../utils/chai_setup';
import { deployer } from '../utils/deployer';
import { web3, web3Wrapper } from '../utils/web3_wrapper';
chaiSetup.configure();
const expect = chai.expect;
const web3 = web3Factory.create();
const web3Wrapper = new Web3Wrapper(web3.currentProvider);
const blockchainLifecycle = new BlockchainLifecycle();
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
describe('Exchange', () => {
let maker: string;

View File

@ -22,12 +22,11 @@ import { OrderFactory } from '../../util/order_factory';
import { BalancesByOwner, ContractName } from '../../util/types';
import { chaiSetup } from '../utils/chai_setup';
import { deployer } from '../utils/deployer';
import { web3, web3Wrapper } from '../utils/web3_wrapper';
chaiSetup.configure();
const expect = chai.expect;
const web3 = web3Factory.create();
const web3Wrapper = new Web3Wrapper(web3.currentProvider);
const blockchainLifecycle = new BlockchainLifecycle();
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
describe('Exchange', () => {
let maker: string;

View File

@ -0,0 +1,8 @@
import { coverage, env, EnvVars } from '@0xproject/dev-utils';
after('generate coverage report', async () => {
if (env.parseBoolean(EnvVars.SolidityCoverage)) {
const coverageSubprovider = coverage.getCoverageSubproviderSingleton();
await coverageSubprovider.writeCoverageAsync();
}
});

View File

@ -1,5 +1,5 @@
import { LogWithDecodedArgs, ZeroEx } from '0x.js';
import { BlockchainLifecycle, RPC, web3Factory } from '@0xproject/dev-utils';
import { BlockchainLifecycle, web3Factory } from '@0xproject/dev-utils';
import { AbiDecoder, BigNumber } from '@0xproject/utils';
import { Web3Wrapper } from '@0xproject/web3-wrapper';
import * as chai from 'chai';
@ -15,14 +15,12 @@ import { ContractName, SubmissionContractEventArgs } from '../util/types';
import { chaiSetup } from './utils/chai_setup';
import { deployer } from './utils/deployer';
import { web3, web3Wrapper } from './utils/web3_wrapper';
const MULTI_SIG_ABI = artifacts.MultiSigWalletWithTimeLockArtifact.networks[constants.TESTRPC_NETWORK_ID].abi;
chaiSetup.configure();
const expect = chai.expect;
const web3 = web3Factory.create();
const web3Wrapper = new Web3Wrapper(web3.currentProvider);
const blockchainLifecycle = new BlockchainLifecycle();
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
const zeroEx = new ZeroEx(web3.currentProvider, { networkId: constants.TESTRPC_NETWORK_ID });
const abiDecoder = new AbiDecoder([MULTI_SIG_ABI]);
@ -39,11 +37,6 @@ describe('MultiSigWalletWithTimeLock', () => {
let multiSigWrapper: MultiSigWrapper;
let txId: BigNumber;
let initialSecondsTimeLocked: number;
let rpc: RPC;
before(async () => {
rpc = new RPC();
});
beforeEach(async () => {
await blockchainLifecycle.startAsync();
});
@ -192,7 +185,7 @@ describe('MultiSigWalletWithTimeLock', () => {
});
it('should execute if it has enough confirmations and is past the time lock', async () => {
await rpc.increaseTimeAsync(SECONDS_TIME_LOCKED.toNumber());
await web3Wrapper.increaseTimeAsync(SECONDS_TIME_LOCKED.toNumber());
await multiSig.executeTransaction.sendTransactionAsync(txId, { from: owners[0] });
const secondsTimeLocked = new BigNumber(await multiSig.secondsTimeLocked.callAsync());

View File

@ -16,6 +16,7 @@ import { ContractName, SubmissionContractEventArgs, TransactionDataParams } from
import { chaiSetup } from './utils/chai_setup';
import { deployer } from './utils/deployer';
import { web3, web3Wrapper } from './utils/web3_wrapper';
const PROXY_ABI = artifacts.TokenTransferProxyArtifact.networks[constants.TESTRPC_NETWORK_ID].abi;
const MUTISIG_WALLET_WITH_TIME_LOCK_EXCEPT_REMOVE_AUTHORIZED_ADDRESS_ABI =
artifacts.MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddressArtifact.networks[constants.TESTRPC_NETWORK_ID]
@ -23,9 +24,7 @@ const MUTISIG_WALLET_WITH_TIME_LOCK_EXCEPT_REMOVE_AUTHORIZED_ADDRESS_ABI =
chaiSetup.configure();
const expect = chai.expect;
const web3 = web3Factory.create();
const web3Wrapper = new Web3Wrapper(web3.currentProvider);
const blockchainLifecycle = new BlockchainLifecycle();
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
const abiDecoder = new AbiDecoder([MUTISIG_WALLET_WITH_TIME_LOCK_EXCEPT_REMOVE_AUTHORIZED_ADDRESS_ABI]);
describe('MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress', () => {

View File

@ -14,12 +14,11 @@ import { ContractName } from '../util/types';
import { chaiSetup } from './utils/chai_setup';
import { deployer } from './utils/deployer';
import { web3, web3Wrapper } from './utils/web3_wrapper';
chaiSetup.configure();
const expect = chai.expect;
const web3 = web3Factory.create();
const web3Wrapper = new Web3Wrapper(web3.currentProvider);
const blockchainLifecycle = new BlockchainLifecycle();
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
describe('TokenRegistry', () => {
let owner: string;

View File

@ -8,12 +8,11 @@ import { constants } from '../../util/constants';
import { ContractName } from '../../util/types';
import { chaiSetup } from '../utils/chai_setup';
import { deployer } from '../utils/deployer';
import { web3, web3Wrapper } from '../utils/web3_wrapper';
chaiSetup.configure();
const expect = chai.expect;
const web3 = web3Factory.create();
const web3Wrapper = new Web3Wrapper(web3.currentProvider);
const blockchainLifecycle = new BlockchainLifecycle();
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
describe('TokenTransferProxy', () => {
let owner: string;

View File

@ -11,12 +11,11 @@ import { constants } from '../../util/constants';
import { ContractName } from '../../util/types';
import { chaiSetup } from '../utils/chai_setup';
import { deployer } from '../utils/deployer';
import { web3, web3Wrapper } from '../utils/web3_wrapper';
chaiSetup.configure();
const expect = chai.expect;
const web3 = web3Factory.create();
const web3Wrapper = new Web3Wrapper(web3.currentProvider);
const blockchainLifecycle = new BlockchainLifecycle();
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
describe('TokenTransferProxy', () => {
let accounts: string[];

View File

@ -17,12 +17,11 @@ import { OrderFactory } from '../../util/order_factory';
import { BalancesByOwner, ContractName, ExchangeContractErrs } from '../../util/types';
import { chaiSetup } from '../utils/chai_setup';
import { deployer } from '../utils/deployer';
import { web3, web3Wrapper } from '../utils/web3_wrapper';
chaiSetup.configure();
const expect = chai.expect;
const web3 = web3Factory.create();
const web3Wrapper = new Web3Wrapper(web3.currentProvider);
const blockchainLifecycle = new BlockchainLifecycle();
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
describe('Arbitrage', () => {
let coinbase: string;

View File

@ -11,12 +11,11 @@ import { ContractName } from '../util/types';
import { chaiSetup } from './utils/chai_setup';
import { deployer } from './utils/deployer';
import { web3, web3Wrapper } from './utils/web3_wrapper';
const web3 = web3Factory.create();
const web3Wrapper = new Web3Wrapper(web3.currentProvider);
chaiSetup.configure();
const expect = chai.expect;
const blockchainLifecycle = new BlockchainLifecycle();
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
describe('UnlimitedAllowanceToken', () => {
let owner: string;

View File

@ -4,9 +4,12 @@ import * as path from 'path';
import { constants } from '../../util/constants';
import { web3 } from './web3_wrapper';
const deployerOpts = {
web3Provider: web3.currentProvider,
artifactsDir: path.resolve('src', 'artifacts'),
jsonrpcPort: devConstants.RPC_PORT,
jsonrpcUrl: devConstants.RPC_URL,
networkId: constants.TESTRPC_NETWORK_ID,
defaults: {
gas: devConstants.GAS_ESTIMATE,

View File

@ -0,0 +1,6 @@
import { web3Factory } from '@0xproject/dev-utils';
import { Web3Wrapper } from '@0xproject/web3-wrapper';
const web3ProviderConfig = { shouldUseInProcessGanache: true };
export const web3 = web3Factory.create(web3ProviderConfig);
export const web3Wrapper = new Web3Wrapper(web3.currentProvider);

View File

@ -11,12 +11,11 @@ import { ContractName } from '../util/types';
import { chaiSetup } from './utils/chai_setup';
import { deployer } from './utils/deployer';
import { web3, web3Wrapper } from './utils/web3_wrapper';
chaiSetup.configure();
const expect = chai.expect;
const web3 = web3Factory.create();
const web3Wrapper = new Web3Wrapper(web3.currentProvider);
const blockchainLifecycle = new BlockchainLifecycle();
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
describe('ZRXToken', () => {
let owner: string;

View File

@ -3,6 +3,13 @@
## v0.3.0 - _TBD, 2018_
* Add support for Solidity 0.4.20 and 0.4.21
* Replace `jsonrpcPort` config with `jsonrpcUrl` (#426)
* Replace `jsonrpc-port` CLI option with `jsonrpc-url` (#426)
* Export the `Compiler` (#426)
* Load solc from remote source instead of having it locally (#426)
* Add `bytecode`, `runtime_bytecode`, `source_map`, `source_map_runtime` and `sources` fields to artifacts (#426)
* Remove 0x-specific `migrate` command (#426)
* Allow deployer to accept a provider instead of port and host. This makes it possible to run it with in-process ganache-core (#426)
## v0.2.0 - _March 4, 2018_

View File

@ -10,13 +10,14 @@ yarn add @0xproject/deployer
## Usage
### CLI Usage
```bash
node ./node_modules/@0xproject/deployer/lib/cli.js --help
cli.js [command]
Commands:
cli.js compile compile contracts
cli.js migrate compile and deploy contracts using migration scripts
cli.js deploy deploy a single contract with provided arguments
Options:
@ -34,6 +35,34 @@ Options:
--help Show help [boolean]
```
### API Usage
## Migrations
You might want to write a migration scripts (similar to `truffle migrate`), that deploys multiple contracts and configures them. Below you'll find a simple example of such a script to help you get started.
```typescript
import { Deployer } from '@0xproject/deployer';
import * as path from 'path';
const deployerOpts = {
artifactsDir: path.resolve('src', 'artifacts'),
jsonrpcUrl: 'http://localhost:8545',
networkId: 50,
defaults: {
gas: 1000000,
},
};
const deployer = new Deployer(deployerOpts);
(async () => {
const etherToken = await deployer.deployAndSaveAsync('WETH9');
})().catch(console.log);
```
A more sophisticated example can be found [here](https://github.com/0xProject/0x-monorepo/tree/development/packages/contracts/migrations)
## Contributing
We strongly recommend that the community help us make improvements and determine the future direction of the protocol. To report bugs within this package, please create an issue in this repository.

View File

@ -6,7 +6,7 @@
"types": "lib/src/index.d.ts",
"scripts": {
"build:watch": "tsc -w",
"build": "yarn clean && copyfiles 'test/fixtures/contracts/**/*' src/solc/solc_bin/* ./lib && tsc && copyfiles -u 3 './lib/src/monorepo_scripts/**/*' ./scripts",
"build": "yarn clean && copyfiles 'test/fixtures/contracts/**/*' ./lib && tsc && copyfiles -u 3 './lib/src/monorepo_scripts/**/*' ./scripts",
"test": "npm run build; mocha lib/test/*_test.js",
"compile": "npm run build; node lib/src/cli.js compile",
"clean": "shx rm -rf lib scripts",
@ -30,6 +30,7 @@
"devDependencies": {
"@0xproject/monorepo-scripts": "^0.1.12",
"@0xproject/tslint-config": "^0.4.10",
"@types/require-from-string": "^1.2.0",
"chai": "^4.0.1",
"copyfiles": "^1.2.0",
"ethers-typescript-typings": "^0.0.2",
@ -46,7 +47,9 @@
"@0xproject/utils": "^0.4.1",
"@0xproject/web3-wrapper": "^0.2.1",
"ethereumjs-util": "^5.1.1",
"isomorphic-fetch": "^2.2.1",
"lodash": "^4.17.4",
"require-from-string": "^2.0.1",
"solc": "^0.4.18",
"web3": "^0.20.0",
"web3-eth-abi": "^1.0.0-beta.24",

1
packages/deployer/solc_bin/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
*.js

View File

@ -13,7 +13,7 @@ const DEFAULT_OPTIMIZER_ENABLED = false;
const DEFAULT_CONTRACTS_DIR = path.resolve('src/contracts');
const DEFAULT_ARTIFACTS_DIR = path.resolve('src/artifacts');
const DEFAULT_NETWORK_ID = 50;
const DEFAULT_JSONRPC_PORT = 8545;
const DEFAULT_JSONRPC_URL = 'http://localhost:8545';
const DEFAULT_GAS_PRICE = (10 ** 9 * 2).toString();
const DEFAULT_CONTRACTS_LIST = '*';
@ -31,43 +31,12 @@ async function onCompileCommand(argv: CliOptions): Promise<void> {
};
await commands.compileAsync(opts);
}
/**
* Compiles all contracts and runs migration script with options passed in through CLI.
* Uses network ID of running node.
* @param argv Instance of process.argv provided by yargs.
*/
async function onMigrateCommand(argv: CliOptions): Promise<void> {
const url = `http://localhost:${argv.jsonrpcPort}`;
const web3Provider = new Web3.providers.HttpProvider(url);
const web3Wrapper = new Web3Wrapper(web3Provider);
const networkId = await web3Wrapper.getNetworkIdAsync();
const compilerOpts: CompilerOptions = {
contractsDir: argv.contractsDir,
networkId,
optimizerEnabled: argv.shouldOptimize ? 1 : 0,
artifactsDir: argv.artifactsDir,
specifiedContracts: getContractsSetFromList(argv.contracts),
};
await commands.compileAsync(compilerOpts);
const defaults = {
gasPrice: new BigNumber(argv.gasPrice),
from: argv.account,
};
const deployerOpts = {
artifactsDir: argv.artifactsDir,
jsonrpcPort: argv.jsonrpcPort,
networkId,
defaults,
};
await commands.migrateAsync(deployerOpts);
}
/**
* Deploys a single contract with provided name and args.
* @param argv Instance of process.argv provided by yargs.
*/
async function onDeployCommand(argv: CliOptions): Promise<void> {
const url = `http://localhost:${argv.jsonrpcPort}`;
const url = argv.jsonrpcUrl;
const web3Provider = new Web3.providers.HttpProvider(url);
const web3Wrapper = new Web3Wrapper(web3Provider);
const networkId = await web3Wrapper.getNetworkIdAsync();
@ -86,7 +55,7 @@ async function onDeployCommand(argv: CliOptions): Promise<void> {
};
const deployerOpts: DeployerOptions = {
artifactsDir: argv.artifactsDir,
jsonrpcPort: argv.jsonrpcPort,
jsonrpcUrl: argv.jsonrpcUrl,
networkId,
defaults,
};
@ -151,10 +120,10 @@ function deployCommandBuilder(yargsInstance: any) {
default: DEFAULT_ARTIFACTS_DIR,
description: 'path to write contracts artifacts to',
})
.option('jsonrpc-port', {
type: 'number',
default: DEFAULT_JSONRPC_PORT,
description: 'port connected to JSON RPC',
.option('jsonrpc-url', {
type: 'string',
default: DEFAULT_JSONRPC_URL,
description: 'url of JSON RPC',
})
.option('gas-price', {
type: 'string',
@ -171,12 +140,6 @@ function deployCommandBuilder(yargsInstance: any) {
description: 'comma separated list of contracts to compile',
})
.command('compile', 'compile contracts', identityCommandBuilder, onCompileCommand)
.command(
'migrate',
'compile and deploy contracts using migration scripts',
identityCommandBuilder,
onMigrateCommand,
)
.command('deploy', 'deploy a single contract with provided arguments', deployCommandBuilder, onDeployCommand)
.help().argv;
})();

View File

@ -1,6 +1,5 @@
import { Compiler } from './compiler';
import { Deployer } from './deployer';
import { migrator } from './migrations/migrate';
import { CompilerOptions, DeployerOptions } from './utils/types';
export const commands = {
@ -8,10 +7,6 @@ export const commands = {
const compiler = new Compiler(opts);
await compiler.compileAllAsync();
},
async migrateAsync(opts: DeployerOptions): Promise<void> {
const deployer = new Deployer(opts);
await migrator.runMigrationsAsync(deployer);
},
async deployAsync(contractName: string, args: any[], opts: DeployerOptions): Promise<void> {
const deployer = new Deployer(opts);
await deployer.deployAndSaveAsync(contractName, args);

View File

@ -1,6 +1,10 @@
import { promisify } from '@0xproject/utils';
import * as ethUtil from 'ethereumjs-util';
import * as fs from 'fs';
import 'isomorphic-fetch';
import * as _ from 'lodash';
import * as path from 'path';
import * as requireFromString from 'require-from-string';
import solc = require('solc');
import * as Web3 from 'web3';
@ -15,7 +19,6 @@ import {
ContractSourceData,
ContractSources,
ContractSpecificSourceData,
ImportContents,
} from './utils/types';
import { utils } from './utils/utils';
@ -186,9 +189,22 @@ export class Compiler {
}
const fullSolcVersion = binPaths[contractSpecificSourceData.solcVersion];
const solcBinPath = `./solc/solc_bin/${fullSolcVersion}`;
const solcBin = require(solcBinPath);
const solcInstance = solc.setupMethods(solcBin);
const compilerBinFilename = path.join(__dirname, '../../solc_bin', fullSolcVersion);
let solcjs: string;
const isCompilerAvailableLocally = fs.existsSync(compilerBinFilename);
if (isCompilerAvailableLocally) {
solcjs = fs.readFileSync(compilerBinFilename).toString();
} else {
utils.consoleLog(`Downloading ${fullSolcVersion}...`);
const url = `${constants.BASE_COMPILER_URL}${fullSolcVersion}`;
const response = await fetch(url);
if (response.status !== 200) {
throw new Error(`Failed to load ${fullSolcVersion}`);
}
solcjs = await response.text();
fs.writeFileSync(compilerBinFilename, solcjs);
}
const solcInstance = solc.setupMethods(requireFromString(solcjs, compilerBinFilename));
utils.consoleLog(`Compiling ${fileName}...`);
const source = this._contractSources[fileName];
@ -210,11 +226,14 @@ export class Compiler {
this._solcErrors.add(normalizedErrMsg);
});
}
const contractName = path.basename(fileName, constants.SOLIDITY_FILE_EXTENSION);
const contractIdentifier = `${fileName}:${contractName}`;
const abi: Web3.ContractAbi = JSON.parse(compiled.contracts[contractIdentifier].interface);
const unlinked_binary = `0x${compiled.contracts[contractIdentifier].bytecode}`;
const bytecode = `0x${compiled.contracts[contractIdentifier].bytecode}`;
const runtimeBytecode = `0x${compiled.contracts[contractIdentifier].runtimeBytecode}`;
const sourceMap = compiled.contracts[contractIdentifier].srcmap;
const sourceMapRuntime = compiled.contracts[contractIdentifier].srcmapRuntime;
const sources = _.keys(compiled.sources);
const updated_at = Date.now();
const contractNetworkData: ContractNetworkData = {
solc_version: contractSpecificSourceData.solcVersion,
@ -222,8 +241,12 @@ export class Compiler {
source_tree_hash: sourceTreeHash,
optimizer_enabled: this._optimizerEnabled,
abi,
unlinked_binary,
bytecode,
runtime_bytecode: runtimeBytecode,
updated_at,
source_map: sourceMap,
source_map_runtime: sourceMapRuntime,
sources,
};
let newArtifact: ContractArtifact;
@ -284,13 +307,13 @@ export class Compiler {
* @param importPath Path to an imported dependency.
* @return Import contents object containing source code of dependency.
*/
private _findImportsIfSourcesExist(importPath: string): ImportContents {
private _findImportsIfSourcesExist(importPath: string): solc.ImportContents {
const fileName = path.basename(importPath);
const source = this._contractSources[fileName];
if (_.isUndefined(source)) {
throw new Error(`Contract source not found for ${fileName}`);
}
const importContents: ImportContents = {
const importContents: solc.ImportContents = {
contents: source,
};
return importContents;

View File

@ -6,7 +6,13 @@ import * as Web3 from 'web3';
import { Contract } from './utils/contract';
import { encoder } from './utils/encoder';
import { fsWrapper } from './utils/fs_wrapper';
import { ContractArtifact, ContractNetworkData, DeployerOptions } from './utils/types';
import {
ContractArtifact,
ContractNetworkData,
DeployerOptions,
ProviderDeployerOptions,
UrlDeployerOptions,
} from './utils/types';
import { utils } from './utils/utils';
// Gas added to gas estimate to make sure there is sufficient gas for deployment.
@ -15,17 +21,23 @@ const EXTRA_GAS = 200000;
export class Deployer {
public web3Wrapper: Web3Wrapper;
private _artifactsDir: string;
private _jsonrpcPort: number;
private _networkId: number;
private _defaults: Partial<TxData>;
constructor(opts: DeployerOptions) {
this._artifactsDir = opts.artifactsDir;
this._jsonrpcPort = opts.jsonrpcPort;
this._networkId = opts.networkId;
const jsonrpcUrl = `http://localhost:${this._jsonrpcPort}`;
const web3Provider = new Web3.providers.HttpProvider(jsonrpcUrl);
this._defaults = opts.defaults;
let web3Provider: Web3.Provider;
if (_.isUndefined((opts as ProviderDeployerOptions).web3Provider)) {
const jsonrpcUrl = (opts as UrlDeployerOptions).jsonrpcUrl;
if (_.isUndefined(jsonrpcUrl)) {
throw new Error(`Deployer options don't contain web3Provider nor jsonrpcUrl. Please pass one of them`);
}
web3Provider = new Web3.providers.HttpProvider(jsonrpcUrl);
} else {
web3Provider = (opts as ProviderDeployerOptions).web3Provider;
}
this.web3Wrapper = new Web3Wrapper(web3Provider, this._defaults);
}
/**
@ -39,7 +51,7 @@ export class Deployer {
const contractNetworkDataIfExists: ContractNetworkData = this._getContractNetworkDataFromArtifactIfExists(
contractArtifactIfExists,
);
const data = contractNetworkDataIfExists.unlinked_binary;
const data = contractNetworkDataIfExists.bytecode;
const from = await this._getFromAddressAsync();
const gas = await this._getAllowableGasEstimateAsync(data);
const txData = {

View File

@ -1,12 +1,45 @@
// tslint:disable:completed-docs
declare module 'solc' {
// tslint:disable:completed-docs
export function compile(sources: any, optimizerEnabled: number, findImports: (importPath: string) => any): any;
export function setupMethods(solcBin: any): any;
// tslint:enable:completed-docs
import * as Web3 from 'web3';
export interface ContractCompilationResult {
srcmap: string;
srcmapRuntime: string;
bytecode: string;
runtimeBytecode: string;
interface: string;
}
export interface CompilationResult {
errors: string[];
contracts: {
[contractIdentifier: string]: ContractCompilationResult;
};
sources: {
[sourceName: string]: {
AST: any;
};
};
sourceList: string[];
}
export interface ImportContents {
contents: string;
}
export interface InputSources {
sources: {
[fileName: string]: string;
};
}
export interface SolcInstance {
compile(
sources: InputSources,
optimizerEnabled: number,
findImports: (importPath: string) => ImportContents,
): CompilationResult;
}
export function loadRemoteVersion(versionName: string, cb: (err: Error | null, res?: SolcInstance) => void): void;
export function setupMethods(solcBin: any): SolcInstance;
}
declare module 'web3-eth-abi' {
// tslint:disable-next-line:completed-docs
export function encodeParameters(typesArray: string[], parameters: any[]): string;
}

View File

@ -1 +1,2 @@
export { Deployer } from './deployer';
export { Compiler } from './compiler';

View File

@ -1,91 +0,0 @@
import { BigNumber } from '@0xproject/utils';
import { Web3Wrapper } from '@0xproject/web3-wrapper';
import * as _ from 'lodash';
import { Deployer } from '../deployer';
import { constants } from '../utils/constants';
import { tokenInfo } from './config/token_info';
export const migrator = {
/**
* Custom migrations should be defined in this function. This will be called with the CLI 'migrate' command.
* Some operations might be completed in parallel, but we don't do that on purpose.
* That way the addresses are deterministic.
* @param deployer Deployer instance.
*/
async runMigrationsAsync(deployer: Deployer): Promise<void> {
const web3Wrapper: Web3Wrapper = deployer.web3Wrapper;
const accounts: string[] = await web3Wrapper.getAvailableAddressesAsync();
const tokenTransferProxy = await deployer.deployAndSaveAsync('TokenTransferProxy');
const zrxToken = await deployer.deployAndSaveAsync('ZRXToken');
const etherToken = await deployer.deployAndSaveAsync('WETH9');
const tokenReg = await deployer.deployAndSaveAsync('TokenRegistry');
const exchangeArgs = [zrxToken.address, tokenTransferProxy.address];
const owners = [accounts[0], accounts[1]];
const confirmationsRequired = new BigNumber(2);
const secondsRequired = new BigNumber(0);
const multiSigArgs = [owners, confirmationsRequired, secondsRequired, tokenTransferProxy.address];
const exchange = await deployer.deployAndSaveAsync('Exchange', exchangeArgs);
const multiSig = await deployer.deployAndSaveAsync(
'MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress',
multiSigArgs,
);
const owner = accounts[0];
await tokenTransferProxy.addAuthorizedAddress.sendTransactionAsync(exchange.address, { from: owner });
await tokenTransferProxy.transferOwnership.sendTransactionAsync(multiSig.address, { from: owner });
const addTokenGasEstimate = await tokenReg.addToken.estimateGasAsync(
zrxToken.address,
tokenInfo[0].name,
tokenInfo[0].symbol,
tokenInfo[0].decimals,
tokenInfo[0].ipfsHash,
tokenInfo[0].swarmHash,
{ from: owner },
);
await tokenReg.addToken.sendTransactionAsync(
zrxToken.address,
'0x Protocol Token',
'ZRX',
18,
constants.NULL_BYTES,
constants.NULL_BYTES,
{
from: owner,
gas: addTokenGasEstimate,
},
);
await tokenReg.addToken.sendTransactionAsync(
etherToken.address,
'Ether Token',
'WETH',
18,
constants.NULL_BYTES,
constants.NULL_BYTES,
{
from: owner,
gas: addTokenGasEstimate,
},
);
for (const token of tokenInfo) {
const totalSupply = new BigNumber(0);
const args = [token.name, token.symbol, token.decimals, totalSupply];
const dummyToken = await deployer.deployAsync('DummyToken', args);
await tokenReg.addToken.sendTransactionAsync(
dummyToken.address,
token.name,
token.symbol,
token.decimals,
token.ipfsHash,
token.swarmHash,
{
from: owner,
gas: addTokenGasEstimate,
},
);
}
},
};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,4 @@
export const constants = {
NULL_BYTES: '0x',
SOLIDITY_FILE_EXTENSION: '.sol',
BASE_COMPILER_URL: 'https://ethereum.github.io/solc-bin/bin/',
};

View File

@ -24,10 +24,14 @@ export interface ContractNetworkData {
keccak256: string;
source_tree_hash: string;
abi: Web3.ContractAbi;
unlinked_binary: string;
bytecode: string;
runtime_bytecode: string;
address?: string;
constructor_args?: string;
updated_at: number;
source_map: string;
source_map_runtime: string;
sources: string[];
}
export interface SolcErrors {
@ -37,7 +41,7 @@ export interface SolcErrors {
export interface CliOptions extends yargs.Arguments {
artifactsDir: string;
contractsDir: string;
jsonrpcPort: number;
jsonrpcUrl: string;
networkId: number;
shouldOptimize: boolean;
gasPrice: string;
@ -54,13 +58,22 @@ export interface CompilerOptions {
specifiedContracts: Set<string>;
}
export interface DeployerOptions {
export interface BaseDeployerOptions {
artifactsDir: string;
jsonrpcPort: number;
networkId: number;
defaults: Partial<TxData>;
}
export interface ProviderDeployerOptions extends BaseDeployerOptions {
web3Provider: Web3.Provider;
}
export interface UrlDeployerOptions extends BaseDeployerOptions {
jsonrpcUrl: string;
}
export type DeployerOptions = UrlDeployerOptions | ProviderDeployerOptions;
export interface ContractSources {
[key: string]: string;
}
@ -76,10 +89,6 @@ export interface ContractSpecificSourceData {
sourceTreeHashIfExists?: Buffer;
}
export interface ImportContents {
contents: string;
}
// TODO: Consolidate with 0x.js definitions once types are moved into a separate package.
export enum ZeroExError {
ContractDoesNotExist = 'CONTRACT_DOES_NOT_EXIST',

View File

@ -24,7 +24,7 @@ const compiler = new Compiler(compilerOpts);
const deployerOpts = {
artifactsDir,
networkId: constants.networkId,
jsonrpcPort: constants.jsonrpcPort,
jsonrpcUrl: constants.jsonrpcUrl,
defaults: {
gasPrice: constants.gasPrice,
},
@ -53,7 +53,7 @@ describe('#Compiler', () => {
const exchangeArtifact: ContractArtifact = JSON.parse(exchangeArtifactString);
const exchangeContractData: ContractNetworkData = exchangeArtifact.networks[constants.networkId];
// The last 43 bytes of the binaries are metadata which may not be equivalent
const unlinkedBinaryWithoutMetadata = exchangeContractData.unlinked_binary.slice(0, -86);
const unlinkedBinaryWithoutMetadata = exchangeContractData.bytecode.slice(0, -86);
const exchangeBinaryWithoutMetadata = exchange_binary.slice(0, -86);
expect(unlinkedBinaryWithoutMetadata).to.equal(exchangeBinaryWithoutMetadata);
});

View File

@ -2,7 +2,7 @@ import { BigNumber } from '@0xproject/utils';
export const constants = {
networkId: 0,
jsonrpcPort: 8545,
jsonrpcUrl: 'http://localhost:8545',
optimizerEnabled: 0,
gasPrice: new BigNumber(20000000000),
timeoutMs: 20000,

View File

@ -1,5 +1,11 @@
# CHANGELOG
## v0.3.0 - _TBD, 2018_
* Add coverage subprovider if SOLIDITY_COVERAGE env variable is true (#426)
* Refactor `BlockchainLifecycle` to work with in-process ganache (#426)
* Remove `RPC` class and move it's logic to `Web3Wrapper` (#426)
## v0.2.0 - _February 16, 2018_
* Remove subproviders (#392)

View File

@ -2,6 +2,17 @@
Dev utils to be shared across 0x projects and packages
## Configuration
Some env variables might be set to change the behaviour of created web3 providers/instances.
```
VERBOSE_GANACHE: boolean. Enables verbose Ganache logging. Every request/response payload. Slightly slower, but useful for testing.
SOLIDITY_COVERAGE: boolean. If set - adds coverage subprovider which intercepts all calls/transactions and can be later used to compute code coverage.
```
Boolean env variables should be either `true` or `false`. Defaults to `false` if not set.
## Install
```bash

View File

@ -40,6 +40,7 @@
"typescript": "2.7.1"
},
"dependencies": {
"@0xproject/sol-cov": "^0.0.1",
"@0xproject/subproviders": "^0.7.0",
"@0xproject/types": "^0.3.1",
"@0xproject/utils": "^0.4.1",

View File

@ -1,21 +1,22 @@
import { RPC } from './rpc';
import { Web3Wrapper } from '@0xproject/web3-wrapper';
import * as Web3 from 'web3';
export class BlockchainLifecycle {
private _rpc: RPC;
private _web3Wrapper: Web3Wrapper;
private _snapshotIdsStack: number[];
constructor() {
this._rpc = new RPC();
constructor(web3Wrapper: Web3Wrapper) {
this._web3Wrapper = web3Wrapper;
this._snapshotIdsStack = [];
}
// TODO: In order to run these tests on an actual node, we should check if we are running against
// TestRPC, if so, use snapshots, otherwise re-deploy contracts before every test
public async startAsync(): Promise<void> {
const snapshotId = await this._rpc.takeSnapshotAsync();
const snapshotId = await this._web3Wrapper.takeSnapshotAsync();
this._snapshotIdsStack.push(snapshotId);
}
public async revertAsync(): Promise<void> {
const snapshotId = this._snapshotIdsStack.pop() as number;
const didRevert = await this._rpc.revertSnapshotAsync(snapshotId);
const didRevert = await this._web3Wrapper.revertSnapshotAsync(snapshotId);
if (!didRevert) {
throw new Error(`Snapshot with id #${snapshotId} failed to revert`);
}

View File

@ -1,5 +1,6 @@
export const constants = {
RPC_URL: 'http://localhost:8545',
RPC_PORT: 8545,
GAS_ESTIMATE: 1000000,
GAS_ESTIMATE: 5000000,
TESTRPC_FIRST_ADDRESS: '0x5409ed021d9299bf6814279a6a1411a7e866a631',
};

View File

@ -0,0 +1,22 @@
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 = './src/artifacts';
const contractsPath = './src/contracts';
const networkId = 50;
const defaultFromAddress = constants.TESTRPC_FIRST_ADDRESS;
return new CoverageSubprovider(artifactsPath, contractsPath, networkId, defaultFromAddress);
},
};

View File

@ -0,0 +1,24 @@
import * as _ from 'lodash';
import * as process from 'process';
export enum EnvVars {
SolidityCoverage = 'SOLIDITY_COVERAGE',
VerboseGanache = 'VERBOSE_GANACHE',
}
export const env = {
parseBoolean(key: string): boolean {
let isTrue: boolean;
const envVarValue = process.env[key];
if (envVarValue === 'true') {
isTrue = true;
} else if (envVarValue === 'false' || _.isUndefined(envVarValue)) {
isTrue = false;
} else {
throw new Error(
`Failed to parse ENV variable ${key} as boolean. Please make sure it's either true or false. Defaults to false`,
);
}
return isTrue;
},
};

View File

@ -1,4 +1,5 @@
export { RPC } from './rpc';
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';

View File

@ -1,62 +0,0 @@
import * as ethUtil from 'ethereumjs-util';
import * as request from 'request-promise-native';
import { constants } from './constants';
export class RPC {
private _url: string;
private _id: number;
constructor() {
this._url = constants.RPC_URL;
this._id = 0;
}
public async takeSnapshotAsync(): Promise<number> {
const method = 'evm_snapshot';
const params: any[] = [];
const payload = this._toPayload(method, params);
const snapshotIdHex = await this._sendAsync(payload);
const snapshotId = ethUtil.bufferToInt(ethUtil.toBuffer(snapshotIdHex));
return snapshotId;
}
public async revertSnapshotAsync(snapshotId: number): Promise<boolean> {
const method = 'evm_revert';
const params = [snapshotId];
const payload = this._toPayload(method, params);
const didRevert = await this._sendAsync(payload);
return didRevert;
}
public async increaseTimeAsync(time: number) {
const method = 'evm_increaseTime';
const params = [time];
const payload = this._toPayload(method, params);
return this._sendAsync(payload);
}
public async mineBlockAsync(): Promise<void> {
const method = 'evm_mine';
const params: any[] = [];
const payload = this._toPayload(method, params);
await this._sendAsync(payload);
}
private _toPayload(method: string, params: any[] = []): string {
const payload = JSON.stringify({
id: this._id,
method,
params,
});
this._id += 1;
return payload;
}
private async _sendAsync(payload: string): Promise<any> {
const opts = {
method: 'POST',
uri: this._url,
body: payload,
headers: {
'content-type': 'application/json',
},
};
const bodyString = await request(opts);
const body = JSON.parse(bodyString);
return body.result;
}
}

View File

@ -6,9 +6,14 @@
import ProviderEngine = require('web3-provider-engine');
import RpcSubprovider = require('web3-provider-engine/subproviders/rpc');
import { EmptyWalletSubprovider, FakeGasEstimateSubprovider } from '@0xproject/subproviders';
import { EmptyWalletSubprovider, FakeGasEstimateSubprovider, GanacheSubprovider } from '@0xproject/subproviders';
import * as fs from 'fs';
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.
@ -17,24 +22,52 @@ import { constants } from './constants';
// tslint:disable-next-line:ordered-imports
import * as Web3 from 'web3';
export interface Web3Config {
hasAddresses?: boolean; // default: true
shouldUseInProcessGanache?: boolean; // default: false
}
export const web3Factory = {
create(hasAddresses: boolean = true): Web3 {
const provider = this.getRpcProvider(hasAddresses);
create(config: Web3Config = {}): Web3 {
const provider = this.getRpcProvider(config);
const web3 = new Web3();
web3.setProvider(provider);
return web3;
},
getRpcProvider(hasAddresses: boolean = true): Web3.Provider {
getRpcProvider(config: Web3Config = {}): Web3.Provider {
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());
}
provider.addProvider(new FakeGasEstimateSubprovider(constants.GAS_ESTIMATE));
provider.addProvider(
new RpcSubprovider({
rpcUrl: constants.RPC_URL,
}),
);
const logger = {
log: (arg: any) => {
fs.appendFileSync('ganache.log', `${arg}\n`);
},
};
const shouldUseInProcessGanache = !!config.shouldUseInProcessGanache;
if (shouldUseInProcessGanache) {
provider.addProvider(
new GanacheSubprovider({
logger,
verbose: env.parseBoolean(EnvVars.SolidityCoverage),
port: 8545,
networkId: 50,
mnemonic: 'concert load couple harbor equip island argue ramp clarify fence smart topic',
}),
);
} else {
provider.addProvider(
new RpcSubprovider({
rpcUrl: constants.RPC_URL,
}),
);
}
provider.start();
return provider;
},

View File

@ -3,20 +3,19 @@ import { Web3Wrapper } from '@0xproject/web3-wrapper';
import * as chai from 'chai';
import 'mocha';
import { BlockchainLifecycle, RPC, web3Factory } from '../src';
import { BlockchainLifecycle, web3Factory } from '../src';
const expect = chai.expect;
describe('BlockchainLifecycle tests', () => {
const web3 = web3Factory.create();
const web3Wrapper = new Web3Wrapper(web3.currentProvider);
const rpc = new RPC();
const blockchainLifecycle = new BlockchainLifecycle();
const web3Provider = web3Factory.getRpcProvider();
const web3Wrapper = new Web3Wrapper(web3Provider);
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
describe('#startAsync/revertAsync', () => {
it('reverts changes in between', async () => {
const blockNumberBefore = await web3Wrapper.getBlockNumberAsync();
await blockchainLifecycle.startAsync();
await rpc.mineBlockAsync();
await web3Wrapper.mineBlockAsync();
const blockNumberAfter = await web3Wrapper.getBlockNumberAsync();
expect(blockNumberAfter).to.be.equal(blockNumberBefore + 1);
await blockchainLifecycle.revertAsync();

View File

@ -3,18 +3,17 @@ import { Web3Wrapper } from '@0xproject/web3-wrapper';
import * as chai from 'chai';
import 'mocha';
import { RPC, web3Factory } from '../src';
import { web3Factory } from '../src';
const expect = chai.expect;
describe('RPC tests', () => {
const web3 = web3Factory.create();
const web3Wrapper = new Web3Wrapper(web3.currentProvider);
const rpc = new RPC();
const web3Provider = web3Factory.getRpcProvider();
const web3Wrapper = new Web3Wrapper(web3Provider);
describe('#mineBlockAsync', () => {
it('increases block number when called', async () => {
const blockNumberBefore = await web3Wrapper.getBlockNumberAsync();
await rpc.mineBlockAsync();
await web3Wrapper.mineBlockAsync();
const blockNumberAfter = await web3Wrapper.getBlockNumberAsync();
expect(blockNumberAfter).to.be.equal(blockNumberBefore + 1);
});
@ -23,8 +22,8 @@ describe('RPC tests', () => {
it('increases time when called', async () => {
const TIME_DELTA = 1000;
const blockTimestampBefore = await web3Wrapper.getBlockTimestampAsync(BlockParamLiteral.Latest);
await rpc.increaseTimeAsync(TIME_DELTA);
await rpc.mineBlockAsync();
await web3Wrapper.increaseTimeAsync(TIME_DELTA);
await web3Wrapper.mineBlockAsync();
const blockTimestampAfter = await web3Wrapper.getBlockTimestampAsync(BlockParamLiteral.Latest);
expect(blockTimestampAfter).to.be.at.least(blockTimestampBefore + TIME_DELTA);
});
@ -32,9 +31,9 @@ describe('RPC tests', () => {
describe('#takeSnapshotAsync/revertSnapshotAsync', () => {
it('reverts changes in between', async () => {
const blockNumberBefore = await web3Wrapper.getBlockNumberAsync();
const snapshotId = await rpc.takeSnapshotAsync();
await rpc.mineBlockAsync();
await rpc.revertSnapshotAsync(snapshotId);
const snapshotId = await web3Wrapper.takeSnapshotAsync();
await web3Wrapper.mineBlockAsync();
await web3Wrapper.revertSnapshotAsync(snapshotId);
const blockNumberAfter = await web3Wrapper.getBlockNumberAsync();
expect(blockNumberAfter).to.be.equal(blockNumberBefore);
});

View File

@ -1,3 +1,6 @@
{
"extends": ["@0xproject/tslint-config"]
"extends": ["@0xproject/tslint-config"],
"rules": {
"completed-docs": false
}
}

View File

@ -0,0 +1,5 @@
.*
yarn-error.log
/src/
/scripts/
tsconfig.json

View File

@ -0,0 +1 @@
# CHANGELOG

View File

@ -0,0 +1,64 @@
## @0xproject/sol-cov
A Solidity code coverage tool.
## Installation
```bash
yarn add -D @0xproject/sol-cov
```
## Usage
Sol-cov uses transaction traces in order to figure out which lines of Solidity source code have been covered by your tests. In order for it to gather these traces, you must add the `CoverageSubprovider` to the [ProviderEngine](https://github.com/MetaMask/provider-engine) instance you use when running your Solidity tests. If you're unfamiliar with ProviderEngine, please read the [Web3 Provider explained](https://0xproject.com/wiki#Web3-Provider-Explained) wiki article.
The CoverageSubprovider eavesdrops on the `eth_sendTransaction` and `eth_call` RPC calls and collects traces after each call using `debug_traceTransaction`. `eth_call`'s' don't generate traces - so we take a snapshot, re-submit it as a transaction, get the trace and then revert the snapshot.
```typescript
import { CoverageSubprovider } from '@0xproject/sol-cov';
const provider = new ProviderEngine();
const artifactsPath = 'src/artifacts';
const contractsPath = 'src/contracts';
const networkId = 50;
// Some calls might not have `from` address specified. Nevertheless - transactions need to be submitted from an address with at least some funds. defaultFromAddress is the address that will be used to submit those calls as transactions from.
const defaultFromAddress = '0x5409ed021d9299bf6814279a6a1411a7e866a631';
const coverageSubprovider = new CoverageSubprovider(artifactsPath, contractsPath, networkId, defaultFromAddress);
provider.addProvider(coverageSubprovider);
```
After your test suite is complete (e.g global `after` hook), you'll need to call:
```typescript
await coverageSubprovider.writeCoverageAsync();
```
This will create a `coverage.json` file in the `coverage` directory. This file has an [Istanbul format](https://github.com/gotwarlost/istanbul/blob/master/coverage.json.md) - so you can use any of the existing Instanbul reporters.
## Contributing
We strongly encourage the community to help us make improvements. To report bugs within this package, please create an issue in this repository.
Please read our [contribution guidelines](../../CONTRIBUTING.md) before getting started.
### Install Dependencies
If you don't have yarn workspaces enabled (Yarn < v1.0) - enable them:
```bash
yarn config set workspaces-experimental true
```
Then install dependencies
```bash
yarn install
```
### Lint
```bash
yarn lint
```

View File

@ -0,0 +1,45 @@
{
"name": "@0xproject/sol-cov",
"version": "0.0.1",
"description": "Generate coverage reports for Solidity code",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"scripts": {
"build:watch": "tsc -w",
"lint": "tslint --project . 'src/**/*.ts'",
"clean": "shx rm -rf lib",
"build": "tsc"
},
"repository": {
"type": "git",
"url": "https://github.com/0xProject/0x.js.git"
},
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/0xProject/0x.js/issues"
},
"homepage": "https://github.com/0xProject/0x.js/packages/sol-cov/README.md",
"dependencies": {
"@0xproject/subproviders": "^0.7.0",
"@0xproject/utils": "^0.3.4",
"ethereumjs-util": "^5.1.1",
"glob": "^7.1.2",
"istanbul": "^0.4.5",
"lodash": "^4.17.4",
"semaphore-async-await": "^1.5.1",
"solidity-coverage": "^0.4.10",
"solidity-parser-antlr": "^0.2.7",
"solidity-parser-sc": "^0.4.4",
"web3": "^0.20.0"
},
"devDependencies": {
"@0xproject/tslint-config": "^0.4.9",
"@types/istanbul": "^0.4.29",
"@types/node": "^8.0.53",
"npm-run-all": "^4.1.2",
"shx": "^0.2.2",
"tslint": "5.8.0",
"typescript": "2.7.1",
"web3-typescript-typings": "^0.9.11"
}
}

View File

@ -0,0 +1,5 @@
const postpublish_utils = require('../../../scripts/postpublish_utils');
const packageJSON = require('../package.json');
const subPackageName = packageJSON.name;
postpublish_utils.standardPostPublishAsync(subPackageName);

View File

@ -0,0 +1,133 @@
import * as _ from 'lodash';
import * as Parser from 'solidity-parser-antlr';
import { BranchMap, FnMap, LocationByOffset, SingleFileSourceRange, StatementMap } from './types';
export interface CoverageEntriesDescription {
fnMap: FnMap;
branchMap: BranchMap;
statementMap: StatementMap;
modifiersStatementIds: number[];
}
enum BranchType {
If = 'if',
ConditionalExpression = 'cond-expr',
BinaryExpression = 'binary-expr',
}
export class ASTVisitor {
private _entryId = 0;
private _fnMap: FnMap = {};
private _branchMap: BranchMap = {};
private _modifiersStatementIds: number[] = [];
private _statementMap: StatementMap = {};
private _locationByOffset: LocationByOffset;
constructor(locationByOffset: LocationByOffset) {
this._locationByOffset = locationByOffset;
}
public getCollectedCoverageEntries(): CoverageEntriesDescription {
const coverageEntriesDescription = {
fnMap: this._fnMap,
branchMap: this._branchMap,
statementMap: this._statementMap,
modifiersStatementIds: this._modifiersStatementIds,
};
return coverageEntriesDescription;
}
public IfStatement(ast: Parser.IfStatement): void {
this._visitStatement(ast);
this._visitBinaryBranch(ast, ast.trueBody, ast.falseBody || ast, BranchType.If);
}
public FunctionDefinition(ast: Parser.FunctionDefinition): void {
this._visitFunctionLikeDefinition(ast);
}
public ModifierDefinition(ast: Parser.ModifierDefinition): void {
this._visitFunctionLikeDefinition(ast);
}
public ForStatement(ast: Parser.ForStatement): void {
this._visitStatement(ast);
}
public ReturnStatement(ast: Parser.ReturnStatement): void {
this._visitStatement(ast);
}
public BreakStatement(ast: Parser.BreakStatement): void {
this._visitStatement(ast);
}
public ContinueStatement(ast: Parser.ContinueStatement): void {
this._visitStatement(ast);
}
public VariableDeclarationStatement(ast: Parser.VariableDeclarationStatement): void {
this._visitStatement(ast);
}
public Statement(ast: Parser.Statement): void {
this._visitStatement(ast);
}
public WhileStatement(ast: Parser.WhileStatement): void {
this._visitStatement(ast);
}
public SimpleStatement(ast: Parser.SimpleStatement): void {
this._visitStatement(ast);
}
public ThrowStatement(ast: Parser.ThrowStatement): void {
this._visitStatement(ast);
}
public DoWhileStatement(ast: Parser.DoWhileStatement): void {
this._visitStatement(ast);
}
public ExpressionStatement(ast: Parser.ExpressionStatement): void {
this._visitStatement(ast.expression);
}
public InlineAssemblyStatement(ast: Parser.InlineAssemblyStatement): void {
this._visitStatement(ast);
}
public BinaryOperation(ast: Parser.BinaryOperation): void {
const BRANCHING_BIN_OPS = ['&&', '||'];
if (_.includes(BRANCHING_BIN_OPS, ast.operator)) {
this._visitBinaryBranch(ast, ast.left, ast.right, BranchType.BinaryExpression);
}
}
public Conditional(ast: Parser.Conditional): void {
this._visitBinaryBranch(ast, ast.trueExpression, ast.falseExpression, BranchType.ConditionalExpression);
}
public ModifierInvocation(ast: Parser.ModifierInvocation): void {
const BUILTIN_MODIFIERS = ['public', 'view', 'payable', 'external', 'internal', 'pure', 'constant'];
if (!_.includes(BUILTIN_MODIFIERS, ast.name)) {
this._modifiersStatementIds.push(this._entryId);
this._visitStatement(ast);
}
}
private _visitBinaryBranch(
ast: Parser.ASTNode,
left: Parser.ASTNode,
right: Parser.ASTNode,
type: BranchType,
): void {
this._branchMap[this._entryId++] = {
line: this._getExpressionRange(ast).start.line,
type,
locations: [this._getExpressionRange(left), this._getExpressionRange(right)],
};
}
private _visitStatement(ast: Parser.ASTNode): void {
this._statementMap[this._entryId++] = this._getExpressionRange(ast);
}
private _getExpressionRange(ast: Parser.ASTNode): SingleFileSourceRange {
const start = this._locationByOffset[ast.range[0] - 1];
const end = this._locationByOffset[ast.range[1]];
const range = {
start,
end,
};
return range;
}
private _visitFunctionLikeDefinition(ast: Parser.ModifierDefinition | Parser.FunctionDefinition): void {
const loc = this._getExpressionRange(ast);
this._fnMap[this._entryId++] = {
name: ast.name,
line: loc.start.line,
loc,
};
this._visitStatement(ast);
}
}

View File

@ -0,0 +1,44 @@
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, networkId: number) => {
const sourcesGlob = `${sourcesPath}/**/*.sol`;
const sourceFileNames = glob.sync(sourcesGlob, { absolute: true });
const contractsDataIfExists: Array<ContractData | {}> = _.map(sourceFileNames, sourceFileName => {
const baseName = path.basename(sourceFileName, '.sol');
const artifactFileName = path.join(artifactsPath, `${baseName}.json`);
if (!fs.existsSync(artifactFileName)) {
// If the contract isn't directly compiled, but is imported as the part of the other contract - we don't
// have an artifact for it and therefore can't do anything useful with it
return {};
}
const artifact = JSON.parse(fs.readFileSync(artifactFileName).toString());
const sources = _.map(artifact.networks[networkId].sources, source => {
const includedFileName = glob.sync(`${sourcesPath}/**/${source}`, { absolute: true })[0];
return includedFileName;
});
const sourceCodes = _.map(sources, source => {
const includedSourceCode = fs.readFileSync(source).toString();
return includedSourceCode;
});
if (_.isUndefined(artifact.networks[networkId])) {
throw new Error(`No ${baseName} artifacts found for networkId ${networkId}`);
}
const contractData = {
baseName,
sourceCodes,
sources,
sourceMap: artifact.networks[networkId].source_map,
sourceMapRuntime: artifact.networks[networkId].source_map_runtime,
runtimeBytecode: artifact.networks[networkId].runtime_bytecode,
bytecode: artifact.networks[networkId].bytecode,
};
return contractData;
});
const contractsData = _.filter(contractsDataIfExists, contractData => !_.isEmpty(contractData)) as ContractData[];
return contractsData;
};

View File

@ -0,0 +1,24 @@
import * as ethUtil from 'ethereumjs-util';
import * as fs from 'fs';
import * as _ from 'lodash';
import * as path from 'path';
import * as parser from 'solidity-parser-antlr';
import { ASTVisitor, CoverageEntriesDescription } from './ast_visitor';
import { getLocationByOffset } from './source_maps';
// Parsing source code for each transaction/code is slow and therefore we cache it
const coverageEntriesBySourceHash: { [sourceHash: string]: CoverageEntriesDescription } = {};
export const collectCoverageEntries = (contractSource: string, fileName: string) => {
const sourceHash = ethUtil.sha3(contractSource).toString('hex');
if (_.isUndefined(coverageEntriesBySourceHash[sourceHash])) {
const ast = parser.parse(contractSource, { range: true });
const locationByOffset = getLocationByOffset(contractSource);
const visitor = new ASTVisitor(locationByOffset);
parser.visit(ast, visitor);
coverageEntriesBySourceHash[sourceHash] = visitor.getCollectedCoverageEntries();
}
const coverageEntriesDescription = coverageEntriesBySourceHash[sourceHash];
return coverageEntriesDescription;
};

View File

@ -0,0 +1,3 @@
export const constants = {
NEW_CONTRACT: 'NEW_CONTRACT',
};

View File

@ -0,0 +1,186 @@
import * as fs from 'fs';
import { Collector } from 'istanbul';
import * as _ from 'lodash';
import * as path from 'path';
import { collectContractsData } from './collect_contract_data';
import { collectCoverageEntries } from './collect_coverage_entries';
import { constants } from './constants';
import { parseSourceMap } from './source_maps';
import {
BranchCoverage,
BranchDescription,
BranchMap,
ContractData,
Coverage,
FnMap,
FunctionCoverage,
FunctionDescription,
LineColumn,
SingleFileSourceRange,
SourceRange,
StatementCoverage,
StatementDescription,
StatementMap,
TraceInfo,
TraceInfoExistingContract,
TraceInfoNewContract,
} from './types';
import { utils } from './utils';
export class CoverageManager {
private _traceInfos: TraceInfo[] = [];
private _contractsData: ContractData[] = [];
private _getContractCodeAsync: (address: string) => Promise<string>;
private static _getSingleFileCoverageForTrace(
contractData: ContractData,
coveredPcs: number[],
pcToSourceRange: { [programCounter: number]: SourceRange },
fileIndex: number,
): Coverage {
const fileName = contractData.sources[fileIndex];
const coverageEntriesDescription = collectCoverageEntries(contractData.sourceCodes[fileIndex], fileName);
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);
const branchCoverage: BranchCoverage = {};
const branchIds = _.keys(coverageEntriesDescription.branchMap);
for (const branchId of branchIds) {
const branchDescription = coverageEntriesDescription.branchMap[branchId];
const isCoveredByBranchIndex = _.map(branchDescription.locations, location =>
_.some(sourceRanges, range => utils.isRangeInside(range.location, location)),
);
branchCoverage[branchId] = isCoveredByBranchIndex;
}
const statementCoverage: StatementCoverage = {};
const statementIds = _.keys(coverageEntriesDescription.statementMap);
for (const statementId of statementIds) {
const statementDescription = coverageEntriesDescription.statementMap[statementId];
const isCovered = _.some(sourceRanges, range => utils.isRangeInside(range.location, statementDescription));
statementCoverage[statementId] = isCovered;
}
const functionCoverage: FunctionCoverage = {};
const functionIds = _.keys(coverageEntriesDescription.fnMap);
for (const fnId of functionIds) {
const functionDescription = coverageEntriesDescription.fnMap[fnId];
const isCovered = _.some(sourceRanges, range =>
utils.isRangeInside(range.location, functionDescription.loc),
);
functionCoverage[fnId] = isCovered;
}
// HACK: Solidity doesn't emit any opcodes that map back to modifiers with no args, that's why we map back to the
// function range and check if there is any covered statement within that range.
for (const modifierStatementId of coverageEntriesDescription.modifiersStatementIds) {
if (statementCoverage[modifierStatementId]) {
// Already detected as covered
continue;
}
const modifierDescription = coverageEntriesDescription.statementMap[modifierStatementId];
const enclosingFunction = _.find(coverageEntriesDescription.fnMap, functionDescription =>
utils.isRangeInside(modifierDescription, functionDescription.loc),
) as FunctionDescription;
const isModifierCovered = _.some(
coverageEntriesDescription.statementMap,
(statementDescription: StatementDescription, statementId: number) => {
const isInsideTheModifierEnclosingFunction = utils.isRangeInside(
statementDescription,
enclosingFunction.loc,
);
const isCovered = statementCoverage[statementId];
return isInsideTheModifierEnclosingFunction && isCovered;
},
);
statementCoverage[modifierStatementId] = isModifierCovered;
}
const partialCoverage = {
[contractData.sources[fileIndex]]: {
...coverageEntriesDescription,
l: {}, // It's able to derive it from statement coverage
path: fileName,
f: functionCoverage,
s: statementCoverage,
b: branchCoverage,
},
};
return partialCoverage;
}
constructor(
artifactsPath: string,
sourcesPath: string,
networkId: number,
getContractCodeAsync: (address: string) => Promise<string>,
) {
this._getContractCodeAsync = getContractCodeAsync;
this._contractsData = collectContractsData(artifactsPath, sourcesPath, networkId);
}
public appendTraceInfo(traceInfo: TraceInfo): void {
this._traceInfos.push(traceInfo);
}
public async writeCoverageAsync(): Promise<void> {
const finalCoverage = await this._computeCoverageAsync();
const jsonReplacer: null = null;
const numberOfJsonSpaces = 4;
const stringifiedCoverage = JSON.stringify(finalCoverage, jsonReplacer, numberOfJsonSpaces);
fs.writeFileSync('coverage/coverage.json', stringifiedCoverage);
}
private async _computeCoverageAsync(): Promise<Coverage> {
const collector = new Collector();
for (const traceInfo of this._traceInfos) {
if (traceInfo.address !== constants.NEW_CONTRACT) {
// Runtime transaction
const runtimeBytecode = (traceInfo as TraceInfoExistingContract).runtimeBytecode;
const contractData = _.find(this._contractsData, { runtimeBytecode }) as ContractData;
if (_.isUndefined(contractData)) {
throw new Error(`Transaction to an unknown address: ${traceInfo.address}`);
}
const bytecodeHex = contractData.runtimeBytecode.slice(2);
const sourceMap = contractData.sourceMapRuntime;
const pcToSourceRange = parseSourceMap(
contractData.sourceCodes,
sourceMap,
bytecodeHex,
contractData.sources,
);
for (let fileIndex = 0; fileIndex < contractData.sources.length; fileIndex++) {
const singleFileCoverageForTrace = CoverageManager._getSingleFileCoverageForTrace(
contractData,
traceInfo.coveredPcs,
pcToSourceRange,
fileIndex,
);
collector.add(singleFileCoverageForTrace);
}
} else {
// Contract creation transaction
const bytecode = (traceInfo as TraceInfoNewContract).bytecode;
const contractData = _.find(this._contractsData, contractDataCandidate =>
bytecode.startsWith(contractDataCandidate.bytecode),
) as ContractData;
if (_.isUndefined(contractData)) {
throw new Error(`Unknown contract creation transaction`);
}
const bytecodeHex = contractData.bytecode.slice(2);
const sourceMap = contractData.sourceMap;
const pcToSourceRange = parseSourceMap(
contractData.sourceCodes,
sourceMap,
bytecodeHex,
contractData.sources,
);
for (let fileIndex = 0; fileIndex < contractData.sources.length; fileIndex++) {
const singleFileCoverageForTrace = CoverageManager._getSingleFileCoverageForTrace(
contractData,
traceInfo.coveredPcs,
pcToSourceRange,
fileIndex,
);
collector.add(singleFileCoverageForTrace);
}
}
}
// TODO: Remove any cast as soon as https://github.com/DefinitelyTyped/DefinitelyTyped/pull/24233 gets merged
return (collector as any).getFinalCoverage();
}
}

View File

@ -0,0 +1,168 @@
import { Callback, NextCallback, Subprovider } from '@0xproject/subproviders';
import { promisify } from '@0xproject/utils';
import * as _ from 'lodash';
import { Lock } from 'semaphore-async-await';
import * as Web3 from 'web3';
import { constants } from './constants';
import { CoverageManager } from './coverage_manager';
import { TraceInfoExistingContract, TraceInfoNewContract } from './types';
interface MaybeFakeTxData extends Web3.TxData {
isFakeTransaction?: boolean;
}
/*
* This class implements the web3-provider-engine subprovider interface and collects traces of all transactions that were sent and all calls that were executed.
* Because there is no notion of call trace in the rpc - we collect them in rather non-obvious/hacky way.
* On each call - we create a snapshot, execute the call as a transaction, get the trace, revert the snapshot.
* That allows us to not influence the test behaviour.
* Source: https://github.com/MetaMask/provider-engine/blob/master/subproviders/subprovider.js
*/
export class CoverageSubprovider extends Subprovider {
// Lock is used to not accept normal transactions while doing call/snapshot magic because they'll be reverted later otherwise
private _lock: Lock;
private _coverageManager: CoverageManager;
private _defaultFromAddress: string;
constructor(artifactsPath: string, sourcesPath: string, networkId: number, defaultFromAddress: string) {
super();
this._lock = new Lock();
this._defaultFromAddress = defaultFromAddress;
this._coverageManager = new CoverageManager(
artifactsPath,
sourcesPath,
networkId,
this._getContractCodeAsync.bind(this),
);
}
public handleRequest(
payload: Web3.JSONRPCRequestPayload,
next: NextCallback,
end: (err: Error | null, result: any) => void,
) {
switch (payload.method) {
case 'eth_sendTransaction':
const txData = payload.params[0];
next(this._onTransactionSentAsync.bind(this, txData));
return;
case 'eth_call':
const callData = payload.params[0];
const blockNumber = payload.params[1];
next(this._onCallExecutedAsync.bind(this, callData, blockNumber));
return;
default:
next();
return;
}
}
public async writeCoverageAsync(): Promise<void> {
await this._coverageManager.writeCoverageAsync();
}
private async _onTransactionSentAsync(
txData: MaybeFakeTxData,
err: Error | null,
txHash: string | undefined,
cb: Callback,
): Promise<void> {
if (!txData.isFakeTransaction) {
// This transaction is a usual ttransaction. Not a call executed as one.
// And we don't want it to be executed within a snapshotting period
await this._lock.acquire();
}
if (_.isNull(err)) {
const toAddress = _.isUndefined(txData.to) || txData.to === '0x0' ? constants.NEW_CONTRACT : txData.to;
await this._recordTxTraceAsync(toAddress, txData.data, txHash as string);
} else {
const payload = {
method: 'eth_getBlockByNumber',
params: ['latest', true],
};
const jsonRPCResponsePayload = await this.emitPayloadAsync(payload);
const transactions = jsonRPCResponsePayload.result.transactions;
for (const transaction of transactions) {
const toAddress = _.isUndefined(txData.to) || txData.to === '0x0' ? constants.NEW_CONTRACT : txData.to;
await this._recordTxTraceAsync(toAddress, transaction.data, transaction.hash);
}
}
if (!txData.isFakeTransaction) {
// This transaction is a usual ttransaction. Not a call executed as one.
// And we don't want it to be executed within a snapshotting period
this._lock.release();
}
cb();
}
private async _onCallExecutedAsync(
callData: Partial<Web3.CallData>,
blockNumber: Web3.BlockParam,
err: Error | null,
callResult: string,
cb: Callback,
): Promise<void> {
await this._recordCallTraceAsync(callData, blockNumber);
cb();
}
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
};
const jsonRPCResponsePayload = await this.emitPayloadAsync(payload);
const trace: Web3.TransactionTrace = jsonRPCResponsePayload.result;
const coveredPcs = _.map(trace.structLogs, log => log.pc);
if (address === constants.NEW_CONTRACT) {
const traceInfo: TraceInfoNewContract = {
coveredPcs,
txHash,
address: address as 'NEW_CONTRACT',
bytecode: data as string,
};
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);
}
}
private async _recordCallTraceAsync(callData: Partial<Web3.CallData>, blockNumber: Web3.BlockParam): Promise<void> {
// We don't want other transactions to be exeucted during snashotting period, that's why we lock the
// transaction execution for all transactions except our fake ones.
await this._lock.acquire();
const snapshotId = Number((await this.emitPayloadAsync({ method: 'evm_snapshot' })).result);
const fakeTxData: MaybeFakeTxData = {
isFakeTransaction: true, // This transaction (and only it) is allowed to come through when the lock is locked
...callData,
from: callData.from || this._defaultFromAddress,
};
try {
await this.emitPayloadAsync({
method: 'eth_sendTransaction',
params: [fakeTxData],
});
} catch (err) {
// Even if this transaction failed - we've already recorded it's trace.
}
const jsonRPCResponse = await this.emitPayloadAsync({ method: 'evm_revert', params: [snapshotId] });
this._lock.release();
const didRevert = jsonRPCResponse.result;
if (!didRevert) {
throw new Error('Failed to revert the snapshot');
}
}
private async _getContractCodeAsync(address: string): Promise<string> {
const payload = {
method: 'eth_getCode',
params: [address, 'latest'],
};
const jsonRPCResponsePayload = await this.emitPayloadAsync(payload);
const contractCode: string = jsonRPCResponsePayload.result;
return contractCode;
}
}

315
packages/sol-cov/src/globals.d.ts vendored Normal file
View File

@ -0,0 +1,315 @@
// tslint:disable:completed-docs
declare module 'solidity-parser-antlr' {
export interface BaseASTNode {
range: [number, number];
}
export interface SourceUnit extends BaseASTNode {}
export interface PragmaDirective extends BaseASTNode {}
export interface PragmaName extends BaseASTNode {}
export interface PragmaValue extends BaseASTNode {}
export interface Version extends BaseASTNode {}
export interface VersionOperator extends BaseASTNode {}
export interface VersionConstraint extends BaseASTNode {}
export interface ImportDeclaration extends BaseASTNode {}
export interface ImportDirective extends BaseASTNode {}
export interface ContractDefinition extends BaseASTNode {}
export interface InheritanceSpecifier extends BaseASTNode {}
export interface ContractPart extends BaseASTNode {}
export interface StateVariableDeclaration extends BaseASTNode {
variables: VariableDeclaration[];
}
export interface UsingForDeclaration extends BaseASTNode {}
export interface StructDefinition extends BaseASTNode {}
export interface ModifierDefinition extends BaseASTNode {
name: string;
}
export interface ModifierInvocation extends BaseASTNode {
name: string;
}
export interface FunctionDefinition extends BaseASTNode {
name: string;
}
export interface ReturnParameters extends BaseASTNode {}
export interface ModifierList extends BaseASTNode {}
export interface EventDefinition extends BaseASTNode {}
export interface EnumValue extends BaseASTNode {}
export interface EnumDefinition extends BaseASTNode {}
export interface ParameterList extends BaseASTNode {}
export interface Parameter extends BaseASTNode {}
export interface EventParameterList extends BaseASTNode {}
export interface EventParameter extends BaseASTNode {}
export interface FunctionTypeParameterList extends BaseASTNode {}
export interface FunctionTypeParameter extends BaseASTNode {}
export interface VariableDeclaration extends BaseASTNode {
visibility: 'public' | 'private';
isStateVar: boolean;
}
export interface TypeName extends BaseASTNode {}
export interface UserDefinedTypeName extends BaseASTNode {}
export interface Mapping extends BaseASTNode {}
export interface FunctionTypeName extends BaseASTNode {}
export interface StorageLocation extends BaseASTNode {}
export interface StateMutability extends BaseASTNode {}
export interface Block extends BaseASTNode {}
export interface Statement extends BaseASTNode {}
export interface ExpressionStatement extends BaseASTNode {
expression: ASTNode;
}
export interface IfStatement extends BaseASTNode {
trueBody: ASTNode;
falseBody: ASTNode;
}
export interface WhileStatement extends BaseASTNode {}
export interface SimpleStatement extends BaseASTNode {}
export interface ForStatement extends BaseASTNode {}
export interface InlineAssemblyStatement extends BaseASTNode {}
export interface DoWhileStatement extends BaseASTNode {}
export interface ContinueStatement extends BaseASTNode {}
export interface BreakStatement extends BaseASTNode {}
export interface ReturnStatement extends BaseASTNode {}
export interface ThrowStatement extends BaseASTNode {}
export interface VariableDeclarationStatement extends BaseASTNode {}
export interface IdentifierList extends BaseASTNode {}
export interface ElementaryTypeName extends BaseASTNode {}
export interface Expression extends BaseASTNode {}
export interface PrimaryExpression extends BaseASTNode {}
export interface ExpressionList extends BaseASTNode {}
export interface NameValueList extends BaseASTNode {}
export interface NameValue extends BaseASTNode {}
export interface FunctionCallArguments extends BaseASTNode {}
export interface AssemblyBlock extends BaseASTNode {}
export interface AssemblyItem extends BaseASTNode {}
export interface AssemblyExpression extends BaseASTNode {}
export interface AssemblyCall extends BaseASTNode {}
export interface AssemblyLocalDefinition extends BaseASTNode {}
export interface AssemblyAssignment extends BaseASTNode {}
export interface AssemblyIdentifierOrList extends BaseASTNode {}
export interface AssemblyIdentifierList extends BaseASTNode {}
export interface AssemblyStackAssignment extends BaseASTNode {}
export interface LabelDefinition extends BaseASTNode {}
export interface AssemblySwitch extends BaseASTNode {}
export interface AssemblyCase extends BaseASTNode {}
export interface AssemblyFunctionDefinition extends BaseASTNode {}
export interface AssemblyFunctionReturns extends BaseASTNode {}
export interface AssemblyFor extends BaseASTNode {}
export interface AssemblyIf extends BaseASTNode {}
export interface AssemblyLiteral extends BaseASTNode {}
export interface SubAssembly extends BaseASTNode {}
export interface TupleExpression extends BaseASTNode {}
export interface ElementaryTypeNameExpression extends BaseASTNode {}
export interface NumberLiteral extends BaseASTNode {}
export interface Identifier extends BaseASTNode {}
export type BinOp =
| '+'
| '-'
| '*'
| '/'
| '**'
| '%'
| '<<'
| '>>'
| '&&'
| '||'
| '&'
| '|'
| '^'
| '<'
| '>'
| '<='
| '>='
| '=='
| '!='
| '='
| '|='
| '^='
| '&='
| '<<='
| '>>='
| '+='
| '-='
| '*='
| '/='
| '%=';
export interface BinaryOperation extends BaseASTNode {
left: ASTNode;
right: ASTNode;
operator: BinOp;
}
export interface Conditional extends BaseASTNode {
trueExpression: ASTNode;
falseExpression: ASTNode;
}
export type ASTNode =
| SourceUnit
| PragmaDirective
| PragmaName
| PragmaValue
| Version
| VersionOperator
| VersionConstraint
| ImportDeclaration
| ImportDirective
| ContractDefinition
| InheritanceSpecifier
| ContractPart
| StateVariableDeclaration
| UsingForDeclaration
| StructDefinition
| ModifierDefinition
| ModifierInvocation
| FunctionDefinition
| ReturnParameters
| ModifierList
| EventDefinition
| EnumValue
| EnumDefinition
| ParameterList
| Parameter
| EventParameterList
| EventParameter
| FunctionTypeParameterList
| FunctionTypeParameter
| VariableDeclaration
| TypeName
| UserDefinedTypeName
| Mapping
| FunctionTypeName
| StorageLocation
| StateMutability
| Block
| Statement
| ExpressionStatement
| IfStatement
| WhileStatement
| SimpleStatement
| ForStatement
| InlineAssemblyStatement
| DoWhileStatement
| ContinueStatement
| BreakStatement
| ReturnStatement
| ThrowStatement
| VariableDeclarationStatement
| IdentifierList
| ElementaryTypeName
| Expression
| PrimaryExpression
| ExpressionList
| NameValueList
| NameValue
| FunctionCallArguments
| AssemblyBlock
| AssemblyItem
| AssemblyExpression
| AssemblyCall
| AssemblyLocalDefinition
| AssemblyAssignment
| AssemblyIdentifierOrList
| AssemblyIdentifierList
| AssemblyStackAssignment
| LabelDefinition
| AssemblySwitch
| AssemblyCase
| AssemblyFunctionDefinition
| AssemblyFunctionReturns
| AssemblyFor
| AssemblyIf
| AssemblyLiteral
| SubAssembly
| TupleExpression
| ElementaryTypeNameExpression
| NumberLiteral
| Identifier
| BinaryOperation
| Conditional;
export interface Visitor {
SourceUnit?: (node: SourceUnit) => void;
PragmaDirective?: (node: PragmaDirective) => void;
PragmaName?: (node: PragmaName) => void;
PragmaValue?: (node: PragmaValue) => void;
Version?: (node: Version) => void;
VersionOperator?: (node: VersionOperator) => void;
VersionConstraint?: (node: VersionConstraint) => void;
ImportDeclaration?: (node: ImportDeclaration) => void;
ImportDirective?: (node: ImportDirective) => void;
ContractDefinition?: (node: ContractDefinition) => void;
InheritanceSpecifier?: (node: InheritanceSpecifier) => void;
ContractPart?: (node: ContractPart) => void;
StateVariableDeclaration?: (node: StateVariableDeclaration) => void;
UsingForDeclaration?: (node: UsingForDeclaration) => void;
StructDefinition?: (node: StructDefinition) => void;
ModifierDefinition?: (node: ModifierDefinition) => void;
ModifierInvocation?: (node: ModifierInvocation) => void;
FunctionDefinition?: (node: FunctionDefinition) => void;
ReturnParameters?: (node: ReturnParameters) => void;
ModifierList?: (node: ModifierList) => void;
EventDefinition?: (node: EventDefinition) => void;
EnumValue?: (node: EnumValue) => void;
EnumDefinition?: (node: EnumDefinition) => void;
ParameterList?: (node: ParameterList) => void;
Parameter?: (node: Parameter) => void;
EventParameterList?: (node: EventParameterList) => void;
EventParameter?: (node: EventParameter) => void;
FunctionTypeParameterList?: (node: FunctionTypeParameterList) => void;
FunctionTypeParameter?: (node: FunctionTypeParameter) => void;
VariableDeclaration?: (node: VariableDeclaration) => void;
TypeName?: (node: TypeName) => void;
UserDefinedTypeName?: (node: UserDefinedTypeName) => void;
Mapping?: (node: Mapping) => void;
FunctionTypeName?: (node: FunctionTypeName) => void;
StorageLocation?: (node: StorageLocation) => void;
StateMutability?: (node: StateMutability) => void;
Block?: (node: Block) => void;
Statement?: (node: Statement) => void;
ExpressionStatement?: (node: ExpressionStatement) => void;
IfStatement?: (node: IfStatement) => void;
WhileStatement?: (node: WhileStatement) => void;
SimpleStatement?: (node: SimpleStatement) => void;
ForStatement?: (node: ForStatement) => void;
InlineAssemblyStatement?: (node: InlineAssemblyStatement) => void;
DoWhileStatement?: (node: DoWhileStatement) => void;
ContinueStatement?: (node: ContinueStatement) => void;
BreakStatement?: (node: BreakStatement) => void;
ReturnStatement?: (node: ReturnStatement) => void;
ThrowStatement?: (node: ThrowStatement) => void;
VariableDeclarationStatement?: (node: VariableDeclarationStatement) => void;
IdentifierList?: (node: IdentifierList) => void;
ElementaryTypeName?: (node: ElementaryTypeName) => void;
Expression?: (node: Expression) => void;
PrimaryExpression?: (node: PrimaryExpression) => void;
ExpressionList?: (node: ExpressionList) => void;
NameValueList?: (node: NameValueList) => void;
NameValue?: (node: NameValue) => void;
FunctionCallArguments?: (node: FunctionCallArguments) => void;
AssemblyBlock?: (node: AssemblyBlock) => void;
AssemblyItem?: (node: AssemblyItem) => void;
AssemblyExpression?: (node: AssemblyExpression) => void;
AssemblyCall?: (node: AssemblyCall) => void;
AssemblyLocalDefinition?: (node: AssemblyLocalDefinition) => void;
AssemblyAssignment?: (node: AssemblyAssignment) => void;
AssemblyIdentifierOrList?: (node: AssemblyIdentifierOrList) => void;
AssemblyIdentifierList?: (node: AssemblyIdentifierList) => void;
AssemblyStackAssignment?: (node: AssemblyStackAssignment) => void;
LabelDefinition?: (node: LabelDefinition) => void;
AssemblySwitch?: (node: AssemblySwitch) => void;
AssemblyCase?: (node: AssemblyCase) => void;
AssemblyFunctionDefinition?: (node: AssemblyFunctionDefinition) => void;
AssemblyFunctionReturns?: (node: AssemblyFunctionReturns) => void;
AssemblyFor?: (node: AssemblyFor) => void;
AssemblyIf?: (node: AssemblyIf) => void;
AssemblyLiteral?: (node: AssemblyLiteral) => void;
SubAssembly?: (node: SubAssembly) => void;
TupleExpression?: (node: TupleExpression) => void;
ElementaryTypeNameExpression?: (node: ElementaryTypeNameExpression) => void;
NumberLiteral?: (node: NumberLiteral) => void;
Identifier?: (node: Identifier) => void;
BinaryOperation?: (node: BinaryOperation) => void;
Conditional?: (node: Conditional) => void;
}
export interface ParserOpts {
range?: boolean;
}
export function parse(sourceCode: string, parserOpts: ParserOpts): ASTNode;
export function visit(ast: ASTNode, visitor: Visitor): void;
}

View File

@ -0,0 +1 @@
export { CoverageSubprovider } from './coverage_subprovider';

View File

@ -0,0 +1,24 @@
// tslint:disable:number-literal-format
const PUSH1 = 0x60;
const PUSH32 = 0x7f;
const isPush = (inst: number) => inst >= PUSH1 && inst <= PUSH32;
const pushDataLength = (inst: number) => inst - PUSH1 + 1;
const instructionLength = (inst: number) => (isPush(inst) ? pushDataLength(inst) + 1 : 1);
export const getPcToInstructionIndexMapping = (bytecode: Uint8Array) => {
const result: {
[programCounter: number]: number;
} = {};
let byteIndex = 0;
let instructionIndex = 0;
while (byteIndex < bytecode.length) {
const instruction = bytecode[byteIndex];
const length = instructionLength(instruction);
result[byteIndex] = instructionIndex;
byteIndex += length;
instructionIndex += 1;
}
return result;
};

View File

@ -0,0 +1,82 @@
import * as _ from 'lodash';
import { getPcToInstructionIndexMapping } from './instructions';
import { LineColumn, LocationByOffset, SourceRange } from './types';
const RADIX = 10;
export interface SourceLocation {
offset: number;
length: number;
fileIndex: number;
}
export function getLocationByOffset(str: string): LocationByOffset {
const locationByOffset: LocationByOffset = {};
let currentOffset = 0;
for (const char of str.split('')) {
const location = locationByOffset[currentOffset - 1] || { line: 1, column: 0 };
const isNewline = char === '\n';
locationByOffset[currentOffset] = {
line: location.line + (isNewline ? 1 : 0),
column: isNewline ? 0 : location.column + 1,
};
currentOffset++;
}
return locationByOffset;
}
// Parses a sourcemap string
// The solidity sourcemap format is documented here: https://github.com/ethereum/solidity/blob/develop/docs/miscellaneous.rst#source-mappings
export function parseSourceMap(
sourceCodes: string[],
srcMap: string,
bytecodeHex: string,
sources: string[],
): { [programCounter: number]: SourceRange } {
const bytecode = Uint8Array.from(Buffer.from(bytecodeHex, 'hex'));
const pcToInstructionIndex: { [programCounter: number]: number } = getPcToInstructionIndexMapping(bytecode);
const locationByOffsetByFileIndex = _.map(sourceCodes, getLocationByOffset);
const entries = srcMap.split(';');
const parsedEntries: SourceLocation[] = [];
let lastParsedEntry: SourceLocation = {} as any;
const instructionIndexToSourceRange: { [instructionIndex: number]: SourceRange } = {};
_.each(entries, (entry: string, i: number) => {
const [instructionIndexStrIfExists, lengthStrIfExists, fileIndexStrIfExists, jumpTypeStrIfExists] = entry.split(
':',
);
const instructionIndexIfExists = parseInt(instructionIndexStrIfExists, RADIX);
const lengthIfExists = parseInt(lengthStrIfExists, RADIX);
const fileIndexIfExists = parseInt(fileIndexStrIfExists, RADIX);
const offset = _.isNaN(instructionIndexIfExists) ? lastParsedEntry.offset : instructionIndexIfExists;
const length = _.isNaN(lengthIfExists) ? lastParsedEntry.length : lengthIfExists;
const fileIndex = _.isNaN(fileIndexIfExists) ? lastParsedEntry.fileIndex : fileIndexIfExists;
const parsedEntry = {
offset,
length,
fileIndex,
};
if (parsedEntry.fileIndex !== -1) {
const sourceRange = {
location: {
start: locationByOffsetByFileIndex[parsedEntry.fileIndex][parsedEntry.offset - 1],
end:
locationByOffsetByFileIndex[parsedEntry.fileIndex][parsedEntry.offset + parsedEntry.length - 1],
},
fileName: sources[parsedEntry.fileIndex],
};
instructionIndexToSourceRange[i] = sourceRange;
} else {
// Some assembly code generated by Solidity can't be mapped back to a line of source code.
// Source: https://github.com/ethereum/solidity/issues/3629
}
lastParsedEntry = parsedEntry;
});
const pcsToSourceRange: { [programCounter: number]: SourceRange } = {};
for (const programCounterKey of _.keys(pcToInstructionIndex)) {
const pc = parseInt(programCounterKey, RADIX);
const instructionIndex: number = pcToInstructionIndex[pc];
pcsToSourceRange[pc] = instructionIndexToSourceRange[instructionIndex];
}
return pcsToSourceRange;
}

View File

@ -0,0 +1,101 @@
export interface LineColumn {
line: number;
column: number;
}
export interface SourceRange {
location: SingleFileSourceRange;
fileName: string;
}
export interface SingleFileSourceRange {
start: LineColumn;
end: LineColumn;
}
export interface LocationByOffset {
[offset: number]: LineColumn;
}
export interface FunctionDescription {
name: string;
line: number;
loc: SingleFileSourceRange;
skip?: boolean;
}
export type StatementDescription = SingleFileSourceRange;
export interface BranchDescription {
line: number;
type: 'if' | 'switch' | 'cond-expr' | 'binary-expr';
locations: SingleFileSourceRange[];
}
export interface FnMap {
[functionId: string]: FunctionDescription;
}
export interface BranchMap {
[branchId: string]: BranchDescription;
}
export interface StatementMap {
[statementId: string]: StatementDescription;
}
export interface LineCoverage {
[lineNo: number]: boolean;
}
export interface FunctionCoverage {
[functionId: string]: boolean;
}
export interface StatementCoverage {
[statementId: string]: boolean;
}
export interface BranchCoverage {
[branchId: string]: boolean[];
}
export interface Coverage {
[fineName: string]: {
l: LineCoverage;
f: FunctionCoverage;
s: StatementCoverage;
b: BranchCoverage;
fnMap: FnMap;
branchMap: BranchMap;
statementMap: StatementMap;
path: string;
};
}
export interface ContractData {
bytecode: string;
sourceMap: string;
runtimeBytecode: string;
sourceMapRuntime: string;
sourceCodes: string[];
baseName: string;
sources: string[];
}
export interface TraceInfoBase {
coveredPcs: number[];
txHash: string;
}
export interface TraceInfoNewContract extends TraceInfoBase {
address: 'NEW_CONTRACT';
bytecode: string;
}
export interface TraceInfoExistingContract extends TraceInfoBase {
address: string;
runtimeBytecode: string;
}
export type TraceInfo = TraceInfoNewContract | TraceInfoExistingContract;

View File

@ -0,0 +1,13 @@
import { LineColumn, SingleFileSourceRange } from './types';
export const utils = {
compareLineColumn(lhs: LineColumn, rhs: LineColumn): number {
return lhs.line !== rhs.line ? lhs.line - rhs.line : lhs.column - rhs.column;
},
isRangeInside(childRange: SingleFileSourceRange, parentRange: SingleFileSourceRange): boolean {
return (
utils.compareLineColumn(parentRange.start, childRange.start) <= 0 &&
utils.compareLineColumn(childRange.end, parentRange.end) <= 0
);
},
};

View File

@ -0,0 +1,12 @@
{
"extends": "../../tsconfig",
"compilerOptions": {
"outDir": "lib"
},
"include": [
"./src/**/*",
"../../node_modules/types-bn/index.d.ts",
"../../node_modules/web3-typescript-typings/index.d.ts",
"../../node_modules/types-ethereumjs-util/index.d.ts"
]
}

View File

@ -0,0 +1,6 @@
{
"extends": ["@0xproject/tslint-config"],
"rules": {
"completed-docs": false
}
}

View File

@ -1,5 +1,11 @@
# CHANGELOG
## v0.8.0 - _TBD, 2018_
* Export `GanacheSubprovider` and `Subprovider` (#426)
* Make all subproviders to derive from `Subprovider` (#426)
* Add types for `NextCallback`, `OnNextCompleted` (#426)
## v0.7.0 - _March 8, 2018_
* Updated legerco packages. Removed node-hid package as a dependency and make it an optional dependency. It is still used in integration tests but is causing problems for users on Linux distros. (#437)

View File

@ -27,6 +27,7 @@
"es6-promisify": "^5.0.0",
"ethereumjs-tx": "^1.3.3",
"ethereumjs-util": "^5.1.1",
"ganache-core": "0xProject/ganache-core",
"hdkey": "^0.7.1",
"lodash": "^4.17.4",
"semaphore-async-await": "^1.5.1",

View File

@ -67,16 +67,6 @@ declare module '@ledgerhq/hw-transport-node-hid' {
}
}
// Semaphore-async-await declarations
declare module 'semaphore-async-await' {
class Semaphore {
constructor(permits: number);
public wait(): Promise<void>;
public signal(): void;
}
export default Semaphore;
}
// web3-provider-engine declarations
declare module 'web3-provider-engine/subproviders/subprovider' {
class Subprovider {}
@ -141,3 +131,18 @@ declare module '*.json' {
export default json;
/* tslint:enable */
}
// ganache-core declarations
declare module 'ganache-core' {
import * as Web3 from 'web3';
export interface GanacheOpts {
verbose: boolean;
logger: {
log(msg: string): void;
};
port: number;
networkId: number;
mnemonic: string;
}
export function provider(opts: GanacheOpts): Web3.Provider;
}

Some files were not shown because too many files have changed in this diff Show More