From c6c45095a8511814db6aa33e39794ae60debad8b Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Thu, 13 Dec 2018 15:16:51 -0800 Subject: feat(asset-buyer): Custom InsufficientAssetLiquidityError error BREAKING CHANGE: A custom InsufficientAssetLiquidityError error is now raised when there is insufficient liquidity --- packages/asset-buyer/src/types.ts | 10 +++ .../asset-buyer/src/utils/buy_quote_calculator.ts | 11 +++- .../asset-buyer/test/buy_quote_calculator_test.ts | 73 +++++++++++++++++++--- packages/asset-buyer/test/utils/test_helpers.ts | 22 +++++++ 4 files changed, 107 insertions(+), 9 deletions(-) create mode 100644 packages/asset-buyer/test/utils/test_helpers.ts (limited to 'packages') diff --git a/packages/asset-buyer/src/types.ts b/packages/asset-buyer/src/types.ts index 3b573edca..dfa17a22d 100644 --- a/packages/asset-buyer/src/types.ts +++ b/packages/asset-buyer/src/types.ts @@ -117,6 +117,16 @@ export enum AssetBuyerError { TransactionValueTooLow = 'TRANSACTION_VALUE_TOO_LOW', } +export class InsufficientAssetLiquidityError extends Error { + public numAssetsAvailable: BigNumber; + constructor(numAssetsAvailable: BigNumber) { + super(AssetBuyerError.InsufficientAssetLiquidity); + this.numAssetsAvailable = numAssetsAvailable; + // Setting prototype so instanceof works. See https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#extending-built-ins-like-error-array-and-map-may-no-longer-work + Object.setPrototypeOf(this, InsufficientAssetLiquidityError.prototype); + } +} + export interface OrdersAndFillableAmounts { orders: SignedOrder[]; remainingFillableMakerAssetAmounts: BigNumber[]; diff --git a/packages/asset-buyer/src/utils/buy_quote_calculator.ts b/packages/asset-buyer/src/utils/buy_quote_calculator.ts index b15b880c2..e52c9c5bf 100644 --- a/packages/asset-buyer/src/utils/buy_quote_calculator.ts +++ b/packages/asset-buyer/src/utils/buy_quote_calculator.ts @@ -3,7 +3,13 @@ import { BigNumber } from '@0x/utils'; import * as _ from 'lodash'; import { constants } from '../constants'; -import { AssetBuyerError, BuyQuote, BuyQuoteInfo, OrdersAndFillableAmounts } from '../types'; +import { + AssetBuyerError, + BuyQuote, + BuyQuoteInfo, + InsufficientAssetLiquidityError, + OrdersAndFillableAmounts, +} from '../types'; import { orderUtils } from './order_utils'; @@ -33,7 +39,8 @@ export const buyQuoteCalculator = { }); // if we do not have enough orders to cover the desired assetBuyAmount, throw if (remainingFillAmount.gt(constants.ZERO_AMOUNT)) { - throw new Error(AssetBuyerError.InsufficientAssetLiquidity); + const amountRemaining = assetBuyAmount.minus(remainingFillAmount); + throw new InsufficientAssetLiquidityError(amountRemaining); } // if we are not buying ZRX: // given the orders calculated above, find the fee-orders that cover the desired assetBuyAmount (with slippage) diff --git a/packages/asset-buyer/test/buy_quote_calculator_test.ts b/packages/asset-buyer/test/buy_quote_calculator_test.ts index a30017b72..1dd829a0f 100644 --- a/packages/asset-buyer/test/buy_quote_calculator_test.ts +++ b/packages/asset-buyer/test/buy_quote_calculator_test.ts @@ -1,4 +1,5 @@ import { orderFactory } from '@0x/order-utils/lib/src/order_factory'; +import { SignedOrder } from '@0x/types'; import { BigNumber } from '@0x/utils'; import * as chai from 'chai'; import * as _ from 'lodash'; @@ -8,6 +9,7 @@ import { AssetBuyerError, OrdersAndFillableAmounts } from '../src/types'; import { buyQuoteCalculator } from '../src/utils/buy_quote_calculator'; import { chaiSetup } from './utils/chai_setup'; +import { testHelpers } from './utils/test_helpers'; chaiSetup.configure(); const expect = chai.expect; @@ -15,6 +17,10 @@ const expect = chai.expect; // tslint:disable:custom-no-magic-numbers describe('buyQuoteCalculator', () => { describe('#calculate', () => { + let firstOrder: SignedOrder; + let firstRemainingFillAmount: BigNumber; + let secondOrder: SignedOrder; + let secondRemainingFillAmount: BigNumber; let ordersAndFillableAmounts: OrdersAndFillableAmounts; let smallFeeOrderAndFillableAmount: OrdersAndFillableAmounts; let allFeeOrdersAndFillableAmounts: OrdersAndFillableAmounts; @@ -24,18 +30,18 @@ describe('buyQuoteCalculator', () => { // the second order has a rate of 2 makerAsset / WETH with a takerFee of 100 ZRX and has 200 / 200 makerAsset units left to fill (completely fillable) // generate one order for fees // the fee order has a rate of 1 ZRX / WETH with no taker fee and has 100 ZRX left to fill (completely fillable) - const firstOrder = orderFactory.createSignedOrderFromPartial({ + firstOrder = orderFactory.createSignedOrderFromPartial({ makerAssetAmount: new BigNumber(400), takerAssetAmount: new BigNumber(100), takerFee: new BigNumber(200), }); - const firstRemainingFillAmount = new BigNumber(200); - const secondOrder = orderFactory.createSignedOrderFromPartial({ + firstRemainingFillAmount = new BigNumber(200); + secondOrder = orderFactory.createSignedOrderFromPartial({ makerAssetAmount: new BigNumber(200), takerAssetAmount: new BigNumber(100), takerFee: new BigNumber(100), }); - const secondRemainingFillAmount = secondOrder.makerAssetAmount; + secondRemainingFillAmount = secondOrder.makerAssetAmount; ordersAndFillableAmounts = { orders: [firstOrder, secondOrder], remainingFillableMakerAssetAmounts: [firstRemainingFillAmount, secondRemainingFillAmount], @@ -61,9 +67,9 @@ describe('buyQuoteCalculator', () => { ], }; }); - it('should throw if not enough maker asset liquidity', () => { + it('should throw if not enough maker asset liquidity (multiple orders)', () => { // we have 400 makerAsset units available to fill but attempt to calculate a quote for 500 makerAsset units - expect(() => + const errorFunction = () => { buyQuoteCalculator.calculate( ordersAndFillableAmounts, smallFeeOrderAndFillableAmount, @@ -71,8 +77,61 @@ describe('buyQuoteCalculator', () => { 0, 0, false, + ); + }; + testHelpers.expectInsufficientLiquidityError(expect, errorFunction, new BigNumber(400)); + }); + it('should throw if not enough maker asset liquidity (partially filled order)', () => { + const firstOrderAndFillableAmount: OrdersAndFillableAmounts = { + orders: [firstOrder], + remainingFillableMakerAssetAmounts: [firstRemainingFillAmount], + }; + + const errorFunction = () => { + buyQuoteCalculator.calculate( + firstOrderAndFillableAmount, + smallFeeOrderAndFillableAmount, + new BigNumber(201), + 0, + 0, + false, + ); + }; + testHelpers.expectInsufficientLiquidityError(expect, errorFunction, new BigNumber(200)); + }); + it('should throw if not enough maker asset liquidity (completely fillable order)', () => { + const completelyFillableOrder = orderFactory.createSignedOrderFromPartial({ + makerAssetAmount: new BigNumber(123), + takerAssetAmount: new BigNumber(100), + takerFee: new BigNumber(200), + }); + const completelyFillableOrdersAndFillableAmount: OrdersAndFillableAmounts = { + orders: [completelyFillableOrder], + remainingFillableMakerAssetAmounts: [completelyFillableOrder.makerAssetAmount], + }; + const errorFunction = () => { + buyQuoteCalculator.calculate( + completelyFillableOrdersAndFillableAmount, + smallFeeOrderAndFillableAmount, + new BigNumber(124), + 0, + 0, + false, + ); + }; + testHelpers.expectInsufficientLiquidityError(expect, errorFunction, new BigNumber(123)); + }); + it('should not throw if order is fillable', () => { + expect(() => + buyQuoteCalculator.calculate( + ordersAndFillableAmounts, + allFeeOrdersAndFillableAmounts, + new BigNumber(300), + 0, + 0, + false, ), - ).to.throw(AssetBuyerError.InsufficientAssetLiquidity); + ).to.not.throw(); }); it('should throw if not enough ZRX liquidity', () => { // we request 300 makerAsset units but the ZRX order is only enough to fill the first order, which only has 200 makerAssetUnits available diff --git a/packages/asset-buyer/test/utils/test_helpers.ts b/packages/asset-buyer/test/utils/test_helpers.ts new file mode 100644 index 000000000..fd1313ac8 --- /dev/null +++ b/packages/asset-buyer/test/utils/test_helpers.ts @@ -0,0 +1,22 @@ +import { BigNumber } from '@0x/utils'; + +import { InsufficientAssetLiquidityError } from '../../src/types'; + +export const testHelpers = { + expectInsufficientLiquidityError: ( + expect: Chai.ExpectStatic, + functionWhichTriggersError: () => void, + expectedNumAvailable: BigNumber, + ): void => { + let errorThrown = false; + try { + functionWhichTriggersError(); + } catch (e) { + errorThrown = true; + expect(e).to.be.instanceOf(InsufficientAssetLiquidityError); + expect(e.numAssetsAvailable).to.be.bignumber.equal(expectedNumAvailable); + } + + expect(errorThrown).to.be.true(); + }, +}; -- cgit v1.2.3 From a3d93d17cdefc2258a9f08e6fc680df1fb2b8456 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Fri, 14 Dec 2018 10:23:01 -0800 Subject: Factor in slippage amount in InsufficientAssetLiquidityError error, and show in instant --- packages/asset-buyer/src/index.ts | 1 + packages/asset-buyer/src/types.ts | 6 ++--- .../asset-buyer/src/utils/buy_quote_calculator.ts | 17 +++++++++++-- .../asset-buyer/test/buy_quote_calculator_test.ts | 28 ++++++++++++++++++++++ packages/asset-buyer/test/utils/test_helpers.ts | 4 ++-- packages/instant/src/constants.ts | 1 + packages/instant/src/util/asset.ts | 17 ++++++++++++- packages/instant/src/util/buy_quote_updater.ts | 7 +++++- 8 files changed, 72 insertions(+), 9 deletions(-) (limited to 'packages') diff --git a/packages/asset-buyer/src/index.ts b/packages/asset-buyer/src/index.ts index 8418edb42..59515e24f 100644 --- a/packages/asset-buyer/src/index.ts +++ b/packages/asset-buyer/src/index.ts @@ -18,6 +18,7 @@ export { BuyQuoteExecutionOpts, BuyQuoteInfo, BuyQuoteRequestOpts, + InsufficientAssetLiquidityError, OrderProvider, OrderProviderRequest, OrderProviderResponse, diff --git a/packages/asset-buyer/src/types.ts b/packages/asset-buyer/src/types.ts index dfa17a22d..d435f337e 100644 --- a/packages/asset-buyer/src/types.ts +++ b/packages/asset-buyer/src/types.ts @@ -118,10 +118,10 @@ export enum AssetBuyerError { } export class InsufficientAssetLiquidityError extends Error { - public numAssetsAvailable: BigNumber; - constructor(numAssetsAvailable: BigNumber) { + public amountAvailableToFill: BigNumber; + constructor(amountAvailableToFill: BigNumber) { super(AssetBuyerError.InsufficientAssetLiquidity); - this.numAssetsAvailable = numAssetsAvailable; + this.amountAvailableToFill = amountAvailableToFill; // Setting prototype so instanceof works. See https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#extending-built-ins-like-error-array-and-map-may-no-longer-work Object.setPrototypeOf(this, InsufficientAssetLiquidityError.prototype); } diff --git a/packages/asset-buyer/src/utils/buy_quote_calculator.ts b/packages/asset-buyer/src/utils/buy_quote_calculator.ts index e52c9c5bf..23d3e9b24 100644 --- a/packages/asset-buyer/src/utils/buy_quote_calculator.ts +++ b/packages/asset-buyer/src/utils/buy_quote_calculator.ts @@ -1,5 +1,6 @@ import { marketUtils, SignedOrder } from '@0x/order-utils'; import { BigNumber } from '@0x/utils'; +import { Web3Wrapper } from '@0x/web3-wrapper'; import * as _ from 'lodash'; import { constants } from '../constants'; @@ -39,8 +40,20 @@ export const buyQuoteCalculator = { }); // if we do not have enough orders to cover the desired assetBuyAmount, throw if (remainingFillAmount.gt(constants.ZERO_AMOUNT)) { - const amountRemaining = assetBuyAmount.minus(remainingFillAmount); - throw new InsufficientAssetLiquidityError(amountRemaining); + // We needed the amount they requested to buy, plus the amount for slippage + const totalAmountRequested = assetBuyAmount.plus(slippageBufferAmount); + const amountUnableToFill = totalAmountRequested.minus(remainingFillAmount); + // multiplerNeededWithSlippage represents what we need to multiply the assetBuyAmount by + // in order to get the total amount needed considering slippage + // i.e. if slippagePercent was 0.2 (20%), multiplerNeededWithSlippage would be 1.2 + const multiplerNeededWithSlippage = new BigNumber(1).plus(slippagePercentage); + // Given amountAvailableToFillConsideringSlippage * multiplerNeededWithSlippage = amountUnableToFill + // We divide amountUnableToFill by multiplerNeededWithSlippage to determine amountAvailableToFillConsideringSlippage + const amountAvailableToFillConsideringSlippage = amountUnableToFill + .div(multiplerNeededWithSlippage) + .round(0, BigNumber.ROUND_DOWN); + + throw new InsufficientAssetLiquidityError(amountAvailableToFillConsideringSlippage); } // if we are not buying ZRX: // given the orders calculated above, find the fee-orders that cover the desired assetBuyAmount (with slippage) diff --git a/packages/asset-buyer/test/buy_quote_calculator_test.ts b/packages/asset-buyer/test/buy_quote_calculator_test.ts index 1dd829a0f..98458cd5b 100644 --- a/packages/asset-buyer/test/buy_quote_calculator_test.ts +++ b/packages/asset-buyer/test/buy_quote_calculator_test.ts @@ -81,6 +81,34 @@ describe('buyQuoteCalculator', () => { }; testHelpers.expectInsufficientLiquidityError(expect, errorFunction, new BigNumber(400)); }); + it('should throw if not enough maker asset liquidity (multiple orders with 20% slippage)', () => { + // we have 400 makerAsset units available to fill but attempt to calculate a quote for 500 makerAsset units + const errorFunction = () => { + buyQuoteCalculator.calculate( + ordersAndFillableAmounts, + smallFeeOrderAndFillableAmount, + new BigNumber(500), + 0, + 0.2, + false, + ); + }; + testHelpers.expectInsufficientLiquidityError(expect, errorFunction, new BigNumber(333)); + }); + it('should throw if not enough maker asset liquidity (multiple orders with 5% slippage)', () => { + // we have 400 makerAsset units available to fill but attempt to calculate a quote for 500 makerAsset units + const errorFunction = () => { + buyQuoteCalculator.calculate( + ordersAndFillableAmounts, + smallFeeOrderAndFillableAmount, + new BigNumber(600), + 0, + 0.05, + false, + ); + }; + testHelpers.expectInsufficientLiquidityError(expect, errorFunction, new BigNumber(380)); + }); it('should throw if not enough maker asset liquidity (partially filled order)', () => { const firstOrderAndFillableAmount: OrdersAndFillableAmounts = { orders: [firstOrder], diff --git a/packages/asset-buyer/test/utils/test_helpers.ts b/packages/asset-buyer/test/utils/test_helpers.ts index fd1313ac8..b99906792 100644 --- a/packages/asset-buyer/test/utils/test_helpers.ts +++ b/packages/asset-buyer/test/utils/test_helpers.ts @@ -6,7 +6,7 @@ export const testHelpers = { expectInsufficientLiquidityError: ( expect: Chai.ExpectStatic, functionWhichTriggersError: () => void, - expectedNumAvailable: BigNumber, + expectedAmountAvailableToFill: BigNumber, ): void => { let errorThrown = false; try { @@ -14,7 +14,7 @@ export const testHelpers = { } catch (e) { errorThrown = true; expect(e).to.be.instanceOf(InsufficientAssetLiquidityError); - expect(e.numAssetsAvailable).to.be.bignumber.equal(expectedNumAvailable); + expect(e.amountAvailableToFill).to.be.bignumber.equal(expectedAmountAvailableToFill); } expect(errorThrown).to.be.true(); diff --git a/packages/instant/src/constants.ts b/packages/instant/src/constants.ts index f83eb4ac7..2417c8615 100644 --- a/packages/instant/src/constants.ts +++ b/packages/instant/src/constants.ts @@ -16,6 +16,7 @@ export const ONE_SECOND_MS = 1000; export const ONE_MINUTE_MS = ONE_SECOND_MS * 60; export const GIT_SHA = process.env.GIT_SHA; export const NODE_ENV = process.env.NODE_ENV; +export const SLIPPAGE_PERCENTAGE = 0.2; export const NPM_PACKAGE_VERSION = process.env.NPM_PACKAGE_VERSION; export const ACCOUNT_UPDATE_INTERVAL_TIME_MS = ONE_SECOND_MS * 5; export const BUY_QUOTE_UPDATE_INTERVAL_TIME_MS = ONE_SECOND_MS * 15; diff --git a/packages/instant/src/util/asset.ts b/packages/instant/src/util/asset.ts index 13f84ef74..5e83e7e6d 100644 --- a/packages/instant/src/util/asset.ts +++ b/packages/instant/src/util/asset.ts @@ -1,7 +1,10 @@ -import { AssetBuyerError } from '@0x/asset-buyer'; +import { AssetBuyerError, InsufficientAssetLiquidityError } from '@0x/asset-buyer'; import { AssetProxyId, ObjectMap } from '@0x/types'; +import { BigNumber } from '@0x/utils'; +import { Web3Wrapper } from '@0x/web3-wrapper'; import * as _ from 'lodash'; +import { BIG_NUMBER_ZERO } from '../constants'; import { assetDataNetworkMapping } from '../data/asset_data_network_mapping'; import { Asset, AssetMetaData, ERC20Asset, Network, ZeroExInstantError } from '../types'; @@ -110,6 +113,18 @@ export const assetUtils = { assetBuyerErrorMessage: (asset: ERC20Asset, error: Error): string | undefined => { if (error.message === AssetBuyerError.InsufficientAssetLiquidity) { const assetName = assetUtils.bestNameForAsset(asset, 'of this asset'); + if (error instanceof InsufficientAssetLiquidityError) { + const unitAmountAvailableToFill = Web3Wrapper.toUnitAmount( + error.amountAvailableToFill, + asset.metaData.decimals, + ); + const roundedUnitAmountAvailableToFill = unitAmountAvailableToFill.round(2, BigNumber.ROUND_DOWN); + + if (roundedUnitAmountAvailableToFill.greaterThan(BIG_NUMBER_ZERO)) { + return `There are only ${roundedUnitAmountAvailableToFill} ${assetName} available to buy`; + } + } + return `Not enough ${assetName} available`; } else if (error.message === AssetBuyerError.InsufficientZrxLiquidity) { return 'Not enough ZRX available'; diff --git a/packages/instant/src/util/buy_quote_updater.ts b/packages/instant/src/util/buy_quote_updater.ts index 6191c92e3..37974e71c 100644 --- a/packages/instant/src/util/buy_quote_updater.ts +++ b/packages/instant/src/util/buy_quote_updater.ts @@ -5,6 +5,7 @@ import * as _ from 'lodash'; import { Dispatch } from 'redux'; import { oc } from 'ts-optchain'; +import { SLIPPAGE_PERCENTAGE } from '../constants'; import { Action, actions } from '../redux/actions'; import { AffiliateInfo, ERC20Asset, QuoteFetchOrigin } from '../types'; import { analytics } from '../util/analytics'; @@ -33,8 +34,12 @@ export const buyQuoteUpdater = { } const feePercentage = oc(options.affiliateInfo).feePercentage(); let newBuyQuote: BuyQuote | undefined; + const slippagePercentage = SLIPPAGE_PERCENTAGE; try { - newBuyQuote = await assetBuyer.getBuyQuoteAsync(asset.assetData, baseUnitValue, { feePercentage }); + newBuyQuote = await assetBuyer.getBuyQuoteAsync(asset.assetData, baseUnitValue, { + feePercentage, + slippagePercentage, + }); } catch (error) { const errorMessage = assetUtils.assetBuyerErrorMessage(asset, error); -- cgit v1.2.3 From c3884dfa32d0d8292163892934d7f243e813590f Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Fri, 14 Dec 2018 11:06:15 -0800 Subject: More tests for assetBuyerErrorMessage --- packages/instant/test/util/asset.test.ts | 41 +++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) (limited to 'packages') diff --git a/packages/instant/test/util/asset.test.ts b/packages/instant/test/util/asset.test.ts index fc4e4e2e4..c13424685 100644 --- a/packages/instant/test/util/asset.test.ts +++ b/packages/instant/test/util/asset.test.ts @@ -1,5 +1,6 @@ -import { AssetBuyerError } from '@0x/asset-buyer'; +import { AssetBuyerError, InsufficientAssetLiquidityError, BigNumber } from '@0x/asset-buyer'; import { AssetProxyId, ObjectMap } from '@0x/types'; +import { Web3Wrapper } from '@0x/web3-wrapper'; import { Asset, AssetMetaData, ERC20Asset, ERC20AssetMetaData, Network, ZeroExInstantError } from '../../src/types'; import { assetUtils } from '../../src/util/asset'; @@ -19,6 +20,16 @@ const ZRX_ASSET: ERC20Asset = { const META_DATA_MAP: ObjectMap = { [ZRX_ASSET_DATA]: ZRX_META_DATA, }; +const WAX_ASSET: ERC20Asset = { + assetData: '0xf47261b000000000000000000000000039bb259f66e1c59d5abef88375979b4d20d98022', + metaData: { + assetProxyId: AssetProxyId.ERC20, + decimals: 8, + primaryColor: '#EDB740', + symbol: 'wax', + name: 'WAX', + }, +}; describe('assetDataUtil', () => { describe('bestNameForAsset', () => { @@ -47,13 +58,37 @@ describe('assetDataUtil', () => { }); }); describe('assetBuyerErrorMessage', () => { - it('should return message for InsufficientAssetLiquidity', () => { + it('should return message for generic InsufficientAssetLiquidity error', () => { const insufficientAssetError = new Error(AssetBuyerError.InsufficientAssetLiquidity); expect(assetUtils.assetBuyerErrorMessage(ZRX_ASSET, insufficientAssetError)).toEqual( 'Not enough ZRX available', ); }); - it('should return message for InsufficientAssetLiquidity', () => { + it('should return custom message for InsufficientAssetLiquidityError error for token w/ 18 decimals', () => { + const amountAvailable = Web3Wrapper.toBaseUnitAmount(new BigNumber(20.059), 18); + expect( + assetUtils.assetBuyerErrorMessage(ZRX_ASSET, new InsufficientAssetLiquidityError(amountAvailable)), + ).toEqual('There are only 20.05 ZRX available to buy'); + }); + it('should return custom message for InsufficientAssetLiquidityError error for token w/ 18 decimals and small amount available', () => { + const amountAvailable = Web3Wrapper.toBaseUnitAmount(new BigNumber(0.01), 18); + expect( + assetUtils.assetBuyerErrorMessage(ZRX_ASSET, new InsufficientAssetLiquidityError(amountAvailable)), + ).toEqual('There are only 0.01 ZRX available to buy'); + }); + it('should return custom message for InsufficientAssetLiquidityError error for token w/ 8 decimals', () => { + const amountAvailable = Web3Wrapper.toBaseUnitAmount(new BigNumber(3), 8); + expect( + assetUtils.assetBuyerErrorMessage(WAX_ASSET, new InsufficientAssetLiquidityError(amountAvailable)), + ).toEqual('There are only 3 WAX available to buy'); + }); + it('should generic message for InsufficientAssetLiquidityError error when amount available rounds to zero', () => { + const amountAvailable = Web3Wrapper.toBaseUnitAmount(new BigNumber(0.002), 18); + expect( + assetUtils.assetBuyerErrorMessage(ZRX_ASSET, new InsufficientAssetLiquidityError(amountAvailable)), + ).toEqual('Not enough ZRX available'); + }); + it('should return message for InsufficientZrxLiquidity', () => { const insufficientZrxError = new Error(AssetBuyerError.InsufficientZrxLiquidity); expect(assetUtils.assetBuyerErrorMessage(ZRX_ASSET, insufficientZrxError)).toEqual( 'Not enough ZRX available', -- cgit v1.2.3 From 3e596f6a8c211eb39917cfd2a9a68a6facf2c904 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Fri, 14 Dec 2018 11:08:21 -0800 Subject: Nesting errors --- packages/instant/test/util/asset.test.ts | 48 +++++++++++++++++--------------- 1 file changed, 25 insertions(+), 23 deletions(-) (limited to 'packages') diff --git a/packages/instant/test/util/asset.test.ts b/packages/instant/test/util/asset.test.ts index c13424685..dbf2e15a5 100644 --- a/packages/instant/test/util/asset.test.ts +++ b/packages/instant/test/util/asset.test.ts @@ -64,29 +64,31 @@ describe('assetDataUtil', () => { 'Not enough ZRX available', ); }); - it('should return custom message for InsufficientAssetLiquidityError error for token w/ 18 decimals', () => { - const amountAvailable = Web3Wrapper.toBaseUnitAmount(new BigNumber(20.059), 18); - expect( - assetUtils.assetBuyerErrorMessage(ZRX_ASSET, new InsufficientAssetLiquidityError(amountAvailable)), - ).toEqual('There are only 20.05 ZRX available to buy'); - }); - it('should return custom message for InsufficientAssetLiquidityError error for token w/ 18 decimals and small amount available', () => { - const amountAvailable = Web3Wrapper.toBaseUnitAmount(new BigNumber(0.01), 18); - expect( - assetUtils.assetBuyerErrorMessage(ZRX_ASSET, new InsufficientAssetLiquidityError(amountAvailable)), - ).toEqual('There are only 0.01 ZRX available to buy'); - }); - it('should return custom message for InsufficientAssetLiquidityError error for token w/ 8 decimals', () => { - const amountAvailable = Web3Wrapper.toBaseUnitAmount(new BigNumber(3), 8); - expect( - assetUtils.assetBuyerErrorMessage(WAX_ASSET, new InsufficientAssetLiquidityError(amountAvailable)), - ).toEqual('There are only 3 WAX available to buy'); - }); - it('should generic message for InsufficientAssetLiquidityError error when amount available rounds to zero', () => { - const amountAvailable = Web3Wrapper.toBaseUnitAmount(new BigNumber(0.002), 18); - expect( - assetUtils.assetBuyerErrorMessage(ZRX_ASSET, new InsufficientAssetLiquidityError(amountAvailable)), - ).toEqual('Not enough ZRX available'); + describe('InsufficientAssetLiquidityError', () => { + it('should return custom message for token w/ 18 decimals', () => { + const amountAvailable = Web3Wrapper.toBaseUnitAmount(new BigNumber(20.059), 18); + expect( + assetUtils.assetBuyerErrorMessage(ZRX_ASSET, new InsufficientAssetLiquidityError(amountAvailable)), + ).toEqual('There are only 20.05 ZRX available to buy'); + }); + it('should return custom message for token w/ 18 decimals and small amount available', () => { + const amountAvailable = Web3Wrapper.toBaseUnitAmount(new BigNumber(0.01), 18); + expect( + assetUtils.assetBuyerErrorMessage(ZRX_ASSET, new InsufficientAssetLiquidityError(amountAvailable)), + ).toEqual('There are only 0.01 ZRX available to buy'); + }); + it('should return custom message for token w/ 8 decimals', () => { + const amountAvailable = Web3Wrapper.toBaseUnitAmount(new BigNumber(3), 8); + expect( + assetUtils.assetBuyerErrorMessage(WAX_ASSET, new InsufficientAssetLiquidityError(amountAvailable)), + ).toEqual('There are only 3 WAX available to buy'); + }); + it('should generic message when amount available rounds to zero', () => { + const amountAvailable = Web3Wrapper.toBaseUnitAmount(new BigNumber(0.002), 18); + expect( + assetUtils.assetBuyerErrorMessage(ZRX_ASSET, new InsufficientAssetLiquidityError(amountAvailable)), + ).toEqual('Not enough ZRX available'); + }); }); it('should return message for InsufficientZrxLiquidity', () => { const insufficientZrxError = new Error(AssetBuyerError.InsufficientZrxLiquidity); -- cgit v1.2.3 From 69054d85e80f9e41200015a9b0eef2a9fe00f439 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Fri, 14 Dec 2018 15:18:20 -0800 Subject: Only send in amountAvailableToFill if it's a non-zero amount, add additional tests and nest, and put error into its own file --- packages/asset-buyer/src/errors.ts | 16 ++ packages/asset-buyer/src/index.ts | 2 +- packages/asset-buyer/src/types.ts | 12 +- .../asset-buyer/src/utils/buy_quote_calculator.ts | 16 +- .../asset-buyer/test/buy_quote_calculator_test.ts | 196 ++++++++++++--------- packages/asset-buyer/test/utils/test_helpers.ts | 10 +- packages/instant/src/util/asset.ts | 6 +- 7 files changed, 154 insertions(+), 104 deletions(-) create mode 100644 packages/asset-buyer/src/errors.ts (limited to 'packages') diff --git a/packages/asset-buyer/src/errors.ts b/packages/asset-buyer/src/errors.ts new file mode 100644 index 000000000..ef05a5d0a --- /dev/null +++ b/packages/asset-buyer/src/errors.ts @@ -0,0 +1,16 @@ +import { BigNumber } from '@0x/utils'; + +import { AssetBuyerError } from './types'; + +/** + * Error class representing insufficient asset liquidity + */ +export class InsufficientAssetLiquidityError extends Error { + public amountAvailableToFill?: BigNumber; + constructor(amountAvailableToFill?: BigNumber) { + super(AssetBuyerError.InsufficientAssetLiquidity); + this.amountAvailableToFill = amountAvailableToFill; + // Setting prototype so instanceof works. See https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#extending-built-ins-like-error-array-and-map-may-no-longer-work + Object.setPrototypeOf(this, InsufficientAssetLiquidityError.prototype); + } +} diff --git a/packages/asset-buyer/src/index.ts b/packages/asset-buyer/src/index.ts index 59515e24f..a42d7e272 100644 --- a/packages/asset-buyer/src/index.ts +++ b/packages/asset-buyer/src/index.ts @@ -9,6 +9,7 @@ export { SignedOrder } from '@0x/types'; export { BigNumber } from '@0x/utils'; export { AssetBuyer } from './asset_buyer'; +export { InsufficientAssetLiquidityError } from './errors'; export { BasicOrderProvider } from './order_providers/basic_order_provider'; export { StandardRelayerAPIOrderProvider } from './order_providers/standard_relayer_api_order_provider'; export { @@ -18,7 +19,6 @@ export { BuyQuoteExecutionOpts, BuyQuoteInfo, BuyQuoteRequestOpts, - InsufficientAssetLiquidityError, OrderProvider, OrderProviderRequest, OrderProviderResponse, diff --git a/packages/asset-buyer/src/types.ts b/packages/asset-buyer/src/types.ts index d435f337e..d5d6be695 100644 --- a/packages/asset-buyer/src/types.ts +++ b/packages/asset-buyer/src/types.ts @@ -102,7 +102,7 @@ export interface AssetBuyerOpts { } /** - * Possible errors thrown by an AssetBuyer instance or associated static methods. + * Possible error messages thrown by an AssetBuyer instance or associated static methods. */ export enum AssetBuyerError { NoEtherTokenContractFound = 'NO_ETHER_TOKEN_CONTRACT_FOUND', @@ -117,16 +117,6 @@ export enum AssetBuyerError { TransactionValueTooLow = 'TRANSACTION_VALUE_TOO_LOW', } -export class InsufficientAssetLiquidityError extends Error { - public amountAvailableToFill: BigNumber; - constructor(amountAvailableToFill: BigNumber) { - super(AssetBuyerError.InsufficientAssetLiquidity); - this.amountAvailableToFill = amountAvailableToFill; - // Setting prototype so instanceof works. See https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#extending-built-ins-like-error-array-and-map-may-no-longer-work - Object.setPrototypeOf(this, InsufficientAssetLiquidityError.prototype); - } -} - export interface OrdersAndFillableAmounts { orders: SignedOrder[]; remainingFillableMakerAssetAmounts: BigNumber[]; diff --git a/packages/asset-buyer/src/utils/buy_quote_calculator.ts b/packages/asset-buyer/src/utils/buy_quote_calculator.ts index 23d3e9b24..59293d1b7 100644 --- a/packages/asset-buyer/src/utils/buy_quote_calculator.ts +++ b/packages/asset-buyer/src/utils/buy_quote_calculator.ts @@ -1,16 +1,10 @@ import { marketUtils, SignedOrder } from '@0x/order-utils'; import { BigNumber } from '@0x/utils'; -import { Web3Wrapper } from '@0x/web3-wrapper'; import * as _ from 'lodash'; import { constants } from '../constants'; -import { - AssetBuyerError, - BuyQuote, - BuyQuoteInfo, - InsufficientAssetLiquidityError, - OrdersAndFillableAmounts, -} from '../types'; +import { InsufficientAssetLiquidityError } from '../errors'; +import { AssetBuyerError, BuyQuote, BuyQuoteInfo, OrdersAndFillableAmounts } from '../types'; import { orderUtils } from './order_utils'; @@ -53,7 +47,11 @@ export const buyQuoteCalculator = { .div(multiplerNeededWithSlippage) .round(0, BigNumber.ROUND_DOWN); - throw new InsufficientAssetLiquidityError(amountAvailableToFillConsideringSlippage); + throw new InsufficientAssetLiquidityError( + amountAvailableToFillConsideringSlippage.gt(constants.ZERO_AMOUNT) + ? amountAvailableToFillConsideringSlippage + : undefined, + ); } // if we are not buying ZRX: // given the orders calculated above, find the fee-orders that cover the desired assetBuyAmount (with slippage) diff --git a/packages/asset-buyer/test/buy_quote_calculator_test.ts b/packages/asset-buyer/test/buy_quote_calculator_test.ts index 98458cd5b..fdc17ef25 100644 --- a/packages/asset-buyer/test/buy_quote_calculator_test.ts +++ b/packages/asset-buyer/test/buy_quote_calculator_test.ts @@ -67,87 +67,125 @@ describe('buyQuoteCalculator', () => { ], }; }); - it('should throw if not enough maker asset liquidity (multiple orders)', () => { - // we have 400 makerAsset units available to fill but attempt to calculate a quote for 500 makerAsset units - const errorFunction = () => { - buyQuoteCalculator.calculate( - ordersAndFillableAmounts, - smallFeeOrderAndFillableAmount, - new BigNumber(500), - 0, - 0, - false, - ); - }; - testHelpers.expectInsufficientLiquidityError(expect, errorFunction, new BigNumber(400)); - }); - it('should throw if not enough maker asset liquidity (multiple orders with 20% slippage)', () => { - // we have 400 makerAsset units available to fill but attempt to calculate a quote for 500 makerAsset units - const errorFunction = () => { - buyQuoteCalculator.calculate( - ordersAndFillableAmounts, - smallFeeOrderAndFillableAmount, - new BigNumber(500), - 0, - 0.2, - false, - ); - }; - testHelpers.expectInsufficientLiquidityError(expect, errorFunction, new BigNumber(333)); - }); - it('should throw if not enough maker asset liquidity (multiple orders with 5% slippage)', () => { - // we have 400 makerAsset units available to fill but attempt to calculate a quote for 500 makerAsset units - const errorFunction = () => { - buyQuoteCalculator.calculate( - ordersAndFillableAmounts, - smallFeeOrderAndFillableAmount, - new BigNumber(600), - 0, - 0.05, - false, - ); - }; - testHelpers.expectInsufficientLiquidityError(expect, errorFunction, new BigNumber(380)); - }); - it('should throw if not enough maker asset liquidity (partially filled order)', () => { - const firstOrderAndFillableAmount: OrdersAndFillableAmounts = { - orders: [firstOrder], - remainingFillableMakerAssetAmounts: [firstRemainingFillAmount], - }; + describe('InsufficientLiquidityError', () => { + it('should throw if not enough maker asset liquidity (multiple orders)', () => { + // we have 400 makerAsset units available to fill but attempt to calculate a quote for 500 makerAsset units + const errorFunction = () => { + buyQuoteCalculator.calculate( + ordersAndFillableAmounts, + smallFeeOrderAndFillableAmount, + new BigNumber(500), + 0, + 0, + false, + ); + }; + testHelpers.expectInsufficientLiquidityError(expect, errorFunction, new BigNumber(400)); + }); + it('should throw if not enough maker asset liquidity (multiple orders with 20% slippage)', () => { + // we have 400 makerAsset units available to fill but attempt to calculate a quote for 500 makerAsset units + const errorFunction = () => { + buyQuoteCalculator.calculate( + ordersAndFillableAmounts, + smallFeeOrderAndFillableAmount, + new BigNumber(500), + 0, + 0.2, + false, + ); + }; + testHelpers.expectInsufficientLiquidityError(expect, errorFunction, new BigNumber(333)); + }); + it('should throw if not enough maker asset liquidity (multiple orders with 5% slippage)', () => { + // we have 400 makerAsset units available to fill but attempt to calculate a quote for 500 makerAsset units + const errorFunction = () => { + buyQuoteCalculator.calculate( + ordersAndFillableAmounts, + smallFeeOrderAndFillableAmount, + new BigNumber(600), + 0, + 0.05, + false, + ); + }; + testHelpers.expectInsufficientLiquidityError(expect, errorFunction, new BigNumber(380)); + }); + it('should throw if not enough maker asset liquidity (partially filled order)', () => { + const firstOrderAndFillableAmount: OrdersAndFillableAmounts = { + orders: [firstOrder], + remainingFillableMakerAssetAmounts: [firstRemainingFillAmount], + }; - const errorFunction = () => { - buyQuoteCalculator.calculate( - firstOrderAndFillableAmount, - smallFeeOrderAndFillableAmount, - new BigNumber(201), - 0, - 0, - false, - ); - }; - testHelpers.expectInsufficientLiquidityError(expect, errorFunction, new BigNumber(200)); - }); - it('should throw if not enough maker asset liquidity (completely fillable order)', () => { - const completelyFillableOrder = orderFactory.createSignedOrderFromPartial({ - makerAssetAmount: new BigNumber(123), - takerAssetAmount: new BigNumber(100), - takerFee: new BigNumber(200), + const errorFunction = () => { + buyQuoteCalculator.calculate( + firstOrderAndFillableAmount, + smallFeeOrderAndFillableAmount, + new BigNumber(201), + 0, + 0, + false, + ); + }; + testHelpers.expectInsufficientLiquidityError(expect, errorFunction, new BigNumber(200)); + }); + it('should throw if not enough maker asset liquidity (completely fillable order)', () => { + const completelyFillableOrder = orderFactory.createSignedOrderFromPartial({ + makerAssetAmount: new BigNumber(123), + takerAssetAmount: new BigNumber(100), + takerFee: new BigNumber(200), + }); + const completelyFillableOrdersAndFillableAmount: OrdersAndFillableAmounts = { + orders: [completelyFillableOrder], + remainingFillableMakerAssetAmounts: [completelyFillableOrder.makerAssetAmount], + }; + const errorFunction = () => { + buyQuoteCalculator.calculate( + completelyFillableOrdersAndFillableAmount, + smallFeeOrderAndFillableAmount, + new BigNumber(124), + 0, + 0, + false, + ); + }; + testHelpers.expectInsufficientLiquidityError(expect, errorFunction, new BigNumber(123)); + }); + it('should throw with 1 amount available if no slippage', () => { + const smallOrder = orderFactory.createSignedOrderFromPartial({ + makerAssetAmount: new BigNumber(1), + takerAssetAmount: new BigNumber(1), + takerFee: new BigNumber(0), + }); + const errorFunction = () => { + buyQuoteCalculator.calculate( + { orders: [smallOrder], remainingFillableMakerAssetAmounts: [smallOrder.makerAssetAmount] }, + smallFeeOrderAndFillableAmount, + new BigNumber(600), + 0, + 0, + false, + ); + }; + testHelpers.expectInsufficientLiquidityError(expect, errorFunction, new BigNumber(1)); + }); + it('should throw without amount available to fill if amount rounds to 0', () => { + const smallOrder = orderFactory.createSignedOrderFromPartial({ + makerAssetAmount: new BigNumber(1), + takerAssetAmount: new BigNumber(1), + takerFee: new BigNumber(0), + }); + const errorFunction = () => { + buyQuoteCalculator.calculate( + { orders: [smallOrder], remainingFillableMakerAssetAmounts: [smallOrder.makerAssetAmount] }, + smallFeeOrderAndFillableAmount, + new BigNumber(600), + 0, + 0.2, + false, + ); + }; + testHelpers.expectInsufficientLiquidityError(expect, errorFunction, undefined); }); - const completelyFillableOrdersAndFillableAmount: OrdersAndFillableAmounts = { - orders: [completelyFillableOrder], - remainingFillableMakerAssetAmounts: [completelyFillableOrder.makerAssetAmount], - }; - const errorFunction = () => { - buyQuoteCalculator.calculate( - completelyFillableOrdersAndFillableAmount, - smallFeeOrderAndFillableAmount, - new BigNumber(124), - 0, - 0, - false, - ); - }; - testHelpers.expectInsufficientLiquidityError(expect, errorFunction, new BigNumber(123)); }); it('should not throw if order is fillable', () => { expect(() => diff --git a/packages/asset-buyer/test/utils/test_helpers.ts b/packages/asset-buyer/test/utils/test_helpers.ts index b99906792..d746316f8 100644 --- a/packages/asset-buyer/test/utils/test_helpers.ts +++ b/packages/asset-buyer/test/utils/test_helpers.ts @@ -1,12 +1,12 @@ import { BigNumber } from '@0x/utils'; -import { InsufficientAssetLiquidityError } from '../../src/types'; +import { InsufficientAssetLiquidityError } from '../../src/errors'; export const testHelpers = { expectInsufficientLiquidityError: ( expect: Chai.ExpectStatic, functionWhichTriggersError: () => void, - expectedAmountAvailableToFill: BigNumber, + expectedAmountAvailableToFill?: BigNumber, ): void => { let errorThrown = false; try { @@ -14,7 +14,11 @@ export const testHelpers = { } catch (e) { errorThrown = true; expect(e).to.be.instanceOf(InsufficientAssetLiquidityError); - expect(e.amountAvailableToFill).to.be.bignumber.equal(expectedAmountAvailableToFill); + if (expectedAmountAvailableToFill) { + expect(e.amountAvailableToFill).to.be.bignumber.equal(expectedAmountAvailableToFill); + } else { + expect(e.amountAvailableToFill).to.be.undefined(); + } } expect(errorThrown).to.be.true(); diff --git a/packages/instant/src/util/asset.ts b/packages/instant/src/util/asset.ts index 5e83e7e6d..756194f1f 100644 --- a/packages/instant/src/util/asset.ts +++ b/packages/instant/src/util/asset.ts @@ -113,7 +113,11 @@ export const assetUtils = { assetBuyerErrorMessage: (asset: ERC20Asset, error: Error): string | undefined => { if (error.message === AssetBuyerError.InsufficientAssetLiquidity) { const assetName = assetUtils.bestNameForAsset(asset, 'of this asset'); - if (error instanceof InsufficientAssetLiquidityError) { + if ( + error instanceof InsufficientAssetLiquidityError && + error.amountAvailableToFill && + error.amountAvailableToFill.greaterThan(BIG_NUMBER_ZERO) + ) { const unitAmountAvailableToFill = Web3Wrapper.toUnitAmount( error.amountAvailableToFill, asset.metaData.decimals, -- cgit v1.2.3 From 5981823ac14f8f129e1866c3e39caed2211d5cea Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Fri, 14 Dec 2018 15:18:35 -0800 Subject: Update asset-buyer changelog describing error change --- packages/asset-buyer/CHANGELOG.json | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'packages') diff --git a/packages/asset-buyer/CHANGELOG.json b/packages/asset-buyer/CHANGELOG.json index 470d9b03b..44837d040 100644 --- a/packages/asset-buyer/CHANGELOG.json +++ b/packages/asset-buyer/CHANGELOG.json @@ -1,4 +1,12 @@ [ + { + "version": "4.0.0", + "changes": [ + { + "note": "Raise custom InsufficientAssetLiquidityError error with amountAvailableToFill attribute" + } + ] + }, { "version": "3.0.4", "changes": [ -- cgit v1.2.3 From ab10119c5af208be60782395b1faff906f0948f2 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Fri, 14 Dec 2018 15:24:01 -0800 Subject: one more test for undefined --- packages/instant/test/util/asset.test.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/instant/test/util/asset.test.ts b/packages/instant/test/util/asset.test.ts index dbf2e15a5..213e69dd8 100644 --- a/packages/instant/test/util/asset.test.ts +++ b/packages/instant/test/util/asset.test.ts @@ -83,7 +83,12 @@ describe('assetDataUtil', () => { assetUtils.assetBuyerErrorMessage(WAX_ASSET, new InsufficientAssetLiquidityError(amountAvailable)), ).toEqual('There are only 3 WAX available to buy'); }); - it('should generic message when amount available rounds to zero', () => { + it('should return generic message when InsufficientAssetLiquidityError contains undefined value', () => { + expect(assetUtils.assetBuyerErrorMessage(ZRX_ASSET, new InsufficientAssetLiquidityError())).toEqual( + 'Not enough ZRX available', + ); + }); + it('should return generic message when amount available rounds to zero', () => { const amountAvailable = Web3Wrapper.toBaseUnitAmount(new BigNumber(0.002), 18); expect( assetUtils.assetBuyerErrorMessage(ZRX_ASSET, new InsufficientAssetLiquidityError(amountAvailable)), -- cgit v1.2.3 From 219902a169f662737de68e8c79e1cb6a164ad548 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Fri, 14 Dec 2018 15:55:52 -0800 Subject: rename boolean var --- packages/asset-buyer/test/utils/test_helpers.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'packages') diff --git a/packages/asset-buyer/test/utils/test_helpers.ts b/packages/asset-buyer/test/utils/test_helpers.ts index d746316f8..9c7c244af 100644 --- a/packages/asset-buyer/test/utils/test_helpers.ts +++ b/packages/asset-buyer/test/utils/test_helpers.ts @@ -8,11 +8,11 @@ export const testHelpers = { functionWhichTriggersError: () => void, expectedAmountAvailableToFill?: BigNumber, ): void => { - let errorThrown = false; + let wasErrorThrown = false; try { functionWhichTriggersError(); } catch (e) { - errorThrown = true; + wasErrorThrown = true; expect(e).to.be.instanceOf(InsufficientAssetLiquidityError); if (expectedAmountAvailableToFill) { expect(e.amountAvailableToFill).to.be.bignumber.equal(expectedAmountAvailableToFill); @@ -21,6 +21,6 @@ export const testHelpers = { } } - expect(errorThrown).to.be.true(); + expect(wasErrorThrown).to.be.true(); }, }; -- cgit v1.2.3 From 2e2e157fc8bb9da26e01ebf0b62e1cb74515d187 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Fri, 14 Dec 2018 16:20:45 -0800 Subject: Fix for asset-buyer documentation --- packages/monorepo-scripts/src/doc_gen_configs.ts | 1 + 1 file changed, 1 insertion(+) (limited to 'packages') diff --git a/packages/monorepo-scripts/src/doc_gen_configs.ts b/packages/monorepo-scripts/src/doc_gen_configs.ts index 7a14f8664..875590b34 100644 --- a/packages/monorepo-scripts/src/doc_gen_configs.ts +++ b/packages/monorepo-scripts/src/doc_gen_configs.ts @@ -9,6 +9,7 @@ export const docGenConfigs: DocGenConfigs = { Array: 'https://developer.mozilla.org/pt-PT/docs/Web/JavaScript/Reference/Global_Objects/Array', BigNumber: 'http://mikemcl.github.io/bignumber.js', Error: 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error', + ErrorConstructor: 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error', Buffer: 'https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/node/v9/index.d.ts#L262', 'solc.StandardContractOutput': 'https://solidity.readthedocs.io/en/v0.4.24/using-the-compiler.html#output-description', -- cgit v1.2.3 From aa9aa1f58a4e63b5e5c7863f3b7afb020d7712c5 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Fri, 14 Dec 2018 17:05:52 -0800 Subject: more documentation --- packages/asset-buyer/src/errors.ts | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'packages') diff --git a/packages/asset-buyer/src/errors.ts b/packages/asset-buyer/src/errors.ts index ef05a5d0a..68e9a684b 100644 --- a/packages/asset-buyer/src/errors.ts +++ b/packages/asset-buyer/src/errors.ts @@ -6,7 +6,13 @@ import { AssetBuyerError } from './types'; * Error class representing insufficient asset liquidity */ export class InsufficientAssetLiquidityError extends Error { + /** + * The amount availabe to fill (in base units) factoring in slippage. + */ public amountAvailableToFill?: BigNumber; + /** + * @param amountAvailableToFill The amount availabe to fill (in base units) factoring in slippage + */ constructor(amountAvailableToFill?: BigNumber) { super(AssetBuyerError.InsufficientAssetLiquidity); this.amountAvailableToFill = amountAvailableToFill; -- cgit v1.2.3 From c8c8219c055cc5798cf5cdc71199ee7ae505cd5a Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Fri, 21 Dec 2018 16:26:07 -0800 Subject: Make amountAvailableToFill required --- packages/asset-buyer/src/errors.ts | 4 ++-- packages/asset-buyer/src/utils/buy_quote_calculator.ts | 6 +----- packages/instant/src/util/asset.ts | 1 - 3 files changed, 3 insertions(+), 8 deletions(-) (limited to 'packages') diff --git a/packages/asset-buyer/src/errors.ts b/packages/asset-buyer/src/errors.ts index 68e9a684b..ec5fe548c 100644 --- a/packages/asset-buyer/src/errors.ts +++ b/packages/asset-buyer/src/errors.ts @@ -9,11 +9,11 @@ export class InsufficientAssetLiquidityError extends Error { /** * The amount availabe to fill (in base units) factoring in slippage. */ - public amountAvailableToFill?: BigNumber; + public amountAvailableToFill: BigNumber; /** * @param amountAvailableToFill The amount availabe to fill (in base units) factoring in slippage */ - constructor(amountAvailableToFill?: BigNumber) { + constructor(amountAvailableToFill: BigNumber) { super(AssetBuyerError.InsufficientAssetLiquidity); this.amountAvailableToFill = amountAvailableToFill; // Setting prototype so instanceof works. See https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#extending-built-ins-like-error-array-and-map-may-no-longer-work diff --git a/packages/asset-buyer/src/utils/buy_quote_calculator.ts b/packages/asset-buyer/src/utils/buy_quote_calculator.ts index 59293d1b7..ceeee93d3 100644 --- a/packages/asset-buyer/src/utils/buy_quote_calculator.ts +++ b/packages/asset-buyer/src/utils/buy_quote_calculator.ts @@ -47,11 +47,7 @@ export const buyQuoteCalculator = { .div(multiplerNeededWithSlippage) .round(0, BigNumber.ROUND_DOWN); - throw new InsufficientAssetLiquidityError( - amountAvailableToFillConsideringSlippage.gt(constants.ZERO_AMOUNT) - ? amountAvailableToFillConsideringSlippage - : undefined, - ); + throw new InsufficientAssetLiquidityError(amountAvailableToFillConsideringSlippage); } // if we are not buying ZRX: // given the orders calculated above, find the fee-orders that cover the desired assetBuyAmount (with slippage) diff --git a/packages/instant/src/util/asset.ts b/packages/instant/src/util/asset.ts index e7aa55c88..b009a327f 100644 --- a/packages/instant/src/util/asset.ts +++ b/packages/instant/src/util/asset.ts @@ -115,7 +115,6 @@ export const assetUtils = { const assetName = assetUtils.bestNameForAsset(asset, 'of this asset'); if ( error instanceof InsufficientAssetLiquidityError && - error.amountAvailableToFill && error.amountAvailableToFill.greaterThan(BIG_NUMBER_ZERO) ) { const unitAmountAvailableToFill = Web3Wrapper.toUnitAmount( -- cgit v1.2.3 From 26977f6408442883c20001a57a3b9001f4ab9b25 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Fri, 21 Dec 2018 16:29:53 -0800 Subject: Fix var name and use floor instead of .round(0, ROUND_DOWN) --- packages/asset-buyer/src/utils/buy_quote_calculator.ts | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) (limited to 'packages') diff --git a/packages/asset-buyer/src/utils/buy_quote_calculator.ts b/packages/asset-buyer/src/utils/buy_quote_calculator.ts index ceeee93d3..fcded6ab1 100644 --- a/packages/asset-buyer/src/utils/buy_quote_calculator.ts +++ b/packages/asset-buyer/src/utils/buy_quote_calculator.ts @@ -36,16 +36,14 @@ export const buyQuoteCalculator = { if (remainingFillAmount.gt(constants.ZERO_AMOUNT)) { // We needed the amount they requested to buy, plus the amount for slippage const totalAmountRequested = assetBuyAmount.plus(slippageBufferAmount); - const amountUnableToFill = totalAmountRequested.minus(remainingFillAmount); - // multiplerNeededWithSlippage represents what we need to multiply the assetBuyAmount by + const amountAbleToFill = totalAmountRequested.minus(remainingFillAmount); + // multiplierNeededWithSlippage represents what we need to multiply the assetBuyAmount by // in order to get the total amount needed considering slippage - // i.e. if slippagePercent was 0.2 (20%), multiplerNeededWithSlippage would be 1.2 - const multiplerNeededWithSlippage = new BigNumber(1).plus(slippagePercentage); - // Given amountAvailableToFillConsideringSlippage * multiplerNeededWithSlippage = amountUnableToFill - // We divide amountUnableToFill by multiplerNeededWithSlippage to determine amountAvailableToFillConsideringSlippage - const amountAvailableToFillConsideringSlippage = amountUnableToFill - .div(multiplerNeededWithSlippage) - .round(0, BigNumber.ROUND_DOWN); + // i.e. if slippagePercent was 0.2 (20%), multiplierNeededWithSlippage would be 1.2 + const multiplierNeededWithSlippage = new BigNumber(1).plus(slippagePercentage); + // Given amountAvailableToFillConsideringSlippage * multiplierNeededWithSlippage = amountAbleToFill + // We divide amountUnableToFill by multiplierNeededWithSlippage to determine amountAvailableToFillConsideringSlippage + const amountAvailableToFillConsideringSlippage = amountAbleToFill.div(multiplierNeededWithSlippage).floor(); throw new InsufficientAssetLiquidityError(amountAvailableToFillConsideringSlippage); } -- cgit v1.2.3 From 472f89bd3d459c8b5c29de8a808381bf19c8c72b Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Wed, 2 Jan 2019 14:46:31 -0800 Subject: feat(monorepo-scripts): Alert to discord when new publish happens --- packages/monorepo-scripts/package.json | 1 + packages/monorepo-scripts/src/constants.ts | 1 + packages/monorepo-scripts/src/prepublish_checks.ts | 7 ++++++ packages/monorepo-scripts/src/publish.ts | 12 +++++++++- packages/monorepo-scripts/src/utils/discord.ts | 27 ++++++++++++++++++++++ .../src/utils/github_release_utils.ts | 9 ++++++-- 6 files changed, 54 insertions(+), 3 deletions(-) create mode 100644 packages/monorepo-scripts/src/utils/discord.ts (limited to 'packages') diff --git a/packages/monorepo-scripts/package.json b/packages/monorepo-scripts/package.json index 0483e87c8..a64f101fe 100644 --- a/packages/monorepo-scripts/package.json +++ b/packages/monorepo-scripts/package.json @@ -47,6 +47,7 @@ "typescript": "3.0.1" }, "dependencies": { + "@0x/utils": "^2.0.8", "@lerna/batch-packages": "^3.0.0-beta.18", "@types/depcheck": "^0.6.0", "async-child-process": "^1.1.1", diff --git a/packages/monorepo-scripts/src/constants.ts b/packages/monorepo-scripts/src/constants.ts index acb4b211e..8f907ffef 100644 --- a/packages/monorepo-scripts/src/constants.ts +++ b/packages/monorepo-scripts/src/constants.ts @@ -5,5 +5,6 @@ export const constants = { stagingWebsite: 'http://staging-0xproject.s3-website-us-east-1.amazonaws.com', lernaExecutable: path.join('node_modules', '@0x-lerna-fork', 'lerna', 'cli.js'), githubPersonalAccessToken: process.env.GITHUB_PERSONAL_ACCESS_TOKEN_0X_JS, + discordAlertWebhookUrl: process.env.DISCORD_GITHUB_RELEASE_WEBHOOK_URL, dependenciesUpdatedMessage: 'Dependencies updated', }; diff --git a/packages/monorepo-scripts/src/prepublish_checks.ts b/packages/monorepo-scripts/src/prepublish_checks.ts index 36e61714b..82eaf5cf9 100644 --- a/packages/monorepo-scripts/src/prepublish_checks.ts +++ b/packages/monorepo-scripts/src/prepublish_checks.ts @@ -140,6 +140,13 @@ async function checkPublishRequiredSetupAsync(): Promise { ); } + // Check to see if discord URL is set up + if (_.isUndefined(constants.discordAlertWebhookUrl)) { + throw new Error( + 'You must have a discord webhook URL set to an envVar named `DISCORD_GITHUB_RELEASE_WEBHOOK_URL`. Add it then try again.', + ); + } + // Check Yarn version is 1.X utils.log('Checking the yarn version...'); const result = await execAsync(`yarn --version`); diff --git a/packages/monorepo-scripts/src/publish.ts b/packages/monorepo-scripts/src/publish.ts index 854a72b86..06b0d72ae 100644 --- a/packages/monorepo-scripts/src/publish.ts +++ b/packages/monorepo-scripts/src/publish.ts @@ -14,6 +14,7 @@ import { constants } from './constants'; import { Package, PackageToNextVersion, VersionChangelog } from './types'; import { changelogUtils } from './utils/changelog_utils'; import { configs } from './utils/configs'; +import { alertDiscord } from './utils/discord'; import { DocGenerateAndUploadUtils } from './utils/doc_generate_and_upload_utils'; import { publishReleaseNotesAsync } from './utils/github_release_utils'; import { utils } from './utils/utils'; @@ -84,7 +85,16 @@ async function confirmAsync(message: string): Promise { await generateAndUploadDocJsonsAsync(packagesWithDocs, isStaging, shouldUploadDocs); } const isDryRun = configs.IS_LOCAL_PUBLISH; - await publishReleaseNotesAsync(updatedPublicPackages, isDryRun); + const releaseNotes = await publishReleaseNotesAsync(updatedPublicPackages, isDryRun); + utils.log('Published release notes'); + + if (!isDryRun && releaseNotes) { + try { + alertDiscord(releaseNotes); + } catch (e) { + utils.log("Couldn't alert discord, error: ", e.message, '. Please alert manually.'); + } + } })().catch(err => { utils.log(err); process.exit(1); diff --git a/packages/monorepo-scripts/src/utils/discord.ts b/packages/monorepo-scripts/src/utils/discord.ts new file mode 100644 index 000000000..2482a18f0 --- /dev/null +++ b/packages/monorepo-scripts/src/utils/discord.ts @@ -0,0 +1,27 @@ +import { fetchAsync } from '@0x/utils'; + +import { constants } from '../constants'; + +import { utils } from './utils'; + +export const alertDiscord = async (releaseNotes: string): Promise => { + const webhookUrl = constants.discordAlertWebhookUrl; + if (!webhookUrl) { + utils.log('Not alerting to discord because webhook url not set'); + return false; + } + + utils.log('Alerting discord...'); + const payload = { + content: `New monorepo package released! View at https://github.com/0xProject/0x-monorepo/releases \n\n ${releaseNotes}`, + }; + await fetchAsync(webhookUrl, { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify(payload), + }); + return true; +}; diff --git a/packages/monorepo-scripts/src/utils/github_release_utils.ts b/packages/monorepo-scripts/src/utils/github_release_utils.ts index e63244b46..1044c25e9 100644 --- a/packages/monorepo-scripts/src/utils/github_release_utils.ts +++ b/packages/monorepo-scripts/src/utils/github_release_utils.ts @@ -12,7 +12,10 @@ import { utils } from './utils'; const publishReleaseAsync = promisify(publishRelease); // tslint:disable-next-line:completed-docs -export async function publishReleaseNotesAsync(packagesToPublish: Package[], isDryRun: boolean): Promise { +export async function publishReleaseNotesAsync( + packagesToPublish: Package[], + isDryRun: boolean, +): Promise { // Git push a tag representing this publish (publish-{commit-hash}) (truncate hash) const result = await execAsync('git log -n 1 --pretty=format:"%H"', { cwd: constants.monorepoRootPath }); const latestGitCommit = result.stdout; @@ -75,6 +78,8 @@ export async function publishReleaseNotesAsync(packagesToPublish: Package[], isD utils.log('Publishing release notes ', releaseName, '...'); await publishReleaseAsync(publishReleaseConfigs); + + return aggregateNotes; } // Asset paths should described from the monorepo root. This method prefixes @@ -88,7 +93,7 @@ function adjustAssetPaths(assets: string[]): string[] { return finalAssets; } -function getReleaseNotesForPackage(packageLocation: string, packageName: string): string { +export function getReleaseNotesForPackage(packageLocation: string, packageName: string): string { const changelogJSONPath = path.join(packageLocation, 'CHANGELOG.json'); const changelogJSON = readFileSync(changelogJSONPath, 'utf-8'); const changelogs = JSON.parse(changelogJSON); -- cgit v1.2.3 From 9f47f90c6e80ba9a61bcb6065fed1e2c6be8c5b7 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Wed, 2 Jan 2019 15:16:45 -0800 Subject: remove unused export --- packages/monorepo-scripts/src/utils/github_release_utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/monorepo-scripts/src/utils/github_release_utils.ts b/packages/monorepo-scripts/src/utils/github_release_utils.ts index 1044c25e9..48704f3aa 100644 --- a/packages/monorepo-scripts/src/utils/github_release_utils.ts +++ b/packages/monorepo-scripts/src/utils/github_release_utils.ts @@ -93,7 +93,7 @@ function adjustAssetPaths(assets: string[]): string[] { return finalAssets; } -export function getReleaseNotesForPackage(packageLocation: string, packageName: string): string { +function getReleaseNotesForPackage(packageLocation: string, packageName: string): string { const changelogJSONPath = path.join(packageLocation, 'CHANGELOG.json'); const changelogJSON = readFileSync(changelogJSONPath, 'utf-8'); const changelogs = JSON.parse(changelogJSON); -- cgit v1.2.3 From 92d45a19d1b8f894add205f87bee77962848699b Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Wed, 2 Jan 2019 15:19:38 -0800 Subject: await alerting discord --- packages/monorepo-scripts/src/publish.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/monorepo-scripts/src/publish.ts b/packages/monorepo-scripts/src/publish.ts index 06b0d72ae..923248360 100644 --- a/packages/monorepo-scripts/src/publish.ts +++ b/packages/monorepo-scripts/src/publish.ts @@ -90,7 +90,7 @@ async function confirmAsync(message: string): Promise { if (!isDryRun && releaseNotes) { try { - alertDiscord(releaseNotes); + await alertDiscord(releaseNotes); } catch (e) { utils.log("Couldn't alert discord, error: ", e.message, '. Please alert manually.'); } -- cgit v1.2.3 From 9131ca15620362565ca90a54971a3fc609876e9f Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Wed, 2 Jan 2019 15:22:07 -0800 Subject: Use constant --- packages/monorepo-scripts/src/constants.ts | 1 + packages/monorepo-scripts/src/utils/discord.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/monorepo-scripts/src/constants.ts b/packages/monorepo-scripts/src/constants.ts index 8f907ffef..c15bcabf4 100644 --- a/packages/monorepo-scripts/src/constants.ts +++ b/packages/monorepo-scripts/src/constants.ts @@ -6,5 +6,6 @@ export const constants = { lernaExecutable: path.join('node_modules', '@0x-lerna-fork', 'lerna', 'cli.js'), githubPersonalAccessToken: process.env.GITHUB_PERSONAL_ACCESS_TOKEN_0X_JS, discordAlertWebhookUrl: process.env.DISCORD_GITHUB_RELEASE_WEBHOOK_URL, + releasesUrl: 'https://github.com/0xProject/0x-monorepo/releases', dependenciesUpdatedMessage: 'Dependencies updated', }; diff --git a/packages/monorepo-scripts/src/utils/discord.ts b/packages/monorepo-scripts/src/utils/discord.ts index 2482a18f0..116b1b908 100644 --- a/packages/monorepo-scripts/src/utils/discord.ts +++ b/packages/monorepo-scripts/src/utils/discord.ts @@ -13,7 +13,7 @@ export const alertDiscord = async (releaseNotes: string): Promise => { utils.log('Alerting discord...'); const payload = { - content: `New monorepo package released! View at https://github.com/0xProject/0x-monorepo/releases \n\n ${releaseNotes}`, + content: `New monorepo package released! View at ${constants.releasesUrl} \n\n ${releaseNotes}`, }; await fetchAsync(webhookUrl, { method: 'POST', -- cgit v1.2.3 From 89f67b9becf3b4c0d13cceeaca4d5656ad500a9a Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Wed, 2 Jan 2019 16:56:28 -0800 Subject: take out no-longer necessary test --- packages/instant/test/util/asset.test.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) (limited to 'packages') diff --git a/packages/instant/test/util/asset.test.ts b/packages/instant/test/util/asset.test.ts index 213e69dd8..402a556d5 100644 --- a/packages/instant/test/util/asset.test.ts +++ b/packages/instant/test/util/asset.test.ts @@ -1,4 +1,4 @@ -import { AssetBuyerError, InsufficientAssetLiquidityError, BigNumber } from '@0x/asset-buyer'; +import { AssetBuyerError, BigNumber, InsufficientAssetLiquidityError } from '@0x/asset-buyer'; import { AssetProxyId, ObjectMap } from '@0x/types'; import { Web3Wrapper } from '@0x/web3-wrapper'; @@ -83,11 +83,6 @@ describe('assetDataUtil', () => { assetUtils.assetBuyerErrorMessage(WAX_ASSET, new InsufficientAssetLiquidityError(amountAvailable)), ).toEqual('There are only 3 WAX available to buy'); }); - it('should return generic message when InsufficientAssetLiquidityError contains undefined value', () => { - expect(assetUtils.assetBuyerErrorMessage(ZRX_ASSET, new InsufficientAssetLiquidityError())).toEqual( - 'Not enough ZRX available', - ); - }); it('should return generic message when amount available rounds to zero', () => { const amountAvailable = Web3Wrapper.toBaseUnitAmount(new BigNumber(0.002), 18); expect( -- cgit v1.2.3 From 76dde294f10bb3d5c1074cd6599668bcb1cdd8ec Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Fri, 4 Jan 2019 07:54:01 -0800 Subject: Add PR number --- packages/asset-buyer/CHANGELOG.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/asset-buyer/CHANGELOG.json b/packages/asset-buyer/CHANGELOG.json index 44837d040..6069d6a12 100644 --- a/packages/asset-buyer/CHANGELOG.json +++ b/packages/asset-buyer/CHANGELOG.json @@ -3,7 +3,8 @@ "version": "4.0.0", "changes": [ { - "note": "Raise custom InsufficientAssetLiquidityError error with amountAvailableToFill attribute" + "note": "Raise custom InsufficientAssetLiquidityError error with amountAvailableToFill attribute", + "pr": 1437 } ] }, -- cgit v1.2.3 From 442de09bbe36cb4a1c2d32cca6e65aee710f6382 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Fri, 4 Jan 2019 08:54:44 -0800 Subject: Add async suffix --- packages/monorepo-scripts/src/publish.ts | 4 ++-- packages/monorepo-scripts/src/utils/discord.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'packages') diff --git a/packages/monorepo-scripts/src/publish.ts b/packages/monorepo-scripts/src/publish.ts index 923248360..f11f998b8 100644 --- a/packages/monorepo-scripts/src/publish.ts +++ b/packages/monorepo-scripts/src/publish.ts @@ -14,7 +14,7 @@ import { constants } from './constants'; import { Package, PackageToNextVersion, VersionChangelog } from './types'; import { changelogUtils } from './utils/changelog_utils'; import { configs } from './utils/configs'; -import { alertDiscord } from './utils/discord'; +import { alertDiscordAsync } from './utils/discord'; import { DocGenerateAndUploadUtils } from './utils/doc_generate_and_upload_utils'; import { publishReleaseNotesAsync } from './utils/github_release_utils'; import { utils } from './utils/utils'; @@ -90,7 +90,7 @@ async function confirmAsync(message: string): Promise { if (!isDryRun && releaseNotes) { try { - await alertDiscord(releaseNotes); + await alertDiscordAsync(releaseNotes); } catch (e) { utils.log("Couldn't alert discord, error: ", e.message, '. Please alert manually.'); } diff --git a/packages/monorepo-scripts/src/utils/discord.ts b/packages/monorepo-scripts/src/utils/discord.ts index 116b1b908..ed10b1cc3 100644 --- a/packages/monorepo-scripts/src/utils/discord.ts +++ b/packages/monorepo-scripts/src/utils/discord.ts @@ -4,7 +4,7 @@ import { constants } from '../constants'; import { utils } from './utils'; -export const alertDiscord = async (releaseNotes: string): Promise => { +export const alertDiscordAsync = async (releaseNotes: string): Promise => { const webhookUrl = constants.discordAlertWebhookUrl; if (!webhookUrl) { utils.log('Not alerting to discord because webhook url not set'); -- cgit v1.2.3 From f1a7efc97ea4eb4305836a3fb1713a72cc12ac94 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Fri, 4 Jan 2019 09:01:13 -0800 Subject: Return void and throw error instead of logging and returning boolean --- packages/monorepo-scripts/src/utils/discord.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'packages') diff --git a/packages/monorepo-scripts/src/utils/discord.ts b/packages/monorepo-scripts/src/utils/discord.ts index ed10b1cc3..2f3afef45 100644 --- a/packages/monorepo-scripts/src/utils/discord.ts +++ b/packages/monorepo-scripts/src/utils/discord.ts @@ -4,11 +4,10 @@ import { constants } from '../constants'; import { utils } from './utils'; -export const alertDiscordAsync = async (releaseNotes: string): Promise => { +export const alertDiscordAsync = async (releaseNotes: string): Promise => { const webhookUrl = constants.discordAlertWebhookUrl; if (!webhookUrl) { - utils.log('Not alerting to discord because webhook url not set'); - return false; + throw new Error("No discord webhook url, can't alert"); } utils.log('Alerting discord...'); @@ -23,5 +22,5 @@ export const alertDiscordAsync = async (releaseNotes: string): Promise }, body: JSON.stringify(payload), }); - return true; + return; }; -- cgit v1.2.3 From 99016cc5a6cc018eac08d715e649fadbe952fe21 Mon Sep 17 00:00:00 2001 From: Fabio B Date: Mon, 7 Jan 2019 09:43:35 -0800 Subject: Use explicit undefined check Co-Authored-By: steveklebanoff --- packages/monorepo-scripts/src/utils/discord.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/monorepo-scripts/src/utils/discord.ts b/packages/monorepo-scripts/src/utils/discord.ts index 2f3afef45..3a0458769 100644 --- a/packages/monorepo-scripts/src/utils/discord.ts +++ b/packages/monorepo-scripts/src/utils/discord.ts @@ -6,7 +6,7 @@ import { utils } from './utils'; export const alertDiscordAsync = async (releaseNotes: string): Promise => { const webhookUrl = constants.discordAlertWebhookUrl; - if (!webhookUrl) { + if (webhookUrl === undefined) { throw new Error("No discord webhook url, can't alert"); } -- cgit v1.2.3 From 571bc736e573312bc1ca1992c2bfc34269d5899c Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Mon, 7 Jan 2019 09:45:04 -0800 Subject: Make error message less scary --- packages/monorepo-scripts/src/publish.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/monorepo-scripts/src/publish.ts b/packages/monorepo-scripts/src/publish.ts index f11f998b8..e0602a74f 100644 --- a/packages/monorepo-scripts/src/publish.ts +++ b/packages/monorepo-scripts/src/publish.ts @@ -92,7 +92,7 @@ async function confirmAsync(message: string): Promise { try { await alertDiscordAsync(releaseNotes); } catch (e) { - utils.log("Couldn't alert discord, error: ", e.message, '. Please alert manually.'); + utils.log("Publish successful, but couldn't auto-alert discord (", e.message, '), Please alert manually.'); } } })().catch(err => { -- cgit v1.2.3 From 1f0f2076a9863d3048312c2f8f27e9b5f0202678 Mon Sep 17 00:00:00 2001 From: fragosti Date: Mon, 7 Jan 2019 17:09:36 -0800 Subject: fix: CORS issue by accessing 0x geth nodes through 0x.org endpoint --- packages/website/ts/utils/configs.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'packages') diff --git a/packages/website/ts/utils/configs.ts b/packages/website/ts/utils/configs.ts index fab382b07..663f90249 100644 --- a/packages/website/ts/utils/configs.ts +++ b/packages/website/ts/utils/configs.ts @@ -39,8 +39,8 @@ export const configs = { ] as OutdatedWrappedEtherByNetworkId[], // The order matters. We first try first node and only then fall back to others. PUBLIC_NODE_URLS_BY_NETWORK_ID: { - [1]: [`https://mainnet.infura.io/${INFURA_API_KEY}`, 'https://mainnet.0xproject.com'], - [42]: [`https://kovan.infura.io/${INFURA_API_KEY}`, 'https://kovan.0xproject.com'], + [1]: [`https://mainnet.infura.io/${INFURA_API_KEY}`, 'https://mainnet.0x.org'], + [42]: [`https://kovan.infura.io/${INFURA_API_KEY}`, 'https://kovan.0x.org'], [3]: [`https://ropsten.infura.io/${INFURA_API_KEY}`], [4]: [`https://rinkeby.infura.io/${INFURA_API_KEY}`], } as PublicNodeUrlsByNetworkId, -- cgit v1.2.3 From eae255b0ff19965cb3cf78f55bdb7df4363af6d1 Mon Sep 17 00:00:00 2001 From: fragosti Date: Mon, 7 Jan 2019 17:19:36 -0800 Subject: chore: point to 0x.org website-api --- packages/website/ts/utils/configs.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'packages') diff --git a/packages/website/ts/utils/configs.ts b/packages/website/ts/utils/configs.ts index 663f90249..7cc854ca0 100644 --- a/packages/website/ts/utils/configs.ts +++ b/packages/website/ts/utils/configs.ts @@ -5,8 +5,8 @@ const INFURA_API_KEY = 'T5WSC8cautR4KXyYgsRs'; export const configs = { AMOUNT_DISPLAY_PRECSION: 5, - BACKEND_BASE_PROD_URL: 'https://website-api.0xproject.com', - BACKEND_BASE_STAGING_URL: 'https://staging-website-api.0xproject.com', + BACKEND_BASE_PROD_URL: 'https://website-api.0x.org', + BACKEND_BASE_STAGING_URL: 'https://staging-website-api.0x.org', BASE_URL, BITLY_ACCESS_TOKEN: 'ffc4c1a31e5143848fb7c523b39f91b9b213d208', DEFAULT_DERIVATION_PATH: `44'/60'/0'`, -- cgit v1.2.3 From f412c36e23124da5ad6c0ee146a3c8063a60aacc Mon Sep 17 00:00:00 2001 From: fragosti Date: Mon, 7 Jan 2019 17:31:38 -0800 Subject: fix: use old forum URL for now --- packages/website/ts/utils/constants.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'packages') diff --git a/packages/website/ts/utils/constants.ts b/packages/website/ts/utils/constants.ts index ada8de549..c62de4eb3 100644 --- a/packages/website/ts/utils/constants.ts +++ b/packages/website/ts/utils/constants.ts @@ -2,7 +2,7 @@ import { ALink } from '@0x/react-shared'; import { BigNumber } from '@0x/utils'; import { Key, WebsitePaths } from 'ts/types'; -const URL_FORUM = 'https://forum.0x.org'; +const URL_FORUM = 'https://forum.0xproject.com'; const URL_ZEROEX_CHAT = 'https://discord.gg/d3FTX3M'; export const constants = { @@ -75,7 +75,7 @@ export const constants = { URL_APACHE_LICENSE: 'http://www.apache.org/licenses/LICENSE-2.0', URL_BITLY_API: 'https://api-ssl.bitly.com', URL_BLOG: 'https://blog.0xproject.com/latest', - URL_DISCOURSE_FORUM: 'https://forum.0x.org', + URL_DISCOURSE_FORUM: 'https://forum.0xproject.com', URL_ECOSYSTEM_APPLY: 'https://0x.smapply.io/', URL_ECOSYSTEM_BLOG_POST: 'https://blog.0xproject.com/announcing-the-0x-ecosystem-acceleration-program-89d1cb89d565', URL_FIREFOX_U2F_ADDON: 'https://addons.mozilla.org/en-US/firefox/addon/u2f-support-add-on/', -- cgit v1.2.3 From 2c974b5f3ffa0e9736000273e39cdeee4a251b94 Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Tue, 8 Jan 2019 12:23:33 +0100 Subject: Refactor out sol-cov, sol-profiler and sol-trace into their separate packages --- packages/metacoin/package.json | 3 +- packages/metacoin/test/utils/coverage.ts | 2 +- packages/metacoin/test/utils/profiler.ts | 2 +- packages/sol-cov/.npmignore | 6 - packages/sol-cov/CHANGELOG.json | 389 ------------------- packages/sol-cov/CHANGELOG.md | 158 -------- packages/sol-cov/README.md | 75 ---- packages/sol-cov/compiler.json | 18 - packages/sol-cov/coverage/.gitkeep | 0 packages/sol-cov/package.json | 87 ----- .../artifact_adapters/abstract_artifact_adapter.ts | 5 - .../sol_compiler_artifact_adapter.ts | 61 --- .../artifact_adapters/truffle_artifact_adapter.ts | 88 ----- packages/sol-cov/src/ast_visitor.ts | 168 --------- packages/sol-cov/src/collect_coverage_entries.ts | 41 -- packages/sol-cov/src/constants.ts | 8 - packages/sol-cov/src/coverage_subprovider.ts | 146 -------- packages/sol-cov/src/get_source_range_snippet.ts | 180 --------- packages/sol-cov/src/globals.d.ts | 7 - packages/sol-cov/src/index.ts | 25 -- packages/sol-cov/src/instructions.ts | 23 -- packages/sol-cov/src/profiler_subprovider.ts | 91 ----- packages/sol-cov/src/revert_trace.ts | 90 ----- packages/sol-cov/src/revert_trace_subprovider.ts | 163 -------- packages/sol-cov/src/source_maps.ts | 81 ---- packages/sol-cov/src/trace.ts | 99 ----- .../sol-cov/src/trace_collection_subprovider.ts | 188 ---------- packages/sol-cov/src/trace_collector.ts | 93 ----- packages/sol-cov/src/trace_info_subprovider.ts | 59 --- packages/sol-cov/src/types.ts | 126 ------- packages/sol-cov/src/utils.ts | 87 ----- .../sol-cov/test/collect_coverage_entries_test.ts | 155 -------- .../fixtures/contracts/AllSolidityFeatures.sol | 413 --------------------- .../test/fixtures/contracts/SimpleStorage.sol | 11 - .../sol-cov/test/fixtures/contracts/Simplest.sol | 2 - .../test/fixtures/contracts/SolcovIgnore.sol | 22 -- packages/sol-cov/test/instructions_test.ts | 19 - .../test/sol_compiler_artifact_adapter_test.ts | 29 -- packages/sol-cov/test/source_maps_test.ts | 71 ---- packages/sol-cov/test/trace_test.ts | 55 --- packages/sol-cov/test/utils_test.ts | 53 --- packages/sol-cov/tsconfig.json | 8 - packages/sol-cov/tslint.json | 6 - packages/sol-cov/typedoc-tsconfig.json | 7 - packages/sol-coverage/.npmignore | 6 + packages/sol-coverage/CHANGELOG.json | 12 + packages/sol-coverage/README.md | 75 ++++ packages/sol-coverage/package.json | 52 +++ packages/sol-coverage/src/coverage_subprovider.ts | 146 ++++++++ packages/sol-coverage/src/globals.d.ts | 7 + packages/sol-coverage/src/index.ts | 23 ++ packages/sol-coverage/tsconfig.json | 8 + packages/sol-coverage/tslint.json | 3 + packages/sol-coverage/typedoc-tsconfig.json | 7 + packages/sol-profiler/.npmignore | 6 + packages/sol-profiler/CHANGELOG.json | 12 + packages/sol-profiler/README.md | 75 ++++ packages/sol-profiler/package.json | 50 +++ packages/sol-profiler/src/globals.d.ts | 7 + packages/sol-profiler/src/index.ts | 25 ++ packages/sol-profiler/src/profiler_subprovider.ts | 98 +++++ packages/sol-profiler/tsconfig.json | 8 + packages/sol-profiler/tslint.json | 3 + packages/sol-profiler/typedoc-tsconfig.json | 7 + packages/sol-trace-based-tools-common/.npmignore | 6 + .../sol-trace-based-tools-common/CHANGELOG.json | 398 ++++++++++++++++++++ packages/sol-trace-based-tools-common/CHANGELOG.md | 158 ++++++++ packages/sol-trace-based-tools-common/README.md | 61 +++ .../sol-trace-based-tools-common/compiler.json | 18 + .../sol-trace-based-tools-common/coverage/.gitkeep | 0 packages/sol-trace-based-tools-common/package.json | 87 +++++ .../artifact_adapters/abstract_artifact_adapter.ts | 5 + .../sol_compiler_artifact_adapter.ts | 61 +++ .../artifact_adapters/truffle_artifact_adapter.ts | 88 +++++ .../src/ast_visitor.ts | 168 +++++++++ .../src/collect_coverage_entries.ts | 41 ++ .../sol-trace-based-tools-common/src/constants.ts | 8 + .../src/get_source_range_snippet.ts | 185 +++++++++ .../sol-trace-based-tools-common/src/globals.d.ts | 7 + packages/sol-trace-based-tools-common/src/index.ts | 39 ++ .../src/instructions.ts | 23 ++ .../src/revert_trace.ts | 95 +++++ .../src/source_maps.ts | 91 +++++ packages/sol-trace-based-tools-common/src/trace.ts | 104 ++++++ .../src/trace_collection_subprovider.ts | 188 ++++++++++ .../src/trace_collector.ts | 93 +++++ .../src/trace_info_subprovider.ts | 59 +++ packages/sol-trace-based-tools-common/src/types.ts | 126 +++++++ packages/sol-trace-based-tools-common/src/utils.ts | 87 +++++ .../test/collect_coverage_entries_test.ts | 155 ++++++++ .../fixtures/contracts/AllSolidityFeatures.sol | 413 +++++++++++++++++++++ .../test/fixtures/contracts/SimpleStorage.sol | 11 + .../test/fixtures/contracts/Simplest.sol | 2 + .../test/fixtures/contracts/SolcovIgnore.sol | 22 ++ .../test/instructions_test.ts | 19 + .../test/sol_compiler_artifact_adapter_test.ts | 29 ++ .../test/source_maps_test.ts | 71 ++++ .../test/trace_test.ts | 55 +++ .../test/utils_test.ts | 53 +++ .../sol-trace-based-tools-common/tsconfig.json | 8 + packages/sol-trace-based-tools-common/tslint.json | 3 + packages/sol-trace/.npmignore | 6 + packages/sol-trace/CHANGELOG.json | 12 + packages/sol-trace/README.md | 75 ++++ packages/sol-trace/package.json | 52 +++ packages/sol-trace/src/globals.d.ts | 7 + packages/sol-trace/src/index.ts | 24 ++ packages/sol-trace/src/revert_trace_subprovider.ts | 167 +++++++++ packages/sol-trace/tsconfig.json | 8 + packages/sol-trace/tslint.json | 3 + packages/sol-trace/typedoc-tsconfig.json | 7 + packages/sra-spec/src/md/introduction.md | 4 +- packages/website/md/docs/sol_cov/1/installation.md | 17 - packages/website/md/docs/sol_cov/1/introduction.md | 1 - packages/website/md/docs/sol_cov/1/usage.md | 62 ---- packages/website/md/docs/sol_cov/2/installation.md | 17 - packages/website/md/docs/sol_cov/2/introduction.md | 1 - packages/website/md/docs/sol_cov/2/usage.md | 62 ---- .../website/md/docs/sol_coverage/installation.md | 17 + .../website/md/docs/sol_coverage/introduction.md | 1 + packages/website/md/docs/sol_coverage/usage.md | 62 ++++ .../website/md/docs/sol_profiler/installation.md | 17 + .../website/md/docs/sol_profiler/introduction.md | 1 + packages/website/md/docs/sol_profiler/usage.md | 62 ++++ packages/website/md/docs/sol_trace/installation.md | 17 + packages/website/md/docs/sol_trace/introduction.md | 1 + packages/website/md/docs/sol_trace/usage.md | 62 ++++ .../ts/containers/asset_buyer_documentation.ts | 38 +- .../website/ts/containers/connect_documentation.ts | 38 +- .../containers/contract_wrappers_documentation.ts | 38 +- .../ts/containers/ethereum_types_documentation.ts | 36 +- .../ts/containers/json_schemas_documentation.ts | 38 +- .../ts/containers/migrations_documentation.ts | 2 +- .../ts/containers/order_utils_documentation.ts | 38 +- .../ts/containers/order_watcher_documentation.ts | 38 +- .../ts/containers/smart_contracts_documentation.ts | 36 +- .../ts/containers/sol_compiler_documentation.ts | 38 +- .../website/ts/containers/sol_cov_documentation.ts | 77 ---- .../ts/containers/sol_coverage_documentation.ts | 43 +++ .../ts/containers/sol_profiler_documentation.ts | 43 +++ .../ts/containers/sol_trace_documentation.ts | 43 +++ .../ts/containers/subproviders_documentation.ts | 38 +- .../ts/containers/web3_wrapper_documentation.ts | 38 +- .../ts/containers/zero_ex_js_documentation.ts | 38 +- packages/website/ts/index.tsx | 23 +- .../website/ts/pages/documentation/doc_page.tsx | 4 +- .../website/ts/pages/documentation/docs_home.tsx | 22 +- packages/website/ts/types.ts | 8 +- .../website/ts/utils/documentation_container.ts | 35 ++ 149 files changed, 4557 insertions(+), 4047 deletions(-) delete mode 100644 packages/sol-cov/.npmignore delete mode 100644 packages/sol-cov/CHANGELOG.json delete mode 100644 packages/sol-cov/CHANGELOG.md delete mode 100644 packages/sol-cov/README.md delete mode 100644 packages/sol-cov/compiler.json delete mode 100644 packages/sol-cov/coverage/.gitkeep delete mode 100644 packages/sol-cov/package.json delete mode 100644 packages/sol-cov/src/artifact_adapters/abstract_artifact_adapter.ts delete mode 100644 packages/sol-cov/src/artifact_adapters/sol_compiler_artifact_adapter.ts delete mode 100644 packages/sol-cov/src/artifact_adapters/truffle_artifact_adapter.ts delete mode 100644 packages/sol-cov/src/ast_visitor.ts delete mode 100644 packages/sol-cov/src/collect_coverage_entries.ts delete mode 100644 packages/sol-cov/src/constants.ts delete mode 100644 packages/sol-cov/src/coverage_subprovider.ts delete mode 100644 packages/sol-cov/src/get_source_range_snippet.ts delete mode 100644 packages/sol-cov/src/globals.d.ts delete mode 100644 packages/sol-cov/src/index.ts delete mode 100644 packages/sol-cov/src/instructions.ts delete mode 100644 packages/sol-cov/src/profiler_subprovider.ts delete mode 100644 packages/sol-cov/src/revert_trace.ts delete mode 100644 packages/sol-cov/src/revert_trace_subprovider.ts delete mode 100644 packages/sol-cov/src/source_maps.ts delete mode 100644 packages/sol-cov/src/trace.ts delete mode 100644 packages/sol-cov/src/trace_collection_subprovider.ts delete mode 100644 packages/sol-cov/src/trace_collector.ts delete mode 100644 packages/sol-cov/src/trace_info_subprovider.ts delete mode 100644 packages/sol-cov/src/types.ts delete mode 100644 packages/sol-cov/src/utils.ts delete mode 100644 packages/sol-cov/test/collect_coverage_entries_test.ts delete mode 100644 packages/sol-cov/test/fixtures/contracts/AllSolidityFeatures.sol delete mode 100644 packages/sol-cov/test/fixtures/contracts/SimpleStorage.sol delete mode 100644 packages/sol-cov/test/fixtures/contracts/Simplest.sol delete mode 100644 packages/sol-cov/test/fixtures/contracts/SolcovIgnore.sol delete mode 100644 packages/sol-cov/test/instructions_test.ts delete mode 100644 packages/sol-cov/test/sol_compiler_artifact_adapter_test.ts delete mode 100644 packages/sol-cov/test/source_maps_test.ts delete mode 100644 packages/sol-cov/test/trace_test.ts delete mode 100644 packages/sol-cov/test/utils_test.ts delete mode 100644 packages/sol-cov/tsconfig.json delete mode 100644 packages/sol-cov/tslint.json delete mode 100644 packages/sol-cov/typedoc-tsconfig.json create mode 100644 packages/sol-coverage/.npmignore create mode 100644 packages/sol-coverage/CHANGELOG.json create mode 100644 packages/sol-coverage/README.md create mode 100644 packages/sol-coverage/package.json create mode 100644 packages/sol-coverage/src/coverage_subprovider.ts create mode 100644 packages/sol-coverage/src/globals.d.ts create mode 100644 packages/sol-coverage/src/index.ts create mode 100644 packages/sol-coverage/tsconfig.json create mode 100644 packages/sol-coverage/tslint.json create mode 100644 packages/sol-coverage/typedoc-tsconfig.json create mode 100644 packages/sol-profiler/.npmignore create mode 100644 packages/sol-profiler/CHANGELOG.json create mode 100644 packages/sol-profiler/README.md create mode 100644 packages/sol-profiler/package.json create mode 100644 packages/sol-profiler/src/globals.d.ts create mode 100644 packages/sol-profiler/src/index.ts create mode 100644 packages/sol-profiler/src/profiler_subprovider.ts create mode 100644 packages/sol-profiler/tsconfig.json create mode 100644 packages/sol-profiler/tslint.json create mode 100644 packages/sol-profiler/typedoc-tsconfig.json create mode 100644 packages/sol-trace-based-tools-common/.npmignore create mode 100644 packages/sol-trace-based-tools-common/CHANGELOG.json create mode 100644 packages/sol-trace-based-tools-common/CHANGELOG.md create mode 100644 packages/sol-trace-based-tools-common/README.md create mode 100644 packages/sol-trace-based-tools-common/compiler.json create mode 100644 packages/sol-trace-based-tools-common/coverage/.gitkeep create mode 100644 packages/sol-trace-based-tools-common/package.json create mode 100644 packages/sol-trace-based-tools-common/src/artifact_adapters/abstract_artifact_adapter.ts create mode 100644 packages/sol-trace-based-tools-common/src/artifact_adapters/sol_compiler_artifact_adapter.ts create mode 100644 packages/sol-trace-based-tools-common/src/artifact_adapters/truffle_artifact_adapter.ts create mode 100644 packages/sol-trace-based-tools-common/src/ast_visitor.ts create mode 100644 packages/sol-trace-based-tools-common/src/collect_coverage_entries.ts create mode 100644 packages/sol-trace-based-tools-common/src/constants.ts create mode 100644 packages/sol-trace-based-tools-common/src/get_source_range_snippet.ts create mode 100644 packages/sol-trace-based-tools-common/src/globals.d.ts create mode 100644 packages/sol-trace-based-tools-common/src/index.ts create mode 100644 packages/sol-trace-based-tools-common/src/instructions.ts create mode 100644 packages/sol-trace-based-tools-common/src/revert_trace.ts create mode 100644 packages/sol-trace-based-tools-common/src/source_maps.ts create mode 100644 packages/sol-trace-based-tools-common/src/trace.ts create mode 100644 packages/sol-trace-based-tools-common/src/trace_collection_subprovider.ts create mode 100644 packages/sol-trace-based-tools-common/src/trace_collector.ts create mode 100644 packages/sol-trace-based-tools-common/src/trace_info_subprovider.ts create mode 100644 packages/sol-trace-based-tools-common/src/types.ts create mode 100644 packages/sol-trace-based-tools-common/src/utils.ts create mode 100644 packages/sol-trace-based-tools-common/test/collect_coverage_entries_test.ts create mode 100644 packages/sol-trace-based-tools-common/test/fixtures/contracts/AllSolidityFeatures.sol create mode 100644 packages/sol-trace-based-tools-common/test/fixtures/contracts/SimpleStorage.sol create mode 100644 packages/sol-trace-based-tools-common/test/fixtures/contracts/Simplest.sol create mode 100644 packages/sol-trace-based-tools-common/test/fixtures/contracts/SolcovIgnore.sol create mode 100644 packages/sol-trace-based-tools-common/test/instructions_test.ts create mode 100644 packages/sol-trace-based-tools-common/test/sol_compiler_artifact_adapter_test.ts create mode 100644 packages/sol-trace-based-tools-common/test/source_maps_test.ts create mode 100644 packages/sol-trace-based-tools-common/test/trace_test.ts create mode 100644 packages/sol-trace-based-tools-common/test/utils_test.ts create mode 100644 packages/sol-trace-based-tools-common/tsconfig.json create mode 100644 packages/sol-trace-based-tools-common/tslint.json create mode 100644 packages/sol-trace/.npmignore create mode 100644 packages/sol-trace/CHANGELOG.json create mode 100644 packages/sol-trace/README.md create mode 100644 packages/sol-trace/package.json create mode 100644 packages/sol-trace/src/globals.d.ts create mode 100644 packages/sol-trace/src/index.ts create mode 100644 packages/sol-trace/src/revert_trace_subprovider.ts create mode 100644 packages/sol-trace/tsconfig.json create mode 100644 packages/sol-trace/tslint.json create mode 100644 packages/sol-trace/typedoc-tsconfig.json delete mode 100644 packages/website/md/docs/sol_cov/1/installation.md delete mode 100644 packages/website/md/docs/sol_cov/1/introduction.md delete mode 100644 packages/website/md/docs/sol_cov/1/usage.md delete mode 100644 packages/website/md/docs/sol_cov/2/installation.md delete mode 100644 packages/website/md/docs/sol_cov/2/introduction.md delete mode 100644 packages/website/md/docs/sol_cov/2/usage.md create mode 100644 packages/website/md/docs/sol_coverage/installation.md create mode 100644 packages/website/md/docs/sol_coverage/introduction.md create mode 100644 packages/website/md/docs/sol_coverage/usage.md create mode 100644 packages/website/md/docs/sol_profiler/installation.md create mode 100644 packages/website/md/docs/sol_profiler/introduction.md create mode 100644 packages/website/md/docs/sol_profiler/usage.md create mode 100644 packages/website/md/docs/sol_trace/installation.md create mode 100644 packages/website/md/docs/sol_trace/introduction.md create mode 100644 packages/website/md/docs/sol_trace/usage.md delete mode 100644 packages/website/ts/containers/sol_cov_documentation.ts create mode 100644 packages/website/ts/containers/sol_coverage_documentation.ts create mode 100644 packages/website/ts/containers/sol_profiler_documentation.ts create mode 100644 packages/website/ts/containers/sol_trace_documentation.ts create mode 100644 packages/website/ts/utils/documentation_container.ts (limited to 'packages') diff --git a/packages/metacoin/package.json b/packages/metacoin/package.json index 0e9fa4920..33fc16a3e 100644 --- a/packages/metacoin/package.json +++ b/packages/metacoin/package.json @@ -32,7 +32,8 @@ "@0x/abi-gen": "^1.0.19", "@0x/abi-gen-templates": "^1.0.1", "@0x/base-contract": "^3.0.10", - "@0x/sol-cov": "^2.1.16", + "@0x/sol-coverage": "^1.0.0", + "@0x/sol-profiler": "^1.0.0", "@0x/subproviders": "^2.1.8", "@0x/tslint-config": "^2.0.0", "@0x/types": "^1.4.1", diff --git a/packages/metacoin/test/utils/coverage.ts b/packages/metacoin/test/utils/coverage.ts index 31275a163..1a06d8c3c 100644 --- a/packages/metacoin/test/utils/coverage.ts +++ b/packages/metacoin/test/utils/coverage.ts @@ -1,5 +1,5 @@ import { devConstants } from '@0x/dev-utils'; -import { CoverageSubprovider, SolCompilerArtifactAdapter } from '@0x/sol-cov'; +import { CoverageSubprovider, SolCompilerArtifactAdapter } from '@0x/sol-coverage'; import * as _ from 'lodash'; import { config } from './config'; diff --git a/packages/metacoin/test/utils/profiler.ts b/packages/metacoin/test/utils/profiler.ts index e7c373d20..6e6fc309f 100644 --- a/packages/metacoin/test/utils/profiler.ts +++ b/packages/metacoin/test/utils/profiler.ts @@ -1,5 +1,5 @@ import { devConstants } from '@0x/dev-utils'; -import { ProfilerSubprovider, SolCompilerArtifactAdapter } from '@0x/sol-cov'; +import { ProfilerSubprovider, SolCompilerArtifactAdapter } from '@0x/sol-profiler'; import * as _ from 'lodash'; import { config } from './config'; diff --git a/packages/sol-cov/.npmignore b/packages/sol-cov/.npmignore deleted file mode 100644 index 037786e46..000000000 --- a/packages/sol-cov/.npmignore +++ /dev/null @@ -1,6 +0,0 @@ -.* -yarn-error.log -/src/ -/scripts/ -tsconfig.json -/lib/src/monorepo_scripts/ diff --git a/packages/sol-cov/CHANGELOG.json b/packages/sol-cov/CHANGELOG.json deleted file mode 100644 index b7973c135..000000000 --- a/packages/sol-cov/CHANGELOG.json +++ /dev/null @@ -1,389 +0,0 @@ -[ - { - "version": "2.1.16", - "changes": [ - { - "note": "Dependencies updated" - } - ], - "timestamp": 1544739608 - }, - { - "version": "2.1.15", - "changes": [ - { - "note": "Dependencies updated" - } - ], - "timestamp": 1544570656 - }, - { - "timestamp": 1543401373, - "version": "2.1.14", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1542821676, - "version": "2.1.13", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1542208198, - "version": "2.1.12", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1542134075, - "version": "2.1.11", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1542028948, - "version": "2.1.10", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "version": "2.1.9", - "changes": [ - { - "note": "Dependencies updated" - } - ], - "timestamp": 1541740904 - }, - { - "version": "2.1.8", - "changes": [ - { - "note": "Make @types/solidity-parser-antlr a 'dependency' so it's available to users of the library", - "pr": 1105 - } - ], - "timestamp": 1539871071 - }, - { - "version": "2.1.7", - "changes": [ - { - "note": "Dependencies updated" - } - ], - "timestamp": 1538693146 - }, - { - "timestamp": 1538157789, - "version": "2.1.6", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1537907159, - "version": "2.1.5", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1537875740, - "version": "2.1.4", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1537541580, - "version": "2.1.3", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1536142250, - "version": "2.1.2", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1535377027, - "version": "2.1.1", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "version": "2.1.0", - "changes": [ - { - "note": - "Export types: `JSONRPCRequestPayload`, `Provider`, `JSONRPCErrorCallback`, `JSONRPCResponsePayload`, `JSONRPCRequestPayloadWithMethod`, `NextCallback`, `ErrorCallback`, `OnNextCompleted` and `Callback`", - "pr": 924 - } - ], - "timestamp": 1535133899 - }, - { - "version": "2.0.0", - "changes": [ - { - "note": - "Fix a bug when eth_call coverage was not computed because of silent schema validation failures", - "pr": 938 - }, - { - "note": "Make `TruffleArtifactAdapter` read the `truffle.js` config for `solc` settings", - "pr": 938 - }, - { - "note": - "Change the first param of `TruffleArtifactAdapter` to be the `projectRoot` instead of `sourcesDir`", - "pr": 938 - }, - { - "note": - "Throw a helpful error message if truffle artifacts were generated with a different solc version than the one passed in", - "pr": 938 - } - ], - "timestamp": 1534210131 - }, - { - "version": "1.0.3", - "changes": [ - { - "note": "Dependencies updated" - } - ], - "timestamp": 1532619515 - }, - { - "timestamp": 1532605697, - "version": "1.0.2", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1532357734, - "version": "1.0.1", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1532043000, - "version": "1.0.0", - "changes": [ - { - "note": - "Add artifact adapter as a parameter for `CoverageSubprovider`. Export `AbstractArtifactAdapter`", - "pr": 589 - }, - { - "note": "Implement `SolCompilerArtifactAdapter` and `TruffleArtifactAdapter`", - "pr": 589 - }, - { - "note": "Properly parse multi-level traces", - "pr": 589 - }, - { - "note": "Add support for solidity libraries", - "pr": 589 - }, - { - "note": "Fixed a bug causing `RegExp` to crash if contract code is longer that 32767 characters", - "pr": 675 - }, - { - "note": "Fixed a bug caused by Geth debug trace depth being 1indexed", - "pr": 675 - }, - { - "note": "Fixed a bug when the tool crashed on empty traces", - "pr": 675 - }, - { - "note": "Use `BlockchainLifecycle` to support reverts on Geth", - "pr": 675 - }, - { - "note": "Add `ProfilerSubprovider` as a hacky way to profile code using coverage tools", - "pr": 675 - }, - { - "note": "Collect traces from `estimate_gas` calls", - "pr": 675 - }, - { - "note": "Fix a race condition caused by not awaiting the transaction before getting a trace", - "pr": 675 - }, - { - "note": "Add `start`/`stop` functionality to `CoverageSubprovider` and `ProfilerSubprovider`", - "pr": 675 - }, - { - "note": "Skip interface artifacts with a warning instead of failing", - "pr": 675 - }, - { - "note": "Fix `solcVersion` regex in parameter validation", - "pr": 690 - }, - { - "note": - "Fix a bug when in `TruffleArtifactsAdapter` causing it to throw if `compiler.json` is not there", - "pr": 690 - }, - { - "note": "HUGE perf improvements", - "pr": 690 - }, - { - "note": "Create `RevertTraceSubprovider` which prints a stack trace when a `REVERT` is detected", - "pr": 705 - }, - { - "note": "Add source code snippets to stack traces printed by `RevertTraceSubprovider`", - "pr": 725 - } - ] - }, - { - "timestamp": 1531919263, - "version": "0.1.3", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1531149657, - "version": "0.1.2", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1529397769, - "version": "0.1.1", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "version": "0.1.0", - "changes": [ - { - "note": "Incorrect publish that was unpublished" - } - ], - "timestamp": 1527810075 - }, - { - "timestamp": 1527009134, - "version": "0.0.11", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1525477860, - "version": "0.0.10", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1525428773, - "version": "0.0.9", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1524044013, - "version": "0.0.8", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1523462196, - "version": "0.0.7", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1522673609, - "version": "0.0.6", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1522658513, - "version": "0.0.5", - "changes": [ - { - "note": "Dependencies updated" - } - ] - } -] diff --git a/packages/sol-cov/CHANGELOG.md b/packages/sol-cov/CHANGELOG.md deleted file mode 100644 index 879ef9c95..000000000 --- a/packages/sol-cov/CHANGELOG.md +++ /dev/null @@ -1,158 +0,0 @@ - - -CHANGELOG - -## v2.1.16 - _December 13, 2018_ - - * Dependencies updated - -## v2.1.15 - _December 11, 2018_ - - * Dependencies updated - -## v2.1.14 - _November 28, 2018_ - - * Dependencies updated - -## v2.1.13 - _November 21, 2018_ - - * Dependencies updated - -## v2.1.12 - _November 14, 2018_ - - * Dependencies updated - -## v2.1.11 - _November 13, 2018_ - - * Dependencies updated - -## v2.1.10 - _November 12, 2018_ - - * Dependencies updated - -## v2.1.9 - _November 9, 2018_ - - * Dependencies updated - -## v2.1.8 - _October 18, 2018_ - - * Make @types/solidity-parser-antlr a 'dependency' so it's available to users of the library (#1105) - -## v2.1.7 - _October 4, 2018_ - - * Dependencies updated - -## v2.1.6 - _September 28, 2018_ - - * Dependencies updated - -## v2.1.5 - _September 25, 2018_ - - * Dependencies updated - -## v2.1.4 - _September 25, 2018_ - - * Dependencies updated - -## v2.1.3 - _September 21, 2018_ - - * Dependencies updated - -## v2.1.2 - _September 5, 2018_ - - * Dependencies updated - -## v2.1.1 - _August 27, 2018_ - - * Dependencies updated - -## v2.1.0 - _August 24, 2018_ - - * Export types: `JSONRPCRequestPayload`, `Provider`, `JSONRPCErrorCallback`, `JSONRPCResponsePayload`, `JSONRPCRequestPayloadWithMethod`, `NextCallback`, `ErrorCallback`, `OnNextCompleted` and `Callback` (#924) - -## v2.0.0 - _August 14, 2018_ - - * Fix a bug when eth_call coverage was not computed because of silent schema validation failures (#938) - * Make `TruffleArtifactAdapter` read the `truffle.js` config for `solc` settings (#938) - * Change the first param of `TruffleArtifactAdapter` to be the `projectRoot` instead of `sourcesDir` (#938) - * Throw a helpful error message if truffle artifacts were generated with a different solc version than the one passed in (#938) - -## v1.0.3 - _July 26, 2018_ - - * Dependencies updated - -## v1.0.2 - _July 26, 2018_ - - * Dependencies updated - -## v1.0.1 - _July 23, 2018_ - - * Dependencies updated - -## v1.0.0 - _July 19, 2018_ - - * Add artifact adapter as a parameter for `CoverageSubprovider`. Export `AbstractArtifactAdapter` (#589) - * Implement `SolCompilerArtifactAdapter` and `TruffleArtifactAdapter` (#589) - * Properly parse multi-level traces (#589) - * Add support for solidity libraries (#589) - * Fixed a bug causing `RegExp` to crash if contract code is longer that 32767 characters (#675) - * Fixed a bug caused by Geth debug trace depth being 1indexed (#675) - * Fixed a bug when the tool crashed on empty traces (#675) - * Use `BlockchainLifecycle` to support reverts on Geth (#675) - * Add `ProfilerSubprovider` as a hacky way to profile code using coverage tools (#675) - * Collect traces from `estimate_gas` calls (#675) - * Fix a race condition caused by not awaiting the transaction before getting a trace (#675) - * Add `start`/`stop` functionality to `CoverageSubprovider` and `ProfilerSubprovider` (#675) - * Skip interface artifacts with a warning instead of failing (#675) - * Fix `solcVersion` regex in parameter validation (#690) - * Fix a bug when in `TruffleArtifactsAdapter` causing it to throw if `compiler.json` is not there (#690) - * HUGE perf improvements (#690) - * Create `RevertTraceSubprovider` which prints a stack trace when a `REVERT` is detected (#705) - * Add source code snippets to stack traces printed by `RevertTraceSubprovider` (#725) - -## v0.1.3 - _July 18, 2018_ - - * Dependencies updated - -## v0.1.2 - _July 9, 2018_ - - * Dependencies updated - -## v0.1.1 - _June 19, 2018_ - - * Dependencies updated - -## v0.1.0 - _May 31, 2018_ - - * Incorrect publish that was unpublished - -## v0.0.11 - _May 22, 2018_ - - * Dependencies updated - -## v0.0.10 - _May 4, 2018_ - - * Dependencies updated - -## v0.0.9 - _May 4, 2018_ - - * Dependencies updated - -## v0.0.8 - _April 18, 2018_ - - * Dependencies updated - -## v0.0.7 - _April 11, 2018_ - - * Dependencies updated - -## v0.0.6 - _April 2, 2018_ - - * Dependencies updated - -## v0.0.5 - _April 2, 2018_ - - * Dependencies updated diff --git a/packages/sol-cov/README.md b/packages/sol-cov/README.md deleted file mode 100644 index 31d73dc63..000000000 --- a/packages/sol-cov/README.md +++ /dev/null @@ -1,75 +0,0 @@ -## @0x/sol-cov - -A Solidity code coverage tool. - -### Read the [Documentation](https://0xproject.com/docs/sol-cov). - -## Installation - -```bash -yarn add @0x/sol-cov -``` - -**Import** - -```javascript -import { CoverageSubprovider } from '@0x/sol-cov'; -``` - -or - -```javascript -var CoverageSubprovider = require('@0x/sol-cov').CoverageSubprovider; -``` - -## Contributing - -We welcome improvements and fixes from the wider community! 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 -``` - -### Build - -To build this package and all other monorepo packages that it depends on, run the following from the monorepo root directory: - -```bash -PKG=@0x/sol-cov yarn build -``` - -Or continuously rebuild on change: - -```bash -PKG=@0x/sol-cov yarn watch -``` - -### Clean - -```bash -yarn clean -``` - -### Lint - -```bash -yarn lint -``` - -### Run Tests - -```bash -yarn test -``` diff --git a/packages/sol-cov/compiler.json b/packages/sol-cov/compiler.json deleted file mode 100644 index a6a0c6d3a..000000000 --- a/packages/sol-cov/compiler.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "contracts": ["SimpleStorage"], - "contractsDir": "test/fixtures/contracts", - "artifactsDir": "test/fixtures/artifacts", - "compilerSettings": { - "outputSelection": { - "*": { - "*": [ - "abi", - "evm.bytecode.object", - "evm.bytecode.sourceMap", - "evm.deployedBytecode.object", - "evm.deployedBytecode.sourceMap" - ] - } - } - } -} diff --git a/packages/sol-cov/coverage/.gitkeep b/packages/sol-cov/coverage/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/sol-cov/package.json b/packages/sol-cov/package.json deleted file mode 100644 index 3ade51c80..000000000 --- a/packages/sol-cov/package.json +++ /dev/null @@ -1,87 +0,0 @@ -{ - "name": "@0x/sol-cov", - "version": "2.1.16", - "engines": { - "node": ">=6.12" - }, - "description": "Generate coverage reports for Solidity code", - "main": "lib/src/index.js", - "types": "lib/src/index.d.ts", - "scripts": { - "build": "yarn pre_build && tsc -b", - "build:ci": "yarn build", - "pre_build": "run-s copy_test_fixtures", - "lint": "tslint --format stylish --project .", - "test": "run-s compile_test run_mocha", - "rebuild_and_test": "run-s clean build test", - "test:coverage": "nyc npm run test --all && yarn coverage:report:lcov", - "coverage:report:lcov": "nyc report --reporter=text-lcov > coverage/lcov.info", - "test:circleci": "yarn test:coverage", - "run_mocha": "mocha --require source-map-support/register --require make-promises-safe lib/test/**/*_test.js --exit", - "clean": "shx rm -rf lib test/fixtures/artifacts src/artifacts generated_docs", - "copy_test_fixtures": "copyfiles 'test/fixtures/**/*' ./lib", - "compile_test": "sol-compiler compile", - "docs:json": "typedoc --excludePrivate --excludeExternals --target ES5 --tsconfig typedoc-tsconfig.json --json $JSON_FILE_PATH $PROJECT_FILES" - }, - "config": { - "postpublish": { - "assets": [], - "docOmitExports": [ - "ProfilerSubprovider", - "RevertTraceSubprovider" - ] - } - }, - "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": { - "@0x/dev-utils": "^1.0.21", - "@0x/sol-compiler": "^1.1.16", - "@0x/subproviders": "^2.1.8", - "@0x/typescript-typings": "^3.0.6", - "@0x/utils": "^2.0.8", - "@0x/web3-wrapper": "^3.2.1", - "@types/solidity-parser-antlr": "^0.2.0", - "ethereum-types": "^1.1.4", - "ethereumjs-util": "^5.1.1", - "glob": "^7.1.2", - "istanbul": "^0.4.5", - "lodash": "^4.17.5", - "loglevel": "^1.6.1", - "mkdirp": "^0.5.1", - "rimraf": "^2.6.2", - "semaphore-async-await": "^1.5.1", - "solidity-parser-antlr": "^0.2.12" - }, - "devDependencies": { - "@0x/tslint-config": "^2.0.0", - "@types/istanbul": "^0.4.30", - "@types/loglevel": "^1.5.3", - "@types/mkdirp": "^0.5.1", - "@types/mocha": "^2.2.42", - "@types/node": "*", - "@types/rimraf": "^2.0.2", - "chai": "^4.0.1", - "copyfiles": "^2.0.0", - "dirty-chai": "^2.0.1", - "make-promises-safe": "^1.1.0", - "mocha": "^4.1.0", - "npm-run-all": "^4.1.2", - "nyc": "^11.0.1", - "shx": "^0.2.2", - "sinon": "^4.0.0", - "tslint": "5.11.0", - "typedoc": "0.13.0", - "typescript": "3.0.1" - }, - "publishConfig": { - "access": "public" - } -} diff --git a/packages/sol-cov/src/artifact_adapters/abstract_artifact_adapter.ts b/packages/sol-cov/src/artifact_adapters/abstract_artifact_adapter.ts deleted file mode 100644 index fcc6562ad..000000000 --- a/packages/sol-cov/src/artifact_adapters/abstract_artifact_adapter.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { ContractData } from '../types'; - -export abstract class AbstractArtifactAdapter { - public abstract async collectContractsDataAsync(): Promise; -} diff --git a/packages/sol-cov/src/artifact_adapters/sol_compiler_artifact_adapter.ts b/packages/sol-cov/src/artifact_adapters/sol_compiler_artifact_adapter.ts deleted file mode 100644 index 57391abbe..000000000 --- a/packages/sol-cov/src/artifact_adapters/sol_compiler_artifact_adapter.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { logUtils } from '@0x/utils'; -import { CompilerOptions, ContractArtifact } from 'ethereum-types'; -import * as fs from 'fs'; -import * as glob from 'glob'; -import * as _ from 'lodash'; -import * as path from 'path'; - -import { ContractData } from '../types'; - -import { AbstractArtifactAdapter } from './abstract_artifact_adapter'; - -const CONFIG_FILE = 'compiler.json'; - -export class SolCompilerArtifactAdapter extends AbstractArtifactAdapter { - private readonly _artifactsPath: string; - private readonly _sourcesPath: string; - /** - * Instantiates a SolCompilerArtifactAdapter - * @param artifactsPath Path to your artifacts directory - * @param sourcesPath Path to your contract sources directory - */ - constructor(artifactsPath?: string, sourcesPath?: string) { - super(); - const config: CompilerOptions = fs.existsSync(CONFIG_FILE) - ? JSON.parse(fs.readFileSync(CONFIG_FILE).toString()) - : {}; - if (_.isUndefined(artifactsPath) && _.isUndefined(config.artifactsDir)) { - throw new Error(`artifactsDir not found in ${CONFIG_FILE}`); - } - this._artifactsPath = (artifactsPath || config.artifactsDir) as string; - if (_.isUndefined(sourcesPath) && _.isUndefined(config.contractsDir)) { - throw new Error(`contractsDir not found in ${CONFIG_FILE}`); - } - this._sourcesPath = (sourcesPath || config.contractsDir) as string; - } - public async collectContractsDataAsync(): Promise { - const artifactsGlob = `${this._artifactsPath}/**/*.json`; - const artifactFileNames = glob.sync(artifactsGlob, { absolute: true }); - const contractsData: ContractData[] = []; - for (const artifactFileName of artifactFileNames) { - const artifact: ContractArtifact = JSON.parse(fs.readFileSync(artifactFileName).toString()); - if (_.isUndefined(artifact.compilerOutput.evm)) { - logUtils.warn(`${artifactFileName} doesn't contain bytecode. Skipping...`); - continue; - } - let sources = _.keys(artifact.sources); - sources = _.map(sources, relativeFilePath => path.resolve(this._sourcesPath, relativeFilePath)); - const sourceCodes = _.map(sources, (source: string) => fs.readFileSync(source).toString()); - const contractData = { - sourceCodes, - sources, - bytecode: artifact.compilerOutput.evm.bytecode.object, - sourceMap: artifact.compilerOutput.evm.bytecode.sourceMap, - runtimeBytecode: artifact.compilerOutput.evm.deployedBytecode.object, - sourceMapRuntime: artifact.compilerOutput.evm.deployedBytecode.sourceMap, - }; - contractsData.push(contractData); - } - return contractsData; - } -} diff --git a/packages/sol-cov/src/artifact_adapters/truffle_artifact_adapter.ts b/packages/sol-cov/src/artifact_adapters/truffle_artifact_adapter.ts deleted file mode 100644 index f064911d3..000000000 --- a/packages/sol-cov/src/artifact_adapters/truffle_artifact_adapter.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { Compiler, CompilerOptions } from '@0x/sol-compiler'; -import * as fs from 'fs'; -import * as glob from 'glob'; -import * as path from 'path'; - -import { ContractData } from '../types'; - -import { AbstractArtifactAdapter } from './abstract_artifact_adapter'; -import { SolCompilerArtifactAdapter } from './sol_compiler_artifact_adapter'; - -const DEFAULT_TRUFFLE_ARTIFACTS_DIR = './build/contracts'; - -interface TruffleConfig { - solc?: any; - contracts_build_directory?: string; -} - -export class TruffleArtifactAdapter extends AbstractArtifactAdapter { - private readonly _solcVersion: string; - private readonly _projectRoot: string; - /** - * Instantiates a TruffleArtifactAdapter - * @param projectRoot Path to the truffle project's root directory - * @param solcVersion Solidity version with which to compile all the contracts - */ - constructor(projectRoot: string, solcVersion: string) { - super(); - this._solcVersion = solcVersion; - this._projectRoot = projectRoot; - } - public async collectContractsDataAsync(): Promise { - const artifactsDir = '.0x-artifacts'; - const contractsDir = path.join(this._projectRoot, 'contracts'); - const truffleConfig = this._getTruffleConfig(); - const solcConfig = truffleConfig.solc || {}; - const truffleArtifactsDirectory = truffleConfig.contracts_build_directory || DEFAULT_TRUFFLE_ARTIFACTS_DIR; - this._assertSolidityVersionIsCorrect(truffleArtifactsDirectory); - const compilerOptions: CompilerOptions = { - contractsDir, - artifactsDir, - compilerSettings: { - ...solcConfig, - outputSelection: { - ['*']: { - ['*']: ['abi', 'evm.bytecode.object', 'evm.deployedBytecode.object'], - }, - }, - }, - contracts: '*', - solcVersion: this._solcVersion, - }; - const compiler = new Compiler(compilerOptions); - await compiler.compileAsync(); - const solCompilerArtifactAdapter = new SolCompilerArtifactAdapter(artifactsDir, contractsDir); - const contractsDataFrom0xArtifacts = await solCompilerArtifactAdapter.collectContractsDataAsync(); - return contractsDataFrom0xArtifacts; - } - private _getTruffleConfig(): TruffleConfig { - const truffleConfigFileShort = path.resolve(path.join(this._projectRoot, 'truffle.js')); - const truffleConfigFileLong = path.resolve(path.join(this._projectRoot, 'truffle-config.js')); - if (fs.existsSync(truffleConfigFileShort)) { - const truffleConfig = require(truffleConfigFileShort); - return truffleConfig; - } else if (fs.existsSync(truffleConfigFileLong)) { - const truffleConfig = require(truffleConfigFileLong); - return truffleConfig; - } else { - throw new Error( - `Neither ${truffleConfigFileShort} nor ${truffleConfigFileLong} exists. Make sure the project root is correct`, - ); - } - } - private _assertSolidityVersionIsCorrect(truffleArtifactsDirectory: string): void { - const artifactsGlob = `${truffleArtifactsDirectory}/**/*.json`; - const artifactFileNames = glob.sync(artifactsGlob, { absolute: true }); - for (const artifactFileName of artifactFileNames) { - const artifact = JSON.parse(fs.readFileSync(artifactFileName).toString()); - const compilerVersion = artifact.compiler.version; - if (!compilerVersion.startsWith(this._solcVersion)) { - throw new Error( - `${artifact.contractName} was compiled with solidity ${compilerVersion} but specified version is ${ - this._solcVersion - } making it impossible for sol-cov to process traces`, - ); - } - } - } -} diff --git a/packages/sol-cov/src/ast_visitor.ts b/packages/sol-cov/src/ast_visitor.ts deleted file mode 100644 index e55cdf6ec..000000000 --- a/packages/sol-cov/src/ast_visitor.ts +++ /dev/null @@ -1,168 +0,0 @@ -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 readonly _fnMap: FnMap = {}; - private readonly _branchMap: BranchMap = {}; - private readonly _modifiersStatementIds: number[] = []; - private readonly _statementMap: StatementMap = {}; - private readonly _locationByOffset: LocationByOffset; - private readonly _ignoreRangesBeginningAt: number[]; - // keep track of contract/function ranges that are to be ignored - // so we can also ignore any children nodes within the contract/function - private readonly _ignoreRangesWithin: Array<[number, number]> = []; - constructor(locationByOffset: LocationByOffset, ignoreRangesBeginningAt: number[] = []) { - this._locationByOffset = locationByOffset; - this._ignoreRangesBeginningAt = ignoreRangesBeginningAt; - } - 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 ContractDefinition(ast: Parser.ContractDefinition): void { - if (this._shouldIgnoreExpression(ast)) { - this._ignoreRangesWithin.push(ast.range as [number, number]); - } - } - 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 EmitStatement(ast: any /* TODO: Parser.EmitStatement */): 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)) { - if (this._shouldIgnoreExpression(ast)) { - return; - } - this._modifiersStatementIds.push(this._entryId); - this._visitStatement(ast); - } - } - private _visitBinaryBranch( - ast: Parser.ASTNode, - left: Parser.ASTNode, - right: Parser.ASTNode, - type: BranchType, - ): void { - if (this._shouldIgnoreExpression(ast)) { - return; - } - this._branchMap[this._entryId++] = { - line: this._getExpressionRange(ast).start.line, - type, - locations: [this._getExpressionRange(left), this._getExpressionRange(right)], - }; - } - private _visitStatement(ast: Parser.ASTNode): void { - if (this._shouldIgnoreExpression(ast)) { - return; - } - this._statementMap[this._entryId++] = this._getExpressionRange(ast); - } - private _getExpressionRange(ast: Parser.ASTNode): SingleFileSourceRange { - const astRange = ast.range as [number, number]; - const start = this._locationByOffset[astRange[0]]; - const end = this._locationByOffset[astRange[1] + 1]; - const range = { - start, - end, - }; - return range; - } - private _shouldIgnoreExpression(ast: Parser.ASTNode): boolean { - const [astStart, astEnd] = ast.range as [number, number]; - const isRangeIgnored = _.some( - this._ignoreRangesWithin, - ([rangeStart, rangeEnd]: [number, number]) => astStart >= rangeStart && astEnd <= rangeEnd, - ); - return this._ignoreRangesBeginningAt.includes(astStart) || isRangeIgnored; - } - private _visitFunctionLikeDefinition(ast: Parser.ModifierDefinition | Parser.FunctionDefinition): void { - if (this._shouldIgnoreExpression(ast)) { - this._ignoreRangesWithin.push(ast.range as [number, number]); - return; - } - const loc = this._getExpressionRange(ast); - this._fnMap[this._entryId++] = { - name: ast.name, - line: loc.start.line, - loc, - }; - this._visitStatement(ast); - } -} diff --git a/packages/sol-cov/src/collect_coverage_entries.ts b/packages/sol-cov/src/collect_coverage_entries.ts deleted file mode 100644 index bdbcd613e..000000000 --- a/packages/sol-cov/src/collect_coverage_entries.ts +++ /dev/null @@ -1,41 +0,0 @@ -import * as ethUtil from 'ethereumjs-util'; -import * as _ from 'lodash'; -import * as parser from 'solidity-parser-antlr'; - -import { ASTVisitor, CoverageEntriesDescription } from './ast_visitor'; -import { getLocationByOffset } from './source_maps'; - -const IGNORE_RE = /\/\*\s*solcov\s+ignore\s+next\s*\*\/\s*/gm; - -// Parsing source code for each transaction/code is slow and therefore we cache it -const coverageEntriesBySourceHash: { [sourceHash: string]: CoverageEntriesDescription } = {}; - -export const collectCoverageEntries = (contractSource: string) => { - const sourceHash = ethUtil.sha3(contractSource).toString('hex'); - if (_.isUndefined(coverageEntriesBySourceHash[sourceHash]) && !_.isUndefined(contractSource)) { - const ast = parser.parse(contractSource, { range: true }); - const locationByOffset = getLocationByOffset(contractSource); - const ignoreRangesBegingingAt = gatherRangesToIgnore(contractSource); - const visitor = new ASTVisitor(locationByOffset, ignoreRangesBegingingAt); - parser.visit(ast, visitor); - coverageEntriesBySourceHash[sourceHash] = visitor.getCollectedCoverageEntries(); - } - const coverageEntriesDescription = coverageEntriesBySourceHash[sourceHash]; - return coverageEntriesDescription; -}; - -// Gather the start index of all code blocks preceeded by "/* solcov ignore next */" -function gatherRangesToIgnore(contractSource: string): number[] { - const ignoreRangesStart = []; - - let match; - do { - match = IGNORE_RE.exec(contractSource); - if (match) { - const matchLen = match[0].length; - ignoreRangesStart.push(match.index + matchLen); - } - } while (match); - - return ignoreRangesStart; -} diff --git a/packages/sol-cov/src/constants.ts b/packages/sol-cov/src/constants.ts deleted file mode 100644 index 34d62b537..000000000 --- a/packages/sol-cov/src/constants.ts +++ /dev/null @@ -1,8 +0,0 @@ -// tslint:disable:number-literal-format -export const constants = { - NEW_CONTRACT: 'NEW_CONTRACT' as 'NEW_CONTRACT', - PUSH1: 0x60, - PUSH2: 0x61, - PUSH32: 0x7f, - TIMESTAMP: 0x42, -}; diff --git a/packages/sol-cov/src/coverage_subprovider.ts b/packages/sol-cov/src/coverage_subprovider.ts deleted file mode 100644 index 9667e891c..000000000 --- a/packages/sol-cov/src/coverage_subprovider.ts +++ /dev/null @@ -1,146 +0,0 @@ -import * as _ from 'lodash'; - -import { AbstractArtifactAdapter } from './artifact_adapters/abstract_artifact_adapter'; -import { collectCoverageEntries } from './collect_coverage_entries'; -import { SingleFileSubtraceHandler, TraceCollector } from './trace_collector'; -import { TraceInfoSubprovider } from './trace_info_subprovider'; -import { - BranchCoverage, - ContractData, - Coverage, - FunctionCoverage, - FunctionDescription, - SourceRange, - StatementCoverage, - StatementDescription, - Subtrace, - TraceInfo, -} from './types'; -import { utils } from './utils'; - -/** - * This class implements the [web3-provider-engine](https://github.com/MetaMask/provider-engine) subprovider interface. - * It's used to compute your code coverage while running solidity tests. - */ -export class CoverageSubprovider extends TraceInfoSubprovider { - private readonly _coverageCollector: TraceCollector; - /** - * Instantiates a CoverageSubprovider instance - * @param artifactAdapter Adapter for used artifacts format (0x, truffle, giveth, etc.) - * @param defaultFromAddress default from address to use when sending transactions - * @param isVerbose If true, we will log any unknown transactions. Otherwise we will ignore them - */ - constructor(artifactAdapter: AbstractArtifactAdapter, defaultFromAddress: string, isVerbose: boolean = true) { - const traceCollectionSubproviderConfig = { - shouldCollectTransactionTraces: true, - shouldCollectGasEstimateTraces: true, - shouldCollectCallTraces: true, - }; - super(defaultFromAddress, traceCollectionSubproviderConfig); - this._coverageCollector = new TraceCollector(artifactAdapter, isVerbose, coverageHandler); - } - protected async _handleTraceInfoAsync(traceInfo: TraceInfo): Promise { - await this._coverageCollector.computeSingleTraceCoverageAsync(traceInfo); - } - /** - * Write the test coverage results to a file in Istanbul format. - */ - public async writeCoverageAsync(): Promise { - await this._coverageCollector.writeOutputAsync(); - } -} - -/** - * Computed partial coverage for a single file & subtrace. - * @param contractData Contract metadata (source, srcMap, bytecode) - * @param subtrace A subset of a transcation/call trace that was executed within that contract - * @param pcToSourceRange A mapping from program counters to source ranges - * @param fileIndex Index of a file to compute coverage for - * @return Partial istanbul coverage for that file & subtrace - */ -export const coverageHandler: SingleFileSubtraceHandler = ( - contractData: ContractData, - subtrace: Subtrace, - pcToSourceRange: { [programCounter: number]: SourceRange }, - fileIndex: number, -): Coverage => { - const absoluteFileName = contractData.sources[fileIndex]; - const coverageEntriesDescription = collectCoverageEntries(contractData.sourceCodes[fileIndex]); - - // if the source wasn't provided for the fileIndex, we can't cover the file - if (_.isUndefined(coverageEntriesDescription)) { - return {}; - } - - let sourceRanges = _.map(subtrace, structLog => pcToSourceRange[structLog.pc]); - 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 === absoluteFileName); - const branchCoverage: BranchCoverage = {}; - const branchIds = _.keys(coverageEntriesDescription.branchMap); - for (const branchId of branchIds) { - const branchDescription = coverageEntriesDescription.branchMap[branchId]; - const isBranchCoveredByBranchIndex = _.map(branchDescription.locations, location => { - const isBranchCovered = _.some(sourceRanges, range => utils.isRangeInside(range.location, location)); - const timesBranchCovered = Number(isBranchCovered); - return timesBranchCovered; - }); - branchCoverage[branchId] = isBranchCoveredByBranchIndex; - } - const statementCoverage: StatementCoverage = {}; - const statementIds = _.keys(coverageEntriesDescription.statementMap); - for (const statementId of statementIds) { - const statementDescription = coverageEntriesDescription.statementMap[statementId]; - const isStatementCovered = _.some(sourceRanges, range => - utils.isRangeInside(range.location, statementDescription), - ); - const timesStatementCovered = Number(isStatementCovered); - statementCoverage[statementId] = timesStatementCovered; - } - const functionCoverage: FunctionCoverage = {}; - const functionIds = _.keys(coverageEntriesDescription.fnMap); - for (const fnId of functionIds) { - const functionDescription = coverageEntriesDescription.fnMap[fnId]; - const isFunctionCovered = _.some(sourceRanges, range => - utils.isRangeInside(range.location, functionDescription.loc), - ); - const timesFunctionCovered = Number(isFunctionCovered); - functionCoverage[fnId] = timesFunctionCovered; - } - // 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; - }, - ); - const timesModifierCovered = Number(isModifierCovered); - statementCoverage[modifierStatementId] = timesModifierCovered; - } - const partialCoverage = { - [absoluteFileName]: { - ...coverageEntriesDescription, - path: absoluteFileName, - f: functionCoverage, - s: statementCoverage, - b: branchCoverage, - }, - }; - return partialCoverage; -}; diff --git a/packages/sol-cov/src/get_source_range_snippet.ts b/packages/sol-cov/src/get_source_range_snippet.ts deleted file mode 100644 index bea17beae..000000000 --- a/packages/sol-cov/src/get_source_range_snippet.ts +++ /dev/null @@ -1,180 +0,0 @@ -import * as ethUtil from 'ethereumjs-util'; -import * as _ from 'lodash'; -import * as Parser from 'solidity-parser-antlr'; - -import { SingleFileSourceRange, SourceRange, SourceSnippet } from './types'; -import { utils } from './utils'; - -interface ASTInfo { - type: string; - node: Parser.ASTNode; - name: string | null; - range?: SingleFileSourceRange; -} - -// Parsing source code for each transaction/code is slow and therefore we cache it -const parsedSourceByHash: { [sourceHash: string]: Parser.ASTNode } = {}; - -export function getSourceRangeSnippet(sourceRange: SourceRange, sourceCode: string): SourceSnippet | null { - const sourceHash = ethUtil.sha3(sourceCode).toString('hex'); - if (_.isUndefined(parsedSourceByHash[sourceHash])) { - parsedSourceByHash[sourceHash] = Parser.parse(sourceCode, { loc: true }); - } - const astNode = parsedSourceByHash[sourceHash]; - const visitor = new ASTInfoVisitor(); - Parser.visit(astNode, visitor); - const astInfo = visitor.getASTInfoForRange(sourceRange); - if (astInfo === null) { - return null; - } - const sourceCodeInRange = utils.getRange(sourceCode, sourceRange.location); - return { - ...astInfo, - range: astInfo.range as SingleFileSourceRange, - source: sourceCodeInRange, - fileName: sourceRange.fileName, - }; -} - -// A visitor which collects ASTInfo for most nodes in the AST. -class ASTInfoVisitor { - private readonly _astInfos: ASTInfo[] = []; - public getASTInfoForRange(sourceRange: SourceRange): ASTInfo | null { - // HACK(albrow): Sometimes the source range doesn't exactly match that - // of astInfo. To work around that we try with a +/-1 offset on - // end.column. If nothing matches even with the offset, we return null. - const offset = { - start: { - line: 0, - column: 0, - }, - end: { - line: 0, - column: 0, - }, - }; - let astInfo = this._getASTInfoForRange(sourceRange, offset); - if (astInfo !== null) { - return astInfo; - } - offset.end.column += 1; - astInfo = this._getASTInfoForRange(sourceRange, offset); - if (astInfo !== null) { - return astInfo; - } - offset.end.column -= 2; - astInfo = this._getASTInfoForRange(sourceRange, offset); - if (astInfo !== null) { - return astInfo; - } - return null; - } - public ContractDefinition(ast: Parser.ContractDefinition): void { - this._visitContractDefinition(ast); - } - public IfStatement(ast: Parser.IfStatement): void { - this._visitStatement(ast); - } - 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 EmitStatement(ast: any /* TODO: Parser.EmitStatement */): 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 ModifierInvocation(ast: Parser.ModifierInvocation): void { - const BUILTIN_MODIFIERS = ['public', 'view', 'payable', 'external', 'internal', 'pure', 'constant']; - if (!_.includes(BUILTIN_MODIFIERS, ast.name)) { - this._visitStatement(ast); - } - } - private _visitStatement(ast: Parser.ASTNode): void { - this._astInfos.push({ - type: ast.type, - node: ast, - name: null, - range: ast.loc, - }); - } - private _visitFunctionLikeDefinition(ast: Parser.ModifierDefinition | Parser.FunctionDefinition): void { - this._astInfos.push({ - type: ast.type, - node: ast, - name: ast.name, - range: ast.loc, - }); - } - private _visitContractDefinition(ast: Parser.ContractDefinition): void { - this._astInfos.push({ - type: ast.type, - node: ast, - name: ast.name, - range: ast.loc, - }); - } - private _getASTInfoForRange(sourceRange: SourceRange, offset: SingleFileSourceRange): ASTInfo | null { - const offsetSourceRange = { - ...sourceRange, - location: { - start: { - line: sourceRange.location.start.line + offset.start.line, - column: sourceRange.location.start.column + offset.start.column, - }, - end: { - line: sourceRange.location.end.line + offset.end.line, - column: sourceRange.location.end.column + offset.end.column, - }, - }, - }; - for (const astInfo of this._astInfos) { - const astInfoRange = astInfo.range as SingleFileSourceRange; - if ( - astInfoRange.start.column === offsetSourceRange.location.start.column && - astInfoRange.start.line === offsetSourceRange.location.start.line && - astInfoRange.end.column === offsetSourceRange.location.end.column && - astInfoRange.end.line === offsetSourceRange.location.end.line - ) { - return astInfo; - } - } - return null; - } -} diff --git a/packages/sol-cov/src/globals.d.ts b/packages/sol-cov/src/globals.d.ts deleted file mode 100644 index e799b3529..000000000 --- a/packages/sol-cov/src/globals.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -// tslint:disable:completed-docs -declare module '*.json' { - const json: any; - /* tslint:disable */ - export default json; - /* tslint:enable */ -} diff --git a/packages/sol-cov/src/index.ts b/packages/sol-cov/src/index.ts deleted file mode 100644 index 348e0fc9b..000000000 --- a/packages/sol-cov/src/index.ts +++ /dev/null @@ -1,25 +0,0 @@ -export { CoverageSubprovider } from './coverage_subprovider'; -export { SolCompilerArtifactAdapter } from './artifact_adapters/sol_compiler_artifact_adapter'; -export { TruffleArtifactAdapter } from './artifact_adapters/truffle_artifact_adapter'; -export { AbstractArtifactAdapter } from './artifact_adapters/abstract_artifact_adapter'; - -// HACK: ProfilerSubprovider is a hacky way to do profiling using coverage tools. Not production ready -export { ProfilerSubprovider } from './profiler_subprovider'; -export { RevertTraceSubprovider } from './revert_trace_subprovider'; - -export { ContractData } from './types'; -export { - JSONRPCRequestPayload, - Provider, - JSONRPCErrorCallback, - JSONRPCResponsePayload, - JSONRPCResponseError, -} from 'ethereum-types'; - -export { - JSONRPCRequestPayloadWithMethod, - NextCallback, - ErrorCallback, - OnNextCompleted, - Callback, -} from '@0x/subproviders'; diff --git a/packages/sol-cov/src/instructions.ts b/packages/sol-cov/src/instructions.ts deleted file mode 100644 index 40987dbe5..000000000 --- a/packages/sol-cov/src/instructions.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { constants } from './constants'; - -const isPush = (inst: number) => inst >= constants.PUSH1 && inst <= constants.PUSH32; - -const pushDataLength = (inst: number) => inst - constants.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; -}; diff --git a/packages/sol-cov/src/profiler_subprovider.ts b/packages/sol-cov/src/profiler_subprovider.ts deleted file mode 100644 index ae9351f17..000000000 --- a/packages/sol-cov/src/profiler_subprovider.ts +++ /dev/null @@ -1,91 +0,0 @@ -import * as _ from 'lodash'; - -import { AbstractArtifactAdapter } from './artifact_adapters/abstract_artifact_adapter'; -import { collectCoverageEntries } from './collect_coverage_entries'; -import { SingleFileSubtraceHandler, TraceCollector } from './trace_collector'; -import { TraceInfoSubprovider } from './trace_info_subprovider'; -import { ContractData, Coverage, SourceRange, Subtrace, TraceInfo } from './types'; -import { utils } from './utils'; - -/** - * This class implements the [web3-provider-engine](https://github.com/MetaMask/provider-engine) subprovider interface. - * ProfilerSubprovider is used to profile Solidity code while running tests. - */ -export class ProfilerSubprovider extends TraceInfoSubprovider { - private readonly _profilerCollector: TraceCollector; - /** - * Instantiates a ProfilerSubprovider instance - * @param artifactAdapter Adapter for used artifacts format (0x, truffle, giveth, etc.) - * @param defaultFromAddress default from address to use when sending transactions - * @param isVerbose If true, we will log any unknown transactions. Otherwise we will ignore them - */ - constructor(artifactAdapter: AbstractArtifactAdapter, defaultFromAddress: string, isVerbose: boolean = true) { - const traceCollectionSubproviderConfig = { - shouldCollectTransactionTraces: true, - shouldCollectGasEstimateTraces: false, - shouldCollectCallTraces: false, - }; - super(defaultFromAddress, traceCollectionSubproviderConfig); - this._profilerCollector = new TraceCollector(artifactAdapter, isVerbose, profilerHandler); - } - protected async _handleTraceInfoAsync(traceInfo: TraceInfo): Promise { - await this._profilerCollector.computeSingleTraceCoverageAsync(traceInfo); - } - /** - * Write the test profiler results to a file in Istanbul format. - */ - public async writeProfilerOutputAsync(): Promise { - await this._profilerCollector.writeOutputAsync(); - } -} - -/** - * Computed partial coverage for a single file & subtrace for the purposes of - * gas profiling. - * @param contractData Contract metadata (source, srcMap, bytecode) - * @param subtrace A subset of a transcation/call trace that was executed within that contract - * @param pcToSourceRange A mapping from program counters to source ranges - * @param fileIndex Index of a file to compute coverage for - * @return Partial istanbul coverage for that file & subtrace - */ -export const profilerHandler: SingleFileSubtraceHandler = ( - contractData: ContractData, - subtrace: Subtrace, - pcToSourceRange: { [programCounter: number]: SourceRange }, - fileIndex: number, -): Coverage => { - const absoluteFileName = contractData.sources[fileIndex]; - const profilerEntriesDescription = collectCoverageEntries(contractData.sourceCodes[fileIndex]); - const gasConsumedByStatement: { [statementId: string]: number } = {}; - const statementIds = _.keys(profilerEntriesDescription.statementMap); - for (const statementId of statementIds) { - const statementDescription = profilerEntriesDescription.statementMap[statementId]; - const totalGasCost = _.sum( - _.map(subtrace, structLog => { - const sourceRange = pcToSourceRange[structLog.pc]; - if (_.isUndefined(sourceRange)) { - return 0; - } - if (sourceRange.fileName !== absoluteFileName) { - return 0; - } - if (utils.isRangeInside(sourceRange.location, statementDescription)) { - return structLog.gasCost; - } else { - return 0; - } - }), - ); - gasConsumedByStatement[statementId] = totalGasCost; - } - const partialProfilerOutput = { - [absoluteFileName]: { - ...profilerEntriesDescription, - path: absoluteFileName, - f: {}, // I's meaningless in profiling context - s: gasConsumedByStatement, - b: {}, // I's meaningless in profiling context - }, - }; - return partialProfilerOutput; -}; diff --git a/packages/sol-cov/src/revert_trace.ts b/packages/sol-cov/src/revert_trace.ts deleted file mode 100644 index d60c6e7d9..000000000 --- a/packages/sol-cov/src/revert_trace.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { logUtils } from '@0x/utils'; -import { OpCode, StructLog } from 'ethereum-types'; - -import * as _ from 'lodash'; - -import { EvmCallStack } from './types'; -import { utils } from './utils'; - -export function getRevertTrace(structLogs: StructLog[], startAddress: string): EvmCallStack { - const evmCallStack: EvmCallStack = []; - const addressStack = [startAddress]; - if (_.isEmpty(structLogs)) { - return []; - } - const normalizedStructLogs = utils.normalizeStructLogs(structLogs); - // tslint:disable-next-line:prefer-for-of - for (let i = 0; i < normalizedStructLogs.length; i++) { - const structLog = normalizedStructLogs[i]; - if (structLog.depth !== addressStack.length - 1) { - throw new Error("Malformed trace. Trace depth doesn't match call stack depth"); - } - // After that check we have a guarantee that call stack is never empty - // If it would: callStack.length - 1 === structLog.depth === -1 - // That means that we can always safely pop from it - - if (utils.isCallLike(structLog.op)) { - const currentAddress = _.last(addressStack) as string; - const jumpAddressOffset = 1; - const newAddress = utils.getAddressFromStackEntry( - structLog.stack[structLog.stack.length - jumpAddressOffset - 1], - ); - - // Sometimes calls don't change the execution context (current address). When we do a transfer to an - // externally owned account - it does the call and immediately returns because there is no fallback - // function. We manually check if the call depth had changed to handle that case. - const nextStructLog = normalizedStructLogs[i + 1]; - if (nextStructLog.depth !== structLog.depth) { - addressStack.push(newAddress); - evmCallStack.push({ - address: currentAddress, - structLog, - }); - } - } else if (utils.isEndOpcode(structLog.op) && structLog.op !== OpCode.Revert) { - // Just like with calls, sometimes returns/stops don't change the execution context (current address). - const nextStructLog = normalizedStructLogs[i + 1]; - if (_.isUndefined(nextStructLog) || nextStructLog.depth !== structLog.depth) { - evmCallStack.pop(); - addressStack.pop(); - } - if (structLog.op === OpCode.SelfDestruct) { - // After contract execution, we look at all sub-calls to external contracts, and for each one, fetch - // the bytecode and compute the coverage for the call. If the contract is destroyed with a call - // to `selfdestruct`, we are unable to fetch it's bytecode and compute coverage. - // TODO: Refactor this logic to fetch the sub-called contract bytecode before the selfdestruct is called - // in order to handle this edge-case. - logUtils.warn( - "Detected a selfdestruct. Sol-cov currently doesn't support that scenario. We'll just skip the trace part for a destructed contract", - ); - } - } else if (structLog.op === OpCode.Revert) { - evmCallStack.push({ - address: _.last(addressStack) as string, - structLog, - }); - return evmCallStack; - } else if (structLog.op === OpCode.Create) { - // TODO: Extract the new contract address from the stack and handle that scenario - logUtils.warn( - "Detected a contract created from within another contract. Sol-cov currently doesn't support that scenario. We'll just skip that trace", - ); - return []; - } else { - if (structLog !== _.last(normalizedStructLogs)) { - const nextStructLog = normalizedStructLogs[i + 1]; - if (nextStructLog.depth === structLog.depth) { - continue; - } else if (nextStructLog.depth === structLog.depth - 1) { - addressStack.pop(); - } else { - throw new Error('Malformed trace. Unexpected call depth change'); - } - } - } - } - if (evmCallStack.length !== 0) { - logUtils.warn('Malformed trace. Call stack non empty at the end. (probably out of gas)'); - } - return []; -} diff --git a/packages/sol-cov/src/revert_trace_subprovider.ts b/packages/sol-cov/src/revert_trace_subprovider.ts deleted file mode 100644 index 6ccf59653..000000000 --- a/packages/sol-cov/src/revert_trace_subprovider.ts +++ /dev/null @@ -1,163 +0,0 @@ -import { stripHexPrefix } from 'ethereumjs-util'; -import * as _ from 'lodash'; -import { getLogger, levels, Logger } from 'loglevel'; - -import { AbstractArtifactAdapter } from './artifact_adapters/abstract_artifact_adapter'; -import { constants } from './constants'; -import { getSourceRangeSnippet } from './get_source_range_snippet'; -import { getRevertTrace } from './revert_trace'; -import { parseSourceMap } from './source_maps'; -import { TraceCollectionSubprovider } from './trace_collection_subprovider'; -import { ContractData, EvmCallStack, SourceRange, SourceSnippet } from './types'; -import { utils } from './utils'; - -/** - * This class implements the [web3-provider-engine](https://github.com/MetaMask/provider-engine) subprovider interface. - * It is used to report call stack traces whenever a revert occurs. - */ -export class RevertTraceSubprovider extends TraceCollectionSubprovider { - // Lock is used to not accept normal transactions while doing call/snapshot magic because they'll be reverted later otherwise - private _contractsData!: ContractData[]; - private readonly _artifactAdapter: AbstractArtifactAdapter; - private readonly _logger: Logger; - - /** - * Instantiates a RevertTraceSubprovider instance - * @param artifactAdapter Adapter for used artifacts format (0x, truffle, giveth, etc.) - * @param defaultFromAddress default from address to use when sending transactions - * @param isVerbose If true, we will log any unknown transactions. Otherwise we will ignore them - */ - constructor(artifactAdapter: AbstractArtifactAdapter, defaultFromAddress: string, isVerbose: boolean = true) { - const traceCollectionSubproviderConfig = { - shouldCollectTransactionTraces: true, - shouldCollectGasEstimateTraces: true, - shouldCollectCallTraces: true, - }; - super(defaultFromAddress, traceCollectionSubproviderConfig); - this._artifactAdapter = artifactAdapter; - this._logger = getLogger('sol-cov'); - this._logger.setLevel(isVerbose ? levels.TRACE : levels.ERROR); - } - // tslint:disable-next-line:no-unused-variable - protected async _recordTxTraceAsync(address: string, data: string | undefined, txHash: string): Promise { - await this._web3Wrapper.awaitTransactionMinedAsync(txHash, 0); - const trace = await this._web3Wrapper.getTransactionTraceAsync(txHash, { - disableMemory: true, - disableStack: false, - disableStorage: true, - }); - const evmCallStack = getRevertTrace(trace.structLogs, address); - if (evmCallStack.length > 0) { - // if getRevertTrace returns a call stack it means there was a - // revert. - await this._printStackTraceAsync(evmCallStack); - } - } - private async _printStackTraceAsync(evmCallStack: EvmCallStack): Promise { - const sourceSnippets: SourceSnippet[] = []; - if (_.isUndefined(this._contractsData)) { - this._contractsData = await this._artifactAdapter.collectContractsDataAsync(); - } - for (const evmCallStackEntry of evmCallStack) { - const isContractCreation = evmCallStackEntry.address === constants.NEW_CONTRACT; - if (isContractCreation) { - this._logger.error('Contract creation not supported'); - continue; - } - const bytecode = await this._web3Wrapper.getContractCodeAsync(evmCallStackEntry.address); - const contractData = utils.getContractDataIfExists(this._contractsData, bytecode); - if (_.isUndefined(contractData)) { - const errMsg = isContractCreation - ? `Unknown contract creation transaction` - : `Transaction to an unknown address: ${evmCallStackEntry.address}`; - this._logger.warn(errMsg); - continue; - } - const bytecodeHex = stripHexPrefix(bytecode); - const sourceMap = isContractCreation ? contractData.sourceMap : contractData.sourceMapRuntime; - - const pcToSourceRange = parseSourceMap( - contractData.sourceCodes, - sourceMap, - bytecodeHex, - contractData.sources, - ); - // tslint:disable-next-line:no-unnecessary-initializer - let sourceRange: SourceRange | undefined = undefined; - let pc = evmCallStackEntry.structLog.pc; - // Sometimes there is not a mapping for this pc (e.g. if the revert - // actually happens in assembly). In that case, we want to keep - // searching backwards by decrementing the pc until we find a - // mapped source range. - while (_.isUndefined(sourceRange) && pc > 0) { - sourceRange = pcToSourceRange[pc]; - pc -= 1; - } - if (_.isUndefined(sourceRange)) { - this._logger.warn( - `could not find matching sourceRange for structLog: ${JSON.stringify( - _.omit(evmCallStackEntry.structLog, 'stack'), - )}`, - ); - continue; - } - - const fileIndex = contractData.sources.indexOf(sourceRange.fileName); - const sourceSnippet = getSourceRangeSnippet(sourceRange, contractData.sourceCodes[fileIndex]); - if (sourceSnippet !== null) { - sourceSnippets.push(sourceSnippet); - } - } - const filteredSnippets = filterSnippets(sourceSnippets); - if (filteredSnippets.length > 0) { - this._logger.error('\n\nStack trace for REVERT:\n'); - _.forEach(_.reverse(filteredSnippets), snippet => { - const traceString = getStackTraceString(snippet); - this._logger.error(traceString); - }); - this._logger.error('\n'); - } else { - this._logger.error('REVERT detected but could not determine stack trace'); - } - } -} - -// removes duplicates and if statements -function filterSnippets(sourceSnippets: SourceSnippet[]): SourceSnippet[] { - if (sourceSnippets.length === 0) { - return []; - } - const results: SourceSnippet[] = [sourceSnippets[0]]; - let prev = sourceSnippets[0]; - for (const sourceSnippet of sourceSnippets) { - if (sourceSnippet.type === 'IfStatement') { - continue; - } else if (sourceSnippet.source === prev.source) { - prev = sourceSnippet; - continue; - } - results.push(sourceSnippet); - prev = sourceSnippet; - } - return results; -} - -function getStackTraceString(sourceSnippet: SourceSnippet): string { - let result = `${sourceSnippet.fileName}:${sourceSnippet.range.start.line}:${sourceSnippet.range.start.column}`; - const snippetString = getSourceSnippetString(sourceSnippet); - if (snippetString !== '') { - result += `:\n ${snippetString}`; - } - return result; -} - -function getSourceSnippetString(sourceSnippet: SourceSnippet): string { - switch (sourceSnippet.type) { - case 'ContractDefinition': - return `contract ${sourceSnippet.name}`; - case 'FunctionDefinition': - return `function ${sourceSnippet.name}`; - default: - return `${sourceSnippet.source}`; - } -} diff --git a/packages/sol-cov/src/source_maps.ts b/packages/sol-cov/src/source_maps.ts deleted file mode 100644 index 90b21dda1..000000000 --- a/packages/sol-cov/src/source_maps.ts +++ /dev/null @@ -1,81 +0,0 @@ -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 = { 0: { line: 1, column: 0 } }; - let currentOffset = 0; - for (const char of str.split('')) { - const location = locationByOffset[currentOffset]; - const isNewline = char === '\n'; - locationByOffset[currentOffset + 1] = { - 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, s => (_.isUndefined(s) ? {} : getLocationByOffset(s))); - const entries = srcMap.split(';'); - let lastParsedEntry: SourceLocation = {} as any; - const instructionIndexToSourceRange: { [instructionIndex: number]: SourceRange } = {}; - _.each(entries, (entry: string, i: number) => { - // tslint:disable-next-line:no-unused-variable - 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 && !_.isUndefined(locationByOffsetByFileIndex[parsedEntry.fileIndex])) { - const sourceRange = { - location: { - start: locationByOffsetByFileIndex[parsedEntry.fileIndex][parsedEntry.offset], - end: locationByOffsetByFileIndex[parsedEntry.fileIndex][parsedEntry.offset + parsedEntry.length], - }, - 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; -} diff --git a/packages/sol-cov/src/trace.ts b/packages/sol-cov/src/trace.ts deleted file mode 100644 index b38dbdce0..000000000 --- a/packages/sol-cov/src/trace.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { logUtils } from '@0x/utils'; -import { OpCode, StructLog } from 'ethereum-types'; -import * as _ from 'lodash'; - -import { utils } from './utils'; - -export interface TraceByContractAddress { - [contractAddress: string]: StructLog[]; -} - -export function getTracesByContractAddress(structLogs: StructLog[], startAddress: string): TraceByContractAddress { - const traceByContractAddress: TraceByContractAddress = {}; - let currentTraceSegment = []; - const addressStack = [startAddress]; - if (_.isEmpty(structLogs)) { - return traceByContractAddress; - } - const normalizedStructLogs = utils.normalizeStructLogs(structLogs); - // tslint:disable-next-line:prefer-for-of - for (let i = 0; i < normalizedStructLogs.length; i++) { - const structLog = normalizedStructLogs[i]; - if (structLog.depth !== addressStack.length - 1) { - throw new Error("Malformed trace. Trace depth doesn't match call stack depth"); - } - // After that check we have a guarantee that call stack is never empty - // If it would: callStack.length - 1 === structLog.depth === -1 - // That means that we can always safely pop from it - currentTraceSegment.push(structLog); - - if (utils.isCallLike(structLog.op)) { - const currentAddress = _.last(addressStack) as string; - const jumpAddressOffset = 1; - const newAddress = utils.getAddressFromStackEntry( - structLog.stack[structLog.stack.length - jumpAddressOffset - 1], - ); - - // Sometimes calls don't change the execution context (current address). When we do a transfer to an - // externally owned account - it does the call and immediately returns because there is no fallback - // function. We manually check if the call depth had changed to handle that case. - const nextStructLog = normalizedStructLogs[i + 1]; - if (nextStructLog.depth !== structLog.depth) { - addressStack.push(newAddress); - traceByContractAddress[currentAddress] = (traceByContractAddress[currentAddress] || []).concat( - currentTraceSegment, - ); - currentTraceSegment = []; - } - } else if (utils.isEndOpcode(structLog.op)) { - const currentAddress = addressStack.pop() as string; - traceByContractAddress[currentAddress] = (traceByContractAddress[currentAddress] || []).concat( - currentTraceSegment, - ); - currentTraceSegment = []; - if (structLog.op === OpCode.SelfDestruct) { - // After contract execution, we look at all sub-calls to external contracts, and for each one, fetch - // the bytecode and compute the coverage for the call. If the contract is destroyed with a call - // to `selfdestruct`, we are unable to fetch it's bytecode and compute coverage. - // TODO: Refactor this logic to fetch the sub-called contract bytecode before the selfdestruct is called - // in order to handle this edge-case. - logUtils.warn( - "Detected a selfdestruct. Sol-cov currently doesn't support that scenario. We'll just skip the trace part for a destructed contract", - ); - } - } else if (structLog.op === OpCode.Create) { - // TODO: Extract the new contract address from the stack and handle that scenario - logUtils.warn( - "Detected a contract created from within another contract. Sol-cov currently doesn't support that scenario. We'll just skip that trace", - ); - return traceByContractAddress; - } else { - if (structLog !== _.last(normalizedStructLogs)) { - const nextStructLog = normalizedStructLogs[i + 1]; - if (nextStructLog.depth === structLog.depth) { - continue; - } else if (nextStructLog.depth === structLog.depth - 1) { - const currentAddress = addressStack.pop() as string; - traceByContractAddress[currentAddress] = (traceByContractAddress[currentAddress] || []).concat( - currentTraceSegment, - ); - currentTraceSegment = []; - } else { - throw new Error('Malformed trace. Unexpected call depth change'); - } - } - } - } - if (addressStack.length !== 0) { - logUtils.warn('Malformed trace. Call stack non empty at the end'); - } - if (currentTraceSegment.length !== 0) { - const currentAddress = addressStack.pop() as string; - traceByContractAddress[currentAddress] = (traceByContractAddress[currentAddress] || []).concat( - currentTraceSegment, - ); - currentTraceSegment = []; - logUtils.warn('Malformed trace. Current trace segment non empty at the end'); - } - return traceByContractAddress; -} diff --git a/packages/sol-cov/src/trace_collection_subprovider.ts b/packages/sol-cov/src/trace_collection_subprovider.ts deleted file mode 100644 index 25e38768d..000000000 --- a/packages/sol-cov/src/trace_collection_subprovider.ts +++ /dev/null @@ -1,188 +0,0 @@ -import { BlockchainLifecycle } from '@0x/dev-utils'; -import { Callback, ErrorCallback, NextCallback, Subprovider } from '@0x/subproviders'; -import { CallDataRPC, marshaller, Web3Wrapper } from '@0x/web3-wrapper'; -import { JSONRPCRequestPayload, Provider, TxData } from 'ethereum-types'; -import * as _ from 'lodash'; -import { Lock } from 'semaphore-async-await'; - -import { constants } from './constants'; -import { BlockParamLiteral } from './types'; - -interface MaybeFakeTxData extends TxData { - isFakeTransaction?: boolean; -} - -const BLOCK_GAS_LIMIT = 6000000; - -export interface TraceCollectionSubproviderConfig { - shouldCollectTransactionTraces: boolean; - shouldCollectCallTraces: boolean; - shouldCollectGasEstimateTraces: boolean; -} - -// Because there is no notion of a call trace in the Ethereum rpc - we collect them in a 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 avoid influencing test behaviour. - -/** - * This class implements the [web3-provider-engine](https://github.com/MetaMask/provider-engine) subprovider interface. - * It collects traces of all transactions that were sent and all calls that were executed through JSON RPC. It must - * be extended by implementing the _recordTxTraceAsync method which is called for every transaction. - */ -export abstract class TraceCollectionSubprovider extends Subprovider { - protected _web3Wrapper!: Web3Wrapper; - // Lock is used to not accept normal transactions while doing call/snapshot magic because they'll be reverted later otherwise - private readonly _lock = new Lock(); - private readonly _defaultFromAddress: string; - private _isEnabled = true; - private readonly _config: TraceCollectionSubproviderConfig; - /** - * Instantiates a TraceCollectionSubprovider instance - * @param defaultFromAddress default from address to use when sending transactions - */ - constructor(defaultFromAddress: string, config: TraceCollectionSubproviderConfig) { - super(); - this._defaultFromAddress = defaultFromAddress; - this._config = config; - } - /** - * Starts trace collection - */ - public start(): void { - this._isEnabled = true; - } - /** - * Stops trace collection - */ - public stop(): void { - this._isEnabled = false; - } - /** - * This method conforms to the web3-provider-engine interface. - * It is called internally by the ProviderEngine when it is this subproviders - * turn to handle a JSON RPC request. - * @param payload JSON RPC payload - * @param next Callback to call if this subprovider decides not to handle the request - * @param _end Callback to call if subprovider handled the request and wants to pass back the request. - */ - // tslint:disable-next-line:prefer-function-over-method async-suffix - public async handleRequest(payload: JSONRPCRequestPayload, next: NextCallback, _end: ErrorCallback): Promise { - if (this._isEnabled) { - switch (payload.method) { - case 'eth_sendTransaction': - if (!this._config.shouldCollectTransactionTraces) { - next(); - } else { - const txData = payload.params[0]; - next(this._onTransactionSentAsync.bind(this, txData)); - } - return; - - case 'eth_call': - if (!this._config.shouldCollectCallTraces) { - next(); - } else { - const callData = payload.params[0]; - next(this._onCallOrGasEstimateExecutedAsync.bind(this, callData)); - } - return; - - case 'eth_estimateGas': - if (!this._config.shouldCollectGasEstimateTraces) { - next(); - } else { - const estimateGasData = payload.params[0]; - next(this._onCallOrGasEstimateExecutedAsync.bind(this, estimateGasData)); - } - return; - - default: - next(); - return; - } - } else { - next(); - return; - } - } - /** - * Set's the subprovider's engine to the ProviderEngine it is added to. - * This is only called within the ProviderEngine source code, do not call - * directly. - * @param engine The ProviderEngine this subprovider is added to - */ - public setEngine(engine: Provider): void { - super.setEngine(engine); - this._web3Wrapper = new Web3Wrapper(engine); - } - protected abstract async _recordTxTraceAsync( - address: string, - data: string | undefined, - txHash: string, - ): Promise; - private async _onTransactionSentAsync( - txData: MaybeFakeTxData, - err: Error | null, - txHash: string | undefined, - cb: Callback, - ): Promise { - if (!txData.isFakeTransaction) { - // This transaction is a usual transaction. Not a call executed as one. - // And we don't want it to be executed within a snapshotting period - await this._lock.acquire(); - } - const NULL_ADDRESS = '0x0'; - if (_.isNull(err)) { - const toAddress = - _.isUndefined(txData.to) || txData.to === NULL_ADDRESS ? constants.NEW_CONTRACT : txData.to; - await this._recordTxTraceAsync(toAddress, txData.data, txHash as string); - } else { - const latestBlock = await this._web3Wrapper.getBlockWithTransactionDataAsync(BlockParamLiteral.Latest); - const transactions = latestBlock.transactions; - for (const transaction of transactions) { - const toAddress = - _.isUndefined(txData.to) || txData.to === NULL_ADDRESS ? constants.NEW_CONTRACT : txData.to; - await this._recordTxTraceAsync(toAddress, transaction.input, transaction.hash); - } - } - if (!txData.isFakeTransaction) { - // This transaction is a usual transaction. 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 _onCallOrGasEstimateExecutedAsync( - callData: Partial, - _err: Error | null, - _callResult: string, - cb: Callback, - ): Promise { - await this._recordCallOrGasEstimateTraceAsync(callData); - cb(); - } - private async _recordCallOrGasEstimateTraceAsync(callData: Partial): Promise { - // 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 blockchainLifecycle = new BlockchainLifecycle(this._web3Wrapper); - await blockchainLifecycle.startAsync(); - const fakeTxData = { - gas: BLOCK_GAS_LIMIT.toString(16), // tslint:disable-line:custom-no-magic-numbers - isFakeTransaction: true, // This transaction (and only it) is allowed to come through when the lock is locked - ...callData, - from: callData.from || this._defaultFromAddress, - }; - try { - const txData = marshaller.unmarshalTxData(fakeTxData); - const txHash = await this._web3Wrapper.sendTransactionAsync(txData); - await this._web3Wrapper.awaitTransactionMinedAsync(txHash, 0); - } catch (err) { - // TODO(logvinov) Check that transaction failed and not some other exception - // Even if this transaction failed - we've already recorded it's trace. - _.noop(); - } - await blockchainLifecycle.revertAsync(); - this._lock.release(); - } -} diff --git a/packages/sol-cov/src/trace_collector.ts b/packages/sol-cov/src/trace_collector.ts deleted file mode 100644 index 44cffc238..000000000 --- a/packages/sol-cov/src/trace_collector.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { promisify } from '@0x/utils'; -import { stripHexPrefix } from 'ethereumjs-util'; -import * as fs from 'fs'; -import { Collector } from 'istanbul'; -import * as _ from 'lodash'; -import { getLogger, levels, Logger } from 'loglevel'; -import * as mkdirp from 'mkdirp'; - -import { AbstractArtifactAdapter } from './artifact_adapters/abstract_artifact_adapter'; -import { constants } from './constants'; -import { parseSourceMap } from './source_maps'; -import { - ContractData, - Coverage, - SourceRange, - Subtrace, - TraceInfo, - TraceInfoExistingContract, - TraceInfoNewContract, -} from './types'; -import { utils } from './utils'; - -const mkdirpAsync = promisify(mkdirp); - -export type SingleFileSubtraceHandler = ( - contractData: ContractData, - subtrace: Subtrace, - pcToSourceRange: { [programCounter: number]: SourceRange }, - fileIndex: number, -) => Coverage; - -/** - * TraceCollector is used by CoverageSubprovider to compute code coverage based on collected trace data. - */ -export class TraceCollector { - private readonly _artifactAdapter: AbstractArtifactAdapter; - private readonly _logger: Logger; - private _contractsData!: ContractData[]; - private readonly _collector = new Collector(); - private readonly _singleFileSubtraceHandler: SingleFileSubtraceHandler; - - /** - * Instantiates a TraceCollector instance - * @param artifactAdapter Adapter for used artifacts format (0x, truffle, giveth, etc.) - * @param isVerbose If true, we will log any unknown transactions. Otherwise we will ignore them - * @param singleFileSubtraceHandler A handler function for computing partial coverage for a single file & subtrace - */ - constructor( - artifactAdapter: AbstractArtifactAdapter, - isVerbose: boolean, - singleFileSubtraceHandler: SingleFileSubtraceHandler, - ) { - this._artifactAdapter = artifactAdapter; - this._logger = getLogger('sol-cov'); - this._logger.setLevel(isVerbose ? levels.TRACE : levels.ERROR); - this._singleFileSubtraceHandler = singleFileSubtraceHandler; - } - public async writeOutputAsync(): Promise { - const finalCoverage = this._collector.getFinalCoverage(); - const stringifiedCoverage = JSON.stringify(finalCoverage, null, '\t'); - await mkdirpAsync('coverage'); - fs.writeFileSync('coverage/coverage.json', stringifiedCoverage); - } - public async computeSingleTraceCoverageAsync(traceInfo: TraceInfo): Promise { - if (_.isUndefined(this._contractsData)) { - this._contractsData = await this._artifactAdapter.collectContractsDataAsync(); - } - const isContractCreation = traceInfo.address === constants.NEW_CONTRACT; - const bytecode = isContractCreation - ? (traceInfo as TraceInfoNewContract).bytecode - : (traceInfo as TraceInfoExistingContract).runtimeBytecode; - const contractData = utils.getContractDataIfExists(this._contractsData, bytecode); - if (_.isUndefined(contractData)) { - const errMsg = isContractCreation - ? `Unknown contract creation transaction` - : `Transaction to an unknown address: ${traceInfo.address}`; - this._logger.warn(errMsg); - return; - } - const bytecodeHex = stripHexPrefix(bytecode); - const sourceMap = isContractCreation ? contractData.sourceMap : contractData.sourceMapRuntime; - const pcToSourceRange = parseSourceMap(contractData.sourceCodes, sourceMap, bytecodeHex, contractData.sources); - for (let fileIndex = 0; fileIndex < contractData.sources.length; fileIndex++) { - const singleFileCoverageForTrace = this._singleFileSubtraceHandler( - contractData, - traceInfo.subtrace, - pcToSourceRange, - fileIndex, - ); - this._collector.add(singleFileCoverageForTrace); - } - } -} diff --git a/packages/sol-cov/src/trace_info_subprovider.ts b/packages/sol-cov/src/trace_info_subprovider.ts deleted file mode 100644 index 635a68f58..000000000 --- a/packages/sol-cov/src/trace_info_subprovider.ts +++ /dev/null @@ -1,59 +0,0 @@ -import * as _ from 'lodash'; - -import { constants } from './constants'; -import { getTracesByContractAddress } from './trace'; -import { TraceCollectionSubprovider } from './trace_collection_subprovider'; -import { TraceInfo, TraceInfoExistingContract, TraceInfoNewContract } from './types'; - -// TraceInfoSubprovider is extended by subproviders which need to work with one -// TraceInfo at a time. It has one abstract method: _handleTraceInfoAsync, which -// is called for each TraceInfo. -export abstract class TraceInfoSubprovider extends TraceCollectionSubprovider { - protected abstract _handleTraceInfoAsync(traceInfo: TraceInfo): Promise; - protected async _recordTxTraceAsync(address: string, data: string | undefined, txHash: string): Promise { - await this._web3Wrapper.awaitTransactionMinedAsync(txHash, 0); - const trace = await this._web3Wrapper.getTransactionTraceAsync(txHash, { - disableMemory: true, - disableStack: false, - disableStorage: true, - }); - const tracesByContractAddress = getTracesByContractAddress(trace.structLogs, address); - const subcallAddresses = _.keys(tracesByContractAddress); - if (address === constants.NEW_CONTRACT) { - for (const subcallAddress of subcallAddresses) { - let traceInfo: TraceInfoNewContract | TraceInfoExistingContract; - if (subcallAddress === 'NEW_CONTRACT') { - const traceForThatSubcall = tracesByContractAddress[subcallAddress]; - traceInfo = { - subtrace: traceForThatSubcall, - txHash, - address: subcallAddress, - bytecode: data as string, - }; - } else { - const runtimeBytecode = await this._web3Wrapper.getContractCodeAsync(subcallAddress); - const traceForThatSubcall = tracesByContractAddress[subcallAddress]; - traceInfo = { - subtrace: traceForThatSubcall, - txHash, - address: subcallAddress, - runtimeBytecode, - }; - } - await this._handleTraceInfoAsync(traceInfo); - } - } else { - for (const subcallAddress of subcallAddresses) { - const runtimeBytecode = await this._web3Wrapper.getContractCodeAsync(subcallAddress); - const traceForThatSubcall = tracesByContractAddress[subcallAddress]; - const traceInfo: TraceInfoExistingContract = { - subtrace: traceForThatSubcall, - txHash, - address: subcallAddress, - runtimeBytecode, - }; - await this._handleTraceInfoAsync(traceInfo); - } - } - } -} diff --git a/packages/sol-cov/src/types.ts b/packages/sol-cov/src/types.ts deleted file mode 100644 index 54ade0400..000000000 --- a/packages/sol-cov/src/types.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { StructLog } from 'ethereum-types'; -import * as Parser from 'solidity-parser-antlr'; - -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]: number; -} - -export interface FunctionCoverage { - [functionId: string]: number; -} - -export interface StatementCoverage { - [statementId: string]: number; -} - -export interface BranchCoverage { - [branchId: string]: number[]; -} - -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[]; - sources: string[]; -} - -// Part of the trace executed within the same context -export type Subtrace = StructLog[]; - -export interface TraceInfoBase { - subtrace: Subtrace; - 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; - -export enum BlockParamLiteral { - Latest = 'latest', -} - -export interface EvmCallStackEntry { - structLog: StructLog; - address: string; -} - -export type EvmCallStack = EvmCallStackEntry[]; - -export interface SourceSnippet { - source: string; - fileName: string; - type: string; - node: Parser.ASTNode; - name: string | null; - range: SingleFileSourceRange; -} diff --git a/packages/sol-cov/src/utils.ts b/packages/sol-cov/src/utils.ts deleted file mode 100644 index d8bc65e73..000000000 --- a/packages/sol-cov/src/utils.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { addressUtils, BigNumber } from '@0x/utils'; -import { OpCode, StructLog } from 'ethereum-types'; -import { addHexPrefix } from 'ethereumjs-util'; -import * as _ from 'lodash'; - -import { ContractData, LineColumn, SingleFileSourceRange } from './types'; - -// This is the minimum length of valid contract bytecode. The Solidity compiler -// metadata is 86 bytes. If you add the '0x' prefix, we get 88. -const MIN_CONTRACT_BYTECODE_LENGTH = 88; - -export const utils = { - compareLineColumn(lhs: LineColumn, rhs: LineColumn): number { - return lhs.line !== rhs.line ? lhs.line - rhs.line : lhs.column - rhs.column; - }, - removeHexPrefix(hex: string): string { - const hexPrefix = '0x'; - return hex.startsWith(hexPrefix) ? hex.slice(hexPrefix.length) : hex; - }, - isRangeInside(childRange: SingleFileSourceRange, parentRange: SingleFileSourceRange): boolean { - return ( - utils.compareLineColumn(parentRange.start, childRange.start) <= 0 && - utils.compareLineColumn(childRange.end, parentRange.end) <= 0 - ); - }, - bytecodeToBytecodeRegex(bytecode: string): string { - const bytecodeRegex = bytecode - // Library linking placeholder: __ConvertLib____________________________ - .replace(/_.*_/, '.*') - // Last 86 characters is solidity compiler metadata that's different between compilations - .replace(/.{86}$/, '') - // Libraries contain their own address at the beginning of the code and it's impossible to know it in advance - .replace(/^0x730000000000000000000000000000000000000000/, '0x73........................................'); - // HACK: Node regexes can't be longer that 32767 characters. Contracts bytecode can. We just truncate the regexes. It's safe in practice. - const MAX_REGEX_LENGTH = 32767; - const truncatedBytecodeRegex = bytecodeRegex.slice(0, MAX_REGEX_LENGTH); - return truncatedBytecodeRegex; - }, - getContractDataIfExists(contractsData: ContractData[], bytecode: string): ContractData | undefined { - if (!bytecode.startsWith('0x')) { - throw new Error(`0x hex prefix missing: ${bytecode}`); - } - const contractData = _.find(contractsData, contractDataCandidate => { - const bytecodeRegex = utils.bytecodeToBytecodeRegex(contractDataCandidate.bytecode); - // If the bytecode is less than the minimum length, we are probably - // dealing with an interface. This isn't what we're looking for. - if (bytecodeRegex.length < MIN_CONTRACT_BYTECODE_LENGTH) { - return false; - } - const runtimeBytecodeRegex = utils.bytecodeToBytecodeRegex(contractDataCandidate.runtimeBytecode); - if (runtimeBytecodeRegex.length < MIN_CONTRACT_BYTECODE_LENGTH) { - return false; - } - // We use that function to find by bytecode or runtimeBytecode. Those are quasi-random strings so - // collisions are practically impossible and it allows us to reuse that code - return !_.isNull(bytecode.match(bytecodeRegex)) || !_.isNull(bytecode.match(runtimeBytecodeRegex)); - }); - return contractData; - }, - isCallLike(op: OpCode): boolean { - return _.includes([OpCode.CallCode, OpCode.StaticCall, OpCode.Call, OpCode.DelegateCall], op); - }, - isEndOpcode(op: OpCode): boolean { - return _.includes([OpCode.Return, OpCode.Stop, OpCode.Revert, OpCode.Invalid, OpCode.SelfDestruct], op); - }, - getAddressFromStackEntry(stackEntry: string): string { - const hexBase = 16; - return addressUtils.padZeros(new BigNumber(addHexPrefix(stackEntry)).toString(hexBase)); - }, - normalizeStructLogs(structLogs: StructLog[]): StructLog[] { - if (structLogs[0].depth === 1) { - // Geth uses 1-indexed depth counter whilst ganache starts from 0 - const newStructLogs = _.map(structLogs, structLog => ({ - ...structLog, - depth: structLog.depth - 1, - })); - return newStructLogs; - } - return structLogs; - }, - getRange(sourceCode: string, range: SingleFileSourceRange): string { - const lines = sourceCode.split('\n').slice(range.start.line - 1, range.end.line); - lines[lines.length - 1] = lines[lines.length - 1].slice(0, range.end.column); - lines[0] = lines[0].slice(range.start.column); - return lines.join('\n'); - }, -}; diff --git a/packages/sol-cov/test/collect_coverage_entries_test.ts b/packages/sol-cov/test/collect_coverage_entries_test.ts deleted file mode 100644 index 7832ec316..000000000 --- a/packages/sol-cov/test/collect_coverage_entries_test.ts +++ /dev/null @@ -1,155 +0,0 @@ -import * as chai from 'chai'; -import * as fs from 'fs'; -import * as _ from 'lodash'; -import 'mocha'; -import * as path from 'path'; - -import { collectCoverageEntries } from '../src/collect_coverage_entries'; -import { utils } from '../src/utils'; - -const expect = chai.expect; - -describe('Collect coverage entries', () => { - describe('#collectCoverageEntries', () => { - it('correctly collects coverage entries for Simplest contract', () => { - const simplestContractBaseName = 'Simplest.sol'; - const simplestContractFileName = path.resolve(__dirname, 'fixtures/contracts', simplestContractBaseName); - const simplestContract = fs.readFileSync(simplestContractFileName).toString(); - const coverageEntries = collectCoverageEntries(simplestContract); - expect(coverageEntries.fnMap).to.be.deep.equal({}); - expect(coverageEntries.branchMap).to.be.deep.equal({}); - expect(coverageEntries.statementMap).to.be.deep.equal({}); - expect(coverageEntries.modifiersStatementIds).to.be.deep.equal([]); - }); - it('correctly collects coverage entries for SimpleStorage contract', () => { - const simpleStorageContractBaseName = 'SimpleStorage.sol'; - const simpleStorageContractFileName = path.resolve( - __dirname, - 'fixtures/contracts', - simpleStorageContractBaseName, - ); - const simpleStorageContract = fs.readFileSync(simpleStorageContractFileName).toString(); - const coverageEntries = collectCoverageEntries(simpleStorageContract); - const fnIds = _.keys(coverageEntries.fnMap); - expect(coverageEntries.fnMap[fnIds[0]].name).to.be.equal('set'); - // tslint:disable-next-line:custom-no-magic-numbers - expect(coverageEntries.fnMap[fnIds[0]].line).to.be.equal(5); - const setFunction = `function set(uint x) { - storedData = x; - }`; - expect(utils.getRange(simpleStorageContract, coverageEntries.fnMap[fnIds[0]].loc)).to.be.equal(setFunction); - expect(coverageEntries.fnMap[fnIds[1]].name).to.be.equal('get'); - // tslint:disable-next-line:custom-no-magic-numbers - expect(coverageEntries.fnMap[fnIds[1]].line).to.be.equal(8); - const getFunction = `function get() constant returns (uint retVal) { - return storedData; - }`; - expect(utils.getRange(simpleStorageContract, coverageEntries.fnMap[fnIds[1]].loc)).to.be.equal(getFunction); - expect(coverageEntries.branchMap).to.be.deep.equal({}); - const statementIds = _.keys(coverageEntries.statementMap); - expect(utils.getRange(simpleStorageContract, coverageEntries.statementMap[statementIds[1]])).to.be.equal( - 'storedData = x', - ); - expect(utils.getRange(simpleStorageContract, coverageEntries.statementMap[statementIds[3]])).to.be.equal( - 'return storedData;', - ); - expect(coverageEntries.modifiersStatementIds).to.be.deep.equal([]); - }); - it('correctly collects coverage entries for AllSolidityFeatures contract', () => { - const simpleStorageContractBaseName = 'AllSolidityFeatures.sol'; - const simpleStorageContractFileName = path.resolve( - __dirname, - 'fixtures/contracts', - simpleStorageContractBaseName, - ); - const simpleStorageContract = fs.readFileSync(simpleStorageContractFileName).toString(); - const coverageEntries = collectCoverageEntries(simpleStorageContract); - const fnDescriptions = _.values(coverageEntries.fnMap); - const fnNames = _.map(fnDescriptions, fnDescription => fnDescription.name); - const expectedFnNames = [ - 'f', - 'c', - 'test', - 'getChoice', - 'Base', - 'Derived', - 'f', - 'f', - '', - 'g', - 'setData', - 'getData', - 'sendHalf', - 'insert', - 'remove', - 'contains', - 'iterate_start', - 'iterate_valid', - 'iterate_advance', - 'iterate_get', - 'insert', - 'sum', - 'restricted', - 'DualIndex', - 'set', - 'transfer_ownership', - 'lookup', - '', - '', - 'sum', - 'someFunction', - 'fun', - 'at', - 'test', - 'get', - 'returnNumber', - 'alloc', - 'ham', - 'getMyTuple', - 'ham', - 'abstain', - 'foobar', - 'foobar', - 'a', - ]; - expect(fnNames).to.be.deep.equal(expectedFnNames); - - const branchDescriptions = _.values(coverageEntries.branchMap); - const branchLines = _.map(branchDescriptions, branchDescription => branchDescription.line); - // tslint:disable-next-line:custom-no-magic-numbers - expect(branchLines).to.be.deep.equal([94, 115, 119, 130, 151, 187]); - const branchTypes = _.map(branchDescriptions, branchDescription => branchDescription.type); - expect(branchTypes).to.be.deep.equal(['if', 'if', 'if', 'if', 'binary-expr', 'if']); - }); - - it('correctly ignores all coverage entries for Ignore contract', () => { - const solcovIgnoreContractBaseName = 'SolcovIgnore.sol'; - const solcovIgnoreContractFileName = path.resolve( - __dirname, - 'fixtures/contracts', - solcovIgnoreContractBaseName, - ); - const solcovIgnoreContract = fs.readFileSync(solcovIgnoreContractFileName).toString(); - const coverageEntries = collectCoverageEntries(solcovIgnoreContract); - const fnIds = _.keys(coverageEntries.fnMap); - - expect(fnIds.length).to.be.equal(1); - expect(coverageEntries.fnMap[fnIds[0]].name).to.be.equal('set'); - // tslint:disable-next-line:custom-no-magic-numbers - expect(coverageEntries.fnMap[fnIds[0]].line).to.be.equal(6); - const setFunction = `function set(uint x) public { - /* solcov ignore next */ - storedData = x; - }`; - expect(utils.getRange(solcovIgnoreContract, coverageEntries.fnMap[fnIds[0]].loc)).to.be.equal(setFunction); - - expect(coverageEntries.branchMap).to.be.deep.equal({}); - const statementIds = _.keys(coverageEntries.statementMap); - expect(utils.getRange(solcovIgnoreContract, coverageEntries.statementMap[statementIds[0]])).to.be.equal( - setFunction, - ); - expect(statementIds.length).to.be.equal(1); - expect(coverageEntries.modifiersStatementIds.length).to.be.equal(0); - }); - }); -}); diff --git a/packages/sol-cov/test/fixtures/contracts/AllSolidityFeatures.sol b/packages/sol-cov/test/fixtures/contracts/AllSolidityFeatures.sol deleted file mode 100644 index 21137347e..000000000 --- a/packages/sol-cov/test/fixtures/contracts/AllSolidityFeatures.sol +++ /dev/null @@ -1,413 +0,0 @@ -// Examples taken from the Solidity documentation online. - -// for pragma version numbers, see https://docs.npmjs.com/misc/semver#versions -pragma solidity 0.4.0; -pragma solidity ^0.4.0; - -import "SomeFile.sol"; -import "SomeFile.sol" as SomeOtherFile; -import * as SomeSymbol from "AnotherFile.sol"; -import {symbol1 as alias, symbol2} from "File.sol"; - -interface i { - function f(); -} - -contract c { - function c() - { - val1 = 1 wei; // 1 - val2 = 1 szabo; // 1 * 10 ** 12 - val3 = 1 finney; // 1 * 10 ** 15 - val4 = 1 ether; // 1 * 10 ** 18 - } - uint256 val1; - uint256 val2; - uint256 val3; - uint256 val4; -} - -contract test { - enum ActionChoices { GoLeft, GoRight, GoStraight, SitStill } - - function test() - { - choices = ActionChoices.GoStraight; - } - function getChoice() returns (uint d) - { - d = uint256(choices); - } - ActionChoices choices; -} - -contract Base { - function Base(uint i) - { - m_i = i; - } - uint public m_i; -} -contract Derived is Base(0) { - function Derived(uint i) Base(i) {} -} - -contract C { - uint248 x; // 31 bytes: slot 0, offset 0 - uint16 y; // 2 bytes: slot 1, offset 0 (does not fit in slot 0) - uint240 z; // 30 bytes: slot 1, offset 2 bytes - uint8 a; // 1 byte: slot 2, offset 0 bytes - struct S { - uint8 a; // 1 byte, slot +0, offset 0 bytes - uint256 b; // 32 bytes, slot +1, offset 0 bytes (does not fit) - } - S structData; // 2 slots, slot 3, offset 0 bytes (does not really apply) - uint8 alpha; // 1 byte, slot 4 (start new slot after struct) - uint16[3] beta; // 3*16 bytes, slots 5+6 (start new slot for array) - uint8 gamma; // 1 byte, slot 7 (start new slot after array) -} - -contract test { - function f(uint x, uint y) returns (uint z) { - var c = x + 3; - var b = 7 + (c * (8 - 7)) - x; - return -(-b | 0); - } -} - -contract test { - function f(uint x, uint y) returns (uint z) { - return 10; - } -} - -contract c { - function () returns (uint) { return g(8); } - function g(uint pos) internal returns (uint) { setData(pos, 8); return getData(pos); } - function setData(uint pos, uint value) internal { data[pos] = value; } - function getData(uint pos) internal { return data[pos]; } - mapping(uint => uint) data; -} - -contract Sharer { - function sendHalf(address addr) returns (uint balance) { - if (!addr.send(msg.value/2)) - throw; // also reverts the transfer to Sharer - return address(this).balance; - } -} - -/// @dev Models a modifiable and iterable set of uint values. -library IntegerSet -{ - struct data - { - /// Mapping item => index (or zero if not present) - mapping(uint => uint) index; - /// Items by index (index 0 is invalid), items with index[item] == 0 are invalid. - uint[] items; - /// Number of stored items. - uint size; - } - function insert(data storage self, uint value) returns (bool alreadyPresent) - { - uint index = self.index[value]; - if (index > 0) - return true; - else - { - if (self.items.length == 0) self.items.length = 1; - index = self.items.length++; - self.items[index] = value; - self.index[value] = index; - self.size++; - return false; - } - } - function remove(data storage self, uint value) returns (bool success) - { - uint index = self.index[value]; - if (index == 0) - return false; - delete self.index[value]; - delete self.items[index]; - self.size --; - } - function contains(data storage self, uint value) returns (bool) - { - return self.index[value] > 0; - } - function iterate_start(data storage self) returns (uint index) - { - return iterate_advance(self, 0); - } - function iterate_valid(data storage self, uint index) returns (bool) - { - return index < self.items.length; - } - function iterate_advance(data storage self, uint index) returns (uint r_index) - { - index++; - while (iterate_valid(self, index) && self.index[self.items[index]] == index) - index++; - return index; - } - function iterate_get(data storage self, uint index) returns (uint value) - { - return self.items[index]; - } -} - -/// How to use it: -contract User -{ - /// Just a struct holding our data. - IntegerSet.data data; - /// Insert something - function insert(uint v) returns (uint size) - { - /// Sends `data` via reference, so IntegerSet can modify it. - IntegerSet.insert(data, v); - /// We can access members of the struct - but we should take care not to mess with them. - return data.size; - } - /// Computes the sum of all stored data. - function sum() returns (uint s) - { - for (var i = IntegerSet.iterate_start(data); IntegerSet.iterate_valid(data, i); i = IntegerSet.iterate_advance(data, i)) - s += IntegerSet.iterate_get(data, i); - } -} - -// This broke it at one point (namely the modifiers). -contract DualIndex { - mapping(uint => mapping(uint => uint)) data; - address public admin; - - modifier restricted { if (msg.sender == admin) _; } - - function DualIndex() { - admin = msg.sender; - } - - function set(uint key1, uint key2, uint value) restricted { - uint[2][4] memory defaults; // "memory" broke things at one time. - data[key1][key2] = value; - } - - function transfer_ownership(address _admin) restricted { - admin = _admin; - } - - function lookup(uint key1, uint key2) returns(uint) { - return data[key1][key2]; - } -} - -contract A { - -} - -contract B { - -} - -contract C is A, B { - -} - -contract TestPrivate -{ - uint private value; -} - -contract TestInternal -{ - uint internal value; -} - -contract FromSolparse is A, B, TestPrivate, TestInternal { - function() { - uint a = 6 ** 9; - var (x) = 100; - uint y = 2 days; - } -} - -contract CommentedOutFunction { - // FYI: This empty function, as well as the commented - // out function below (bad code) is important to this test. - function() { - - } - - // function something() - // uint x = 10; - // } -} - -library VarHasBrackets { - string constant specialRight = "}"; - //string storage specialLeft = "{"; -} - -library UsingExampleLibrary { - function sum(uint[] storage self) returns (uint s) { - for (uint i = 0; i < self.length; i++) - s += self[i]; - } -} - -contract UsingExampleContract { - using UsingExampleLibrary for uint[]; -} - -contract NewStuff { - uint[] b; - - function someFunction() payable { - string storage a = hex"ab1248fe"; - b[2+2]; - } -} - -// modifier with expression -contract MyContract { - function fun() mymodifier(foo.bar()) {} -} - -library GetCode { - function at(address _addr) returns (bytes o_code) { - assembly { - // retrieve the size of the code, this needs assembly - let size := extcodesize(_addr) - // allocate output byte array - this could also be done without assembly - // by using o_code = new bytes(size) - o_code := mload(0x40) - // new "memory end" including padding - mstore(0x40, add(o_code, and(add(add(size, 0x20), 0x1f), not(0x1f)))) - // store length in memory - mstore(o_code, size) - // actually retrieve the code, this needs assembly - extcodecopy(_addr, add(o_code, 0x20), 0, size) - } - } -} - -contract assemblyLocalBinding { - function test(){ - assembly { - let v := 1 - let x := 0x00 - let y := x - let z := "hello" - } - } -} - -contract assemblyReturn { - uint a = 10; - - function get() constant returns(uint) { - assembly { - mstore(0x40, sload(0)) - byte(0) - address(0) - return(0x40,32) - } - } -} - -contract usesConst { - uint const = 0; -} - -contract memoryArrays { - uint seven = 7; - - function returnNumber(uint number) returns (uint){ - return number; - } - - function alloc() { - uint[] memory a = new uint[](7); - uint[] memory b = new uint[](returnNumber(seven)); - } -} - -contract DeclarativeExpressions { - uint a; - uint b = 7; - uint b2=0; - uint public c; - uint constant public d; - uint public constant e; - uint private constant f = 7; - struct S { uint q;} - - function ham(S storage s1, uint[] storage arr) internal { - uint x; - uint y = 7; - S storage s2 = s1; - uint[] memory stor; - uint[] storage stor2 = arr; - } -} - -contract VariableDeclarationTuple { - function getMyTuple() returns (bool, bool){ - return (true, false); - } - - function ham (){ - var (x, y) = (10, 20); - var (a, b) = getMyTuple(); - var (,c) = (10, 20); - var (d,,) = (10, 20, 30); - var (,e,,f,) = (10, 20, 30, 40, 50); - - var ( - num1, num2, - num3, ,num5 - ) = (10, 20, 30, 40, 50); - } -} - -contract TypeIndexSpacing { - uint [ 7 ] x; - uint [] y; -} - -contract Ballot { - - struct Voter { - uint weight; - bool voted; - } - - function abstain() returns (bool) { - return false; - } - - function foobar() payable owner (myPrice) returns (uint[], address myAdd, string[] names) {} - function foobar() payable owner (myPrice) returns (uint[], address myAdd, string[] names); - - Voter you = Voter(1, true); - - Voter me = Voter({ - weight: 2, - voted: abstain() - }); - - Voter airbnb = Voter({ - weight: 2, - voted: true, - }); -} - -contract multilineReturn { - function a() returns (uint x) { - return - 5; - } -} diff --git a/packages/sol-cov/test/fixtures/contracts/SimpleStorage.sol b/packages/sol-cov/test/fixtures/contracts/SimpleStorage.sol deleted file mode 100644 index e4b4ac246..000000000 --- a/packages/sol-cov/test/fixtures/contracts/SimpleStorage.sol +++ /dev/null @@ -1,11 +0,0 @@ -pragma solidity ^0.4.21; - -contract SimpleStorage { - uint public storedData; - function set(uint x) { - storedData = x; - } - function get() constant returns (uint retVal) { - return storedData; - } -} diff --git a/packages/sol-cov/test/fixtures/contracts/Simplest.sol b/packages/sol-cov/test/fixtures/contracts/Simplest.sol deleted file mode 100644 index d71016e07..000000000 --- a/packages/sol-cov/test/fixtures/contracts/Simplest.sol +++ /dev/null @@ -1,2 +0,0 @@ -contract Simplest { -} diff --git a/packages/sol-cov/test/fixtures/contracts/SolcovIgnore.sol b/packages/sol-cov/test/fixtures/contracts/SolcovIgnore.sol deleted file mode 100644 index a7977ffb4..000000000 --- a/packages/sol-cov/test/fixtures/contracts/SolcovIgnore.sol +++ /dev/null @@ -1,22 +0,0 @@ -pragma solidity ^0.4.21; - -contract SolcovIgnore { - uint public storedData; - - function set(uint x) public { - /* solcov ignore next */ - storedData = x; - } - - /* solcov ignore next */ - function get() constant public returns (uint retVal) { - return storedData; - } -} - -/* solcov ignore next */ -contract Ignore { - function ignored() public returns (bool) { - return false; - } -} diff --git a/packages/sol-cov/test/instructions_test.ts b/packages/sol-cov/test/instructions_test.ts deleted file mode 100644 index 058053cf9..000000000 --- a/packages/sol-cov/test/instructions_test.ts +++ /dev/null @@ -1,19 +0,0 @@ -import * as chai from 'chai'; -import 'mocha'; - -import { constants } from '../src/constants'; -import { getPcToInstructionIndexMapping } from '../src/instructions'; - -const expect = chai.expect; - -describe('instructions', () => { - describe('#getPcToInstructionIndexMapping', () => { - it('correctly maps pcs to instruction indexed', () => { - // tslint:disable-next-line:custom-no-magic-numbers - const bytecode = new Uint8Array([constants.PUSH1, 42, constants.PUSH2, 1, 2, constants.TIMESTAMP]); - const pcToInstruction = getPcToInstructionIndexMapping(bytecode); - const expectedPcToInstruction = { '0': 0, '2': 1, '5': 2 }; - expect(pcToInstruction).to.be.deep.equal(expectedPcToInstruction); - }); - }); -}); diff --git a/packages/sol-cov/test/sol_compiler_artifact_adapter_test.ts b/packages/sol-cov/test/sol_compiler_artifact_adapter_test.ts deleted file mode 100644 index 9c58d2cef..000000000 --- a/packages/sol-cov/test/sol_compiler_artifact_adapter_test.ts +++ /dev/null @@ -1,29 +0,0 @@ -import * as chai from 'chai'; -import * as _ from 'lodash'; -import 'mocha'; -import * as path from 'path'; - -import { SolCompilerArtifactAdapter } from '../src/artifact_adapters/sol_compiler_artifact_adapter'; - -const expect = chai.expect; - -describe('SolCompilerArtifactAdapter', () => { - describe('#collectContractsData', () => { - it('correctly collects contracts data', async () => { - const artifactsPath = path.resolve(__dirname, 'fixtures/artifacts'); - const sourcesPath = path.resolve(__dirname, 'fixtures/contracts'); - const zeroExArtifactsAdapter = new SolCompilerArtifactAdapter(artifactsPath, sourcesPath); - const contractsData = await zeroExArtifactsAdapter.collectContractsDataAsync(); - _.forEach(contractsData, contractData => { - expect(contractData).to.have.keys([ - 'sourceCodes', - 'sources', - 'sourceMap', - 'sourceMapRuntime', - 'bytecode', - 'runtimeBytecode', - ]); - }); - }); - }); -}); diff --git a/packages/sol-cov/test/source_maps_test.ts b/packages/sol-cov/test/source_maps_test.ts deleted file mode 100644 index 5820bedd7..000000000 --- a/packages/sol-cov/test/source_maps_test.ts +++ /dev/null @@ -1,71 +0,0 @@ -import * as chai from 'chai'; -import * as fs from 'fs'; -import * as _ from 'lodash'; -import 'mocha'; -import * as path from 'path'; - -import { getLocationByOffset, parseSourceMap } from '../src/source_maps'; - -const expect = chai.expect; - -const simplestContractBaseName = 'Simplest.sol'; -const simplestContractFileName = path.resolve(__dirname, 'fixtures/contracts', simplestContractBaseName); -const simplestContract = fs.readFileSync(simplestContractFileName).toString(); - -describe('source maps', () => { - describe('#getLocationByOffset', () => { - it('correctly computes location by offset', () => { - const locationByOffset = getLocationByOffset(simplestContract); - const expectedLocationByOffset = { - '0': { line: 1, column: 0 }, - '1': { line: 1, column: 1 }, - '2': { line: 1, column: 2 }, - '3': { line: 1, column: 3 }, - '4': { line: 1, column: 4 }, - '5': { line: 1, column: 5 }, - '6': { line: 1, column: 6 }, - '7': { line: 1, column: 7 }, - '8': { line: 1, column: 8 }, - '9': { line: 1, column: 9 }, - '10': { line: 1, column: 10 }, - '11': { line: 1, column: 11 }, - '12': { line: 1, column: 12 }, - '13': { line: 1, column: 13 }, - '14': { line: 1, column: 14 }, - '15': { line: 1, column: 15 }, - '16': { line: 1, column: 16 }, - '17': { line: 1, column: 17 }, - '18': { line: 1, column: 18 }, - '19': { line: 1, column: 19 }, - '20': { line: 2, column: 0 }, - '21': { line: 2, column: 1 }, - '22': { line: 3, column: 0 }, - }; - expect(locationByOffset).to.be.deep.equal(expectedLocationByOffset); - }); - }); - describe('#parseSourceMap', () => { - it('correctly parses the source map', () => { - // This is the source map and bytecode for an empty contract like Example.sol - const srcMap = '0:21:0:-;;;;;;;;;;;;;;;;;'; - const bytecodeHex = - '60606040523415600e57600080fd5b603580601b6000396000f3006060604052600080fd00a165627a7a72305820377cdef690e46589f40efeef14d8ef73504af059fb3fd46f1da3cd2fc52ef7890029'; - const sources = [simplestContractBaseName]; - const pcToSourceRange = parseSourceMap([simplestContract], srcMap, bytecodeHex, sources); - const expectedSourceRange = { - location: { - start: { line: 1, column: 0 }, - end: { line: 2, column: 1 }, - }, - fileName: simplestContractBaseName, - }; - _.forEach(pcToSourceRange, sourceRange => { - // Solidity source maps are too short and we map some instructions to undefined - // Source: https://github.com/ethereum/solidity/issues/3741 - if (!_.isUndefined(sourceRange)) { - expect(sourceRange).to.be.deep.equal(expectedSourceRange); - } - }); - }); - }); -}); diff --git a/packages/sol-cov/test/trace_test.ts b/packages/sol-cov/test/trace_test.ts deleted file mode 100644 index 7a034362c..000000000 --- a/packages/sol-cov/test/trace_test.ts +++ /dev/null @@ -1,55 +0,0 @@ -import * as chai from 'chai'; -import { OpCode, StructLog } from 'ethereum-types'; -import * as _ from 'lodash'; -import 'mocha'; - -import { getTracesByContractAddress } from '../src/trace'; - -const expect = chai.expect; - -const DEFAULT_STRUCT_LOG: StructLog = { - depth: 0, - error: '', - gas: 0, - gasCost: 0, - memory: [], - op: OpCode.Invalid, - pc: 0, - stack: [], - storage: {}, -}; - -function addDefaultStructLogFields(compactStructLog: Partial & { op: OpCode; depth: number }): StructLog { - return { ...DEFAULT_STRUCT_LOG, ...compactStructLog }; -} - -describe('Trace', () => { - describe('#getTracesByContractAddress', () => { - it('correctly splits trace by contract address', () => { - const delegateCallAddress = '0x0000000000000000000000000000000000000002'; - const trace = [ - { - op: OpCode.DelegateCall, - stack: [delegateCallAddress, '0x'], - depth: 0, - }, - { - op: OpCode.Return, - depth: 1, - }, - { - op: OpCode.Return, - depth: 0, - }, - ]; - const fullTrace = _.map(trace, compactStructLog => addDefaultStructLogFields(compactStructLog)); - const startAddress = '0x0000000000000000000000000000000000000001'; - const traceByContractAddress = getTracesByContractAddress(fullTrace, startAddress); - const expectedTraceByContractAddress = { - [startAddress]: [fullTrace[0], fullTrace[2]], - [delegateCallAddress]: [fullTrace[1]], - }; - expect(traceByContractAddress).to.be.deep.equal(expectedTraceByContractAddress); - }); - }); -}); diff --git a/packages/sol-cov/test/utils_test.ts b/packages/sol-cov/test/utils_test.ts deleted file mode 100644 index 6fc8fcfe1..000000000 --- a/packages/sol-cov/test/utils_test.ts +++ /dev/null @@ -1,53 +0,0 @@ -import * as chai from 'chai'; -import * as dirtyChai from 'dirty-chai'; -import 'mocha'; - -import { utils } from '../src/utils'; - -chai.use(dirtyChai); -const expect = chai.expect; - -describe('utils', () => { - describe('#compareLineColumn', () => { - it('correctly compares LineColumns', () => { - expect(utils.compareLineColumn({ line: 1, column: 3 }, { line: 1, column: 4 })).to.be.lessThan(0); - expect(utils.compareLineColumn({ line: 1, column: 4 }, { line: 1, column: 3 })).to.be.greaterThan(0); - expect(utils.compareLineColumn({ line: 1, column: 3 }, { line: 1, column: 3 })).to.be.equal(0); - expect(utils.compareLineColumn({ line: 0, column: 2 }, { line: 1, column: 0 })).to.be.lessThan(0); - expect(utils.compareLineColumn({ line: 1, column: 0 }, { line: 0, column: 2 })).to.be.greaterThan(0); - }); - }); - - describe('#isRangeInside', () => { - it('returns true if inside', () => { - expect( - utils.isRangeInside( - { start: { line: 1, column: 3 }, end: { line: 1, column: 4 } }, - { start: { line: 1, column: 2 }, end: { line: 1, column: 5 } }, - ), - ).to.be.true(); - }); - it('returns true if the same', () => { - expect( - utils.isRangeInside( - { start: { line: 1, column: 3 }, end: { line: 1, column: 4 } }, - { start: { line: 1, column: 3 }, end: { line: 1, column: 4 } }, - ), - ).to.be.true(); - }); - it('returns false if not inside', () => { - expect( - utils.isRangeInside( - { start: { line: 1, column: 3 }, end: { line: 1, column: 4 } }, - { start: { line: 1, column: 4 }, end: { line: 1, column: 4 } }, - ), - ).to.be.false(); - expect( - utils.isRangeInside( - { start: { line: 1, column: 3 }, end: { line: 1, column: 4 } }, - { start: { line: 1, column: 4 }, end: { line: 1, column: 5 } }, - ), - ).to.be.false(); - }); - }); -}); diff --git a/packages/sol-cov/tsconfig.json b/packages/sol-cov/tsconfig.json deleted file mode 100644 index 2ee711adc..000000000 --- a/packages/sol-cov/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../tsconfig", - "compilerOptions": { - "outDir": "lib", - "rootDir": "." - }, - "include": ["./src/**/*", "./test/**/*"] -} diff --git a/packages/sol-cov/tslint.json b/packages/sol-cov/tslint.json deleted file mode 100644 index 631f46bca..000000000 --- a/packages/sol-cov/tslint.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "extends": ["@0x/tslint-config"], - "rules": { - "completed-docs": false - } -} diff --git a/packages/sol-cov/typedoc-tsconfig.json b/packages/sol-cov/typedoc-tsconfig.json deleted file mode 100644 index c9b0af1ae..000000000 --- a/packages/sol-cov/typedoc-tsconfig.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "extends": "../../typedoc-tsconfig", - "compilerOptions": { - "outDir": "lib" - }, - "include": ["./src/**/*", "./test/**/*"] -} diff --git a/packages/sol-coverage/.npmignore b/packages/sol-coverage/.npmignore new file mode 100644 index 000000000..037786e46 --- /dev/null +++ b/packages/sol-coverage/.npmignore @@ -0,0 +1,6 @@ +.* +yarn-error.log +/src/ +/scripts/ +tsconfig.json +/lib/src/monorepo_scripts/ diff --git a/packages/sol-coverage/CHANGELOG.json b/packages/sol-coverage/CHANGELOG.json new file mode 100644 index 000000000..ca6048d99 --- /dev/null +++ b/packages/sol-coverage/CHANGELOG.json @@ -0,0 +1,12 @@ +[ + { + "version": "1.0.0", + "changes": [ + { + "note": + "Initial release as a separate package. For historic entries check @0x/sol-trace-based-tools-common", + "pr": 1492 + } + ] + } +] diff --git a/packages/sol-coverage/README.md b/packages/sol-coverage/README.md new file mode 100644 index 000000000..f8cc62eb8 --- /dev/null +++ b/packages/sol-coverage/README.md @@ -0,0 +1,75 @@ +## @0x/sol-coverage + +A Solidity code coverage tool. + +### Read the [Documentation](https://0xproject.com/docs/sol-coverage). + +## Installation + +```bash +yarn add @0x/sol-coverage +``` + +**Import** + +```javascript +import { CoverageSubprovider } from '@0x/sol-coverage'; +``` + +or + +```javascript +var CoverageSubprovider = require('@0x/sol-coverage').CoverageSubprovider; +``` + +## Contributing + +We welcome improvements and fixes from the wider community! 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 +``` + +### Build + +To build this package and all other monorepo packages that it depends on, run the following from the monorepo root directory: + +```bash +PKG=@0x/sol-coverage yarn build +``` + +Or continuously rebuild on change: + +```bash +PKG=@0x/sol-coverage yarn watch +``` + +### Clean + +```bash +yarn clean +``` + +### Lint + +```bash +yarn lint +``` + +### Run Tests + +```bash +yarn test +``` diff --git a/packages/sol-coverage/package.json b/packages/sol-coverage/package.json new file mode 100644 index 000000000..b95fa5502 --- /dev/null +++ b/packages/sol-coverage/package.json @@ -0,0 +1,52 @@ +{ + "name": "@0x/sol-coverage", + "version": "1.0.0", + "engines": { + "node": ">=6.12" + }, + "description": "Generate coverage reports for Solidity code", + "main": "lib/src/index.js", + "types": "lib/src/index.d.ts", + "scripts": { + "build": "tsc -b", + "build:ci": "yarn build", + "lint": "tslint --format stylish --project .", + "clean": "shx rm -rf lib src/artifacts generated_docs", + "docs:json": "typedoc --excludePrivate --excludeExternals --target ES5 --tsconfig typedoc-tsconfig.json --json $JSON_FILE_PATH $PROJECT_FILES" + }, + "config": { + "postpublish": { + "assets": [] + } + }, + "repository": { + "type": "git", + "url": "https://github.com/0xProject/0x-monorepo.git" + }, + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/0xProject/0x-monorepo/issues" + }, + "homepage": "https://github.com/0xProject/0x-monorepo/packages/sol-coverage/README.md", + "dependencies": { + "@0x/subproviders": "^2.1.8", + "@0x/sol-trace-based-tools-common": "^2.1.16", + "@0x/typescript-typings": "^3.0.6", + "ethereum-types": "^1.1.4", + "lodash": "^4.17.5" + }, + "devDependencies": { + "@0x/tslint-config": "^2.0.0", + "@types/node": "*", + "npm-run-all": "^4.1.2", + "nyc": "^11.0.1", + "shx": "^0.2.2", + "sinon": "^4.0.0", + "tslint": "5.11.0", + "typedoc": "0.13.0", + "typescript": "3.0.1" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/sol-coverage/src/coverage_subprovider.ts b/packages/sol-coverage/src/coverage_subprovider.ts new file mode 100644 index 000000000..255fa9481 --- /dev/null +++ b/packages/sol-coverage/src/coverage_subprovider.ts @@ -0,0 +1,146 @@ +import { + AbstractArtifactAdapter, + BranchCoverage, + collectCoverageEntries, + ContractData, + Coverage, + FunctionCoverage, + FunctionDescription, + SingleFileSubtraceHandler, + SourceRange, + StatementCoverage, + StatementDescription, + Subtrace, + TraceCollector, + TraceInfo, + TraceInfoSubprovider, + utils, +} from '@0x/sol-trace-based-tools-common'; +import * as _ from 'lodash'; + +/** + * This class implements the [web3-provider-engine](https://github.com/MetaMask/provider-engine) subprovider interface. + * It's used to compute your code coverage while running solidity tests. + */ +export class CoverageSubprovider extends TraceInfoSubprovider { + private readonly _coverageCollector: TraceCollector; + /** + * Instantiates a CoverageSubprovider instance + * @param artifactAdapter Adapter for used artifacts format (0x, truffle, giveth, etc.) + * @param defaultFromAddress default from address to use when sending transactions + * @param isVerbose If true, we will log any unknown transactions. Otherwise we will ignore them + */ + constructor(artifactAdapter: AbstractArtifactAdapter, defaultFromAddress: string, isVerbose: boolean = true) { + const traceCollectionSubproviderConfig = { + shouldCollectTransactionTraces: true, + shouldCollectGasEstimateTraces: true, + shouldCollectCallTraces: true, + }; + super(defaultFromAddress, traceCollectionSubproviderConfig); + this._coverageCollector = new TraceCollector(artifactAdapter, isVerbose, coverageHandler); + } + protected async _handleTraceInfoAsync(traceInfo: TraceInfo): Promise { + await this._coverageCollector.computeSingleTraceCoverageAsync(traceInfo); + } + /** + * Write the test coverage results to a file in Istanbul format. + */ + public async writeCoverageAsync(): Promise { + await this._coverageCollector.writeOutputAsync(); + } +} + +/** + * Computed partial coverage for a single file & subtrace. + * @param contractData Contract metadata (source, srcMap, bytecode) + * @param subtrace A subset of a transcation/call trace that was executed within that contract + * @param pcToSourceRange A mapping from program counters to source ranges + * @param fileIndex Index of a file to compute coverage for + * @return Partial istanbul coverage for that file & subtrace + */ +export const coverageHandler: SingleFileSubtraceHandler = ( + contractData: ContractData, + subtrace: Subtrace, + pcToSourceRange: { [programCounter: number]: SourceRange }, + fileIndex: number, +): Coverage => { + const absoluteFileName = contractData.sources[fileIndex]; + const coverageEntriesDescription = collectCoverageEntries(contractData.sourceCodes[fileIndex]); + + // if the source wasn't provided for the fileIndex, we can't cover the file + if (_.isUndefined(coverageEntriesDescription)) { + return {}; + } + + let sourceRanges = _.map(subtrace, structLog => pcToSourceRange[structLog.pc]); + 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 === absoluteFileName); + const branchCoverage: BranchCoverage = {}; + const branchIds = _.keys(coverageEntriesDescription.branchMap); + for (const branchId of branchIds) { + const branchDescription = coverageEntriesDescription.branchMap[branchId]; + const isBranchCoveredByBranchIndex = _.map(branchDescription.locations, location => { + const isBranchCovered = _.some(sourceRanges, range => utils.isRangeInside(range.location, location)); + const timesBranchCovered = Number(isBranchCovered); + return timesBranchCovered; + }); + branchCoverage[branchId] = isBranchCoveredByBranchIndex; + } + const statementCoverage: StatementCoverage = {}; + const statementIds = _.keys(coverageEntriesDescription.statementMap); + for (const statementId of statementIds) { + const statementDescription = coverageEntriesDescription.statementMap[statementId]; + const isStatementCovered = _.some(sourceRanges, range => + utils.isRangeInside(range.location, statementDescription), + ); + const timesStatementCovered = Number(isStatementCovered); + statementCoverage[statementId] = timesStatementCovered; + } + const functionCoverage: FunctionCoverage = {}; + const functionIds = _.keys(coverageEntriesDescription.fnMap); + for (const fnId of functionIds) { + const functionDescription = coverageEntriesDescription.fnMap[fnId]; + const isFunctionCovered = _.some(sourceRanges, range => + utils.isRangeInside(range.location, functionDescription.loc), + ); + const timesFunctionCovered = Number(isFunctionCovered); + functionCoverage[fnId] = timesFunctionCovered; + } + // 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; + }, + ); + const timesModifierCovered = Number(isModifierCovered); + statementCoverage[modifierStatementId] = timesModifierCovered; + } + const partialCoverage = { + [absoluteFileName]: { + ...coverageEntriesDescription, + path: absoluteFileName, + f: functionCoverage, + s: statementCoverage, + b: branchCoverage, + }, + }; + return partialCoverage; +}; diff --git a/packages/sol-coverage/src/globals.d.ts b/packages/sol-coverage/src/globals.d.ts new file mode 100644 index 000000000..e799b3529 --- /dev/null +++ b/packages/sol-coverage/src/globals.d.ts @@ -0,0 +1,7 @@ +// tslint:disable:completed-docs +declare module '*.json' { + const json: any; + /* tslint:disable */ + export default json; + /* tslint:enable */ +} diff --git a/packages/sol-coverage/src/index.ts b/packages/sol-coverage/src/index.ts new file mode 100644 index 000000000..dcf97b72e --- /dev/null +++ b/packages/sol-coverage/src/index.ts @@ -0,0 +1,23 @@ +export { CoverageSubprovider } from './coverage_subprovider'; +export { + SolCompilerArtifactAdapter, + TruffleArtifactAdapter, + AbstractArtifactAdapter, + ContractData, +} from '@0x/sol-trace-based-tools-common'; + +export { + JSONRPCRequestPayload, + Provider, + JSONRPCErrorCallback, + JSONRPCResponsePayload, + JSONRPCResponseError, +} from 'ethereum-types'; + +export { + JSONRPCRequestPayloadWithMethod, + NextCallback, + ErrorCallback, + OnNextCompleted, + Callback, +} from '@0x/subproviders'; diff --git a/packages/sol-coverage/tsconfig.json b/packages/sol-coverage/tsconfig.json new file mode 100644 index 000000000..233008d61 --- /dev/null +++ b/packages/sol-coverage/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig", + "compilerOptions": { + "outDir": "lib", + "rootDir": "." + }, + "include": ["./src/**/*"] +} diff --git a/packages/sol-coverage/tslint.json b/packages/sol-coverage/tslint.json new file mode 100644 index 000000000..dd9053357 --- /dev/null +++ b/packages/sol-coverage/tslint.json @@ -0,0 +1,3 @@ +{ + "extends": ["@0x/tslint-config"] +} diff --git a/packages/sol-coverage/typedoc-tsconfig.json b/packages/sol-coverage/typedoc-tsconfig.json new file mode 100644 index 000000000..c9b0af1ae --- /dev/null +++ b/packages/sol-coverage/typedoc-tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../typedoc-tsconfig", + "compilerOptions": { + "outDir": "lib" + }, + "include": ["./src/**/*", "./test/**/*"] +} diff --git a/packages/sol-profiler/.npmignore b/packages/sol-profiler/.npmignore new file mode 100644 index 000000000..037786e46 --- /dev/null +++ b/packages/sol-profiler/.npmignore @@ -0,0 +1,6 @@ +.* +yarn-error.log +/src/ +/scripts/ +tsconfig.json +/lib/src/monorepo_scripts/ diff --git a/packages/sol-profiler/CHANGELOG.json b/packages/sol-profiler/CHANGELOG.json new file mode 100644 index 000000000..ca6048d99 --- /dev/null +++ b/packages/sol-profiler/CHANGELOG.json @@ -0,0 +1,12 @@ +[ + { + "version": "1.0.0", + "changes": [ + { + "note": + "Initial release as a separate package. For historic entries check @0x/sol-trace-based-tools-common", + "pr": 1492 + } + ] + } +] diff --git a/packages/sol-profiler/README.md b/packages/sol-profiler/README.md new file mode 100644 index 000000000..44fa29e3f --- /dev/null +++ b/packages/sol-profiler/README.md @@ -0,0 +1,75 @@ +## @0x/sol-profiler + +Solidity line-by-line gas profiler. + +### Read the [Documentation](https://0xproject.com/docs/sol-profiler). + +## Installation + +```bash +yarn add @0x/sol-profiler +``` + +**Import** + +```javascript +import { ProfilerSubprovider } from '@0x/sol-profiler'; +``` + +or + +```javascript +var ProfilerSubprovider = require('@0x/sol-profiler').ProfilerSubprovider; +``` + +## Contributing + +We welcome improvements and fixes from the wider community! 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 +``` + +### Build + +To build this package and all other monorepo packages that it depends on, run the following from the monorepo root directory: + +```bash +PKG=@0x/sol-profiler yarn build +``` + +Or continuously rebuild on change: + +```bash +PKG=@0x/sol-profiler yarn watch +``` + +### Clean + +```bash +yarn clean +``` + +### Lint + +```bash +yarn lint +``` + +### Run Tests + +```bash +yarn test +``` diff --git a/packages/sol-profiler/package.json b/packages/sol-profiler/package.json new file mode 100644 index 000000000..03b421b0e --- /dev/null +++ b/packages/sol-profiler/package.json @@ -0,0 +1,50 @@ +{ + "name": "@0x/sol-profiler", + "version": "1.0.0", + "engines": { + "node": ">=6.12" + }, + "description": "Generate profiler reports for Solidity code", + "main": "lib/src/index.js", + "types": "lib/src/index.d.ts", + "scripts": { + "build": "tsc -b", + "build:ci": "yarn build", + "lint": "tslint --format stylish --project .", + "clean": "shx rm -rf lib src/artifacts generated_docs", + "docs:json": "typedoc --excludePrivate --excludeExternals --target ES5 --tsconfig typedoc-tsconfig.json --json $JSON_FILE_PATH $PROJECT_FILES" + }, + "config": { + "postpublish": { + "assets": [] + } + }, + "repository": { + "type": "git", + "url": "https://github.com/0xProject/0x-monorepo.git" + }, + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/0xProject/0x-monorepo/issues" + }, + "homepage": "https://github.com/0xProject/0x-monorepo/packages/sol-profiler/README.md", + "dependencies": { + "@0x/subproviders": "^2.1.8", + "@0x/typescript-typings": "^3.0.6", + "@0x/sol-trace-based-tools-common": "^2.1.16", + "ethereum-types": "^1.1.4", + "lodash": "^4.17.5" + }, + "devDependencies": { + "@0x/tslint-config": "^2.0.0", + "@types/node": "*", + "npm-run-all": "^4.1.2", + "shx": "^0.2.2", + "tslint": "5.11.0", + "typedoc": "0.13.0", + "typescript": "3.0.1" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/sol-profiler/src/globals.d.ts b/packages/sol-profiler/src/globals.d.ts new file mode 100644 index 000000000..e799b3529 --- /dev/null +++ b/packages/sol-profiler/src/globals.d.ts @@ -0,0 +1,7 @@ +// tslint:disable:completed-docs +declare module '*.json' { + const json: any; + /* tslint:disable */ + export default json; + /* tslint:enable */ +} diff --git a/packages/sol-profiler/src/index.ts b/packages/sol-profiler/src/index.ts new file mode 100644 index 000000000..b36ed5baa --- /dev/null +++ b/packages/sol-profiler/src/index.ts @@ -0,0 +1,25 @@ +export { + AbstractArtifactAdapter, + SolCompilerArtifactAdapter, + TruffleArtifactAdapter, + ContractData, +} from '@0x/sol-trace-based-tools-common'; + +// HACK: ProfilerSubprovider is a hacky way to do profiling using coverage tools. Not production ready +export { ProfilerSubprovider } from './profiler_subprovider'; + +export { + JSONRPCRequestPayload, + Provider, + JSONRPCErrorCallback, + JSONRPCResponsePayload, + JSONRPCResponseError, +} from 'ethereum-types'; + +export { + JSONRPCRequestPayloadWithMethod, + NextCallback, + ErrorCallback, + OnNextCompleted, + Callback, +} from '@0x/subproviders'; diff --git a/packages/sol-profiler/src/profiler_subprovider.ts b/packages/sol-profiler/src/profiler_subprovider.ts new file mode 100644 index 000000000..61dc25b6b --- /dev/null +++ b/packages/sol-profiler/src/profiler_subprovider.ts @@ -0,0 +1,98 @@ +import * as _ from 'lodash'; + +import { + AbstractArtifactAdapter, + collectCoverageEntries, + ContractData, + Coverage, + SingleFileSubtraceHandler, + SourceRange, + Subtrace, + TraceCollector, + TraceInfo, + TraceInfoSubprovider, + utils, +} from '@0x/sol-trace-based-tools-common'; + +/** + * This class implements the [web3-provider-engine](https://github.com/MetaMask/provider-engine) subprovider interface. + * ProfilerSubprovider is used to profile Solidity code while running tests. + */ +export class ProfilerSubprovider extends TraceInfoSubprovider { + private readonly _profilerCollector: TraceCollector; + /** + * Instantiates a ProfilerSubprovider instance + * @param artifactAdapter Adapter for used artifacts format (0x, truffle, giveth, etc.) + * @param defaultFromAddress default from address to use when sending transactions + * @param isVerbose If true, we will log any unknown transactions. Otherwise we will ignore them + */ + constructor(artifactAdapter: AbstractArtifactAdapter, defaultFromAddress: string, isVerbose: boolean = true) { + const traceCollectionSubproviderConfig = { + shouldCollectTransactionTraces: true, + shouldCollectGasEstimateTraces: false, + shouldCollectCallTraces: false, + }; + super(defaultFromAddress, traceCollectionSubproviderConfig); + this._profilerCollector = new TraceCollector(artifactAdapter, isVerbose, profilerHandler); + } + protected async _handleTraceInfoAsync(traceInfo: TraceInfo): Promise { + await this._profilerCollector.computeSingleTraceCoverageAsync(traceInfo); + } + /** + * Write the test profiler results to a file in Istanbul format. + */ + public async writeProfilerOutputAsync(): Promise { + await this._profilerCollector.writeOutputAsync(); + } +} + +/** + * Computed partial coverage for a single file & subtrace for the purposes of + * gas profiling. + * @param contractData Contract metadata (source, srcMap, bytecode) + * @param subtrace A subset of a transcation/call trace that was executed within that contract + * @param pcToSourceRange A mapping from program counters to source ranges + * @param fileIndex Index of a file to compute coverage for + * @return Partial istanbul coverage for that file & subtrace + */ +export const profilerHandler: SingleFileSubtraceHandler = ( + contractData: ContractData, + subtrace: Subtrace, + pcToSourceRange: { [programCounter: number]: SourceRange }, + fileIndex: number, +): Coverage => { + const absoluteFileName = contractData.sources[fileIndex]; + const profilerEntriesDescription = collectCoverageEntries(contractData.sourceCodes[fileIndex]); + const gasConsumedByStatement: { [statementId: string]: number } = {}; + const statementIds = _.keys(profilerEntriesDescription.statementMap); + for (const statementId of statementIds) { + const statementDescription = profilerEntriesDescription.statementMap[statementId]; + const totalGasCost = _.sum( + _.map(subtrace, structLog => { + const sourceRange = pcToSourceRange[structLog.pc]; + if (_.isUndefined(sourceRange)) { + return 0; + } + if (sourceRange.fileName !== absoluteFileName) { + return 0; + } + if (utils.isRangeInside(sourceRange.location, statementDescription)) { + return structLog.gasCost; + } else { + return 0; + } + }), + ); + gasConsumedByStatement[statementId] = totalGasCost; + } + const partialProfilerOutput = { + [absoluteFileName]: { + ...profilerEntriesDescription, + path: absoluteFileName, + f: {}, // I's meaningless in profiling context + s: gasConsumedByStatement, + b: {}, // I's meaningless in profiling context + }, + }; + return partialProfilerOutput; +}; diff --git a/packages/sol-profiler/tsconfig.json b/packages/sol-profiler/tsconfig.json new file mode 100644 index 000000000..233008d61 --- /dev/null +++ b/packages/sol-profiler/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig", + "compilerOptions": { + "outDir": "lib", + "rootDir": "." + }, + "include": ["./src/**/*"] +} diff --git a/packages/sol-profiler/tslint.json b/packages/sol-profiler/tslint.json new file mode 100644 index 000000000..dd9053357 --- /dev/null +++ b/packages/sol-profiler/tslint.json @@ -0,0 +1,3 @@ +{ + "extends": ["@0x/tslint-config"] +} diff --git a/packages/sol-profiler/typedoc-tsconfig.json b/packages/sol-profiler/typedoc-tsconfig.json new file mode 100644 index 000000000..a4c669cb6 --- /dev/null +++ b/packages/sol-profiler/typedoc-tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../typedoc-tsconfig", + "compilerOptions": { + "outDir": "lib" + }, + "include": ["./src/**/*"] +} diff --git a/packages/sol-trace-based-tools-common/.npmignore b/packages/sol-trace-based-tools-common/.npmignore new file mode 100644 index 000000000..037786e46 --- /dev/null +++ b/packages/sol-trace-based-tools-common/.npmignore @@ -0,0 +1,6 @@ +.* +yarn-error.log +/src/ +/scripts/ +tsconfig.json +/lib/src/monorepo_scripts/ diff --git a/packages/sol-trace-based-tools-common/CHANGELOG.json b/packages/sol-trace-based-tools-common/CHANGELOG.json new file mode 100644 index 000000000..4c56d547d --- /dev/null +++ b/packages/sol-trace-based-tools-common/CHANGELOG.json @@ -0,0 +1,398 @@ +[ + { + "version": "3.0.0", + "changes": [ + { + "note": "Move out specific tools and leave just the common part", + "pr": 1492 + } + ] + }, + { + "version": "2.1.16", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1544739608 + }, + { + "version": "2.1.15", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1544570656 + }, + { + "timestamp": 1543401373, + "version": "2.1.14", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1542821676, + "version": "2.1.13", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1542208198, + "version": "2.1.12", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1542134075, + "version": "2.1.11", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1542028948, + "version": "2.1.10", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "version": "2.1.9", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1541740904 + }, + { + "version": "2.1.8", + "changes": [ + { + "note": "Make @types/solidity-parser-antlr a 'dependency' so it's available to users of the library", + "pr": 1105 + } + ], + "timestamp": 1539871071 + }, + { + "version": "2.1.7", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1538693146 + }, + { + "timestamp": 1538157789, + "version": "2.1.6", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1537907159, + "version": "2.1.5", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1537875740, + "version": "2.1.4", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1537541580, + "version": "2.1.3", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1536142250, + "version": "2.1.2", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1535377027, + "version": "2.1.1", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "version": "2.1.0", + "changes": [ + { + "note": + "Export types: `JSONRPCRequestPayload`, `Provider`, `JSONRPCErrorCallback`, `JSONRPCResponsePayload`, `JSONRPCRequestPayloadWithMethod`, `NextCallback`, `ErrorCallback`, `OnNextCompleted` and `Callback`", + "pr": 924 + } + ], + "timestamp": 1535133899 + }, + { + "version": "2.0.0", + "changes": [ + { + "note": + "Fix a bug when eth_call coverage was not computed because of silent schema validation failures", + "pr": 938 + }, + { + "note": "Make `TruffleArtifactAdapter` read the `truffle.js` config for `solc` settings", + "pr": 938 + }, + { + "note": + "Change the first param of `TruffleArtifactAdapter` to be the `projectRoot` instead of `sourcesDir`", + "pr": 938 + }, + { + "note": + "Throw a helpful error message if truffle artifacts were generated with a different solc version than the one passed in", + "pr": 938 + } + ], + "timestamp": 1534210131 + }, + { + "version": "1.0.3", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1532619515 + }, + { + "timestamp": 1532605697, + "version": "1.0.2", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1532357734, + "version": "1.0.1", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1532043000, + "version": "1.0.0", + "changes": [ + { + "note": + "Add artifact adapter as a parameter for `CoverageSubprovider`. Export `AbstractArtifactAdapter`", + "pr": 589 + }, + { + "note": "Implement `SolCompilerArtifactAdapter` and `TruffleArtifactAdapter`", + "pr": 589 + }, + { + "note": "Properly parse multi-level traces", + "pr": 589 + }, + { + "note": "Add support for solidity libraries", + "pr": 589 + }, + { + "note": "Fixed a bug causing `RegExp` to crash if contract code is longer that 32767 characters", + "pr": 675 + }, + { + "note": "Fixed a bug caused by Geth debug trace depth being 1indexed", + "pr": 675 + }, + { + "note": "Fixed a bug when the tool crashed on empty traces", + "pr": 675 + }, + { + "note": "Use `BlockchainLifecycle` to support reverts on Geth", + "pr": 675 + }, + { + "note": "Add `ProfilerSubprovider` as a hacky way to profile code using coverage tools", + "pr": 675 + }, + { + "note": "Collect traces from `estimate_gas` calls", + "pr": 675 + }, + { + "note": "Fix a race condition caused by not awaiting the transaction before getting a trace", + "pr": 675 + }, + { + "note": "Add `start`/`stop` functionality to `CoverageSubprovider` and `ProfilerSubprovider`", + "pr": 675 + }, + { + "note": "Skip interface artifacts with a warning instead of failing", + "pr": 675 + }, + { + "note": "Fix `solcVersion` regex in parameter validation", + "pr": 690 + }, + { + "note": + "Fix a bug when in `TruffleArtifactsAdapter` causing it to throw if `compiler.json` is not there", + "pr": 690 + }, + { + "note": "HUGE perf improvements", + "pr": 690 + }, + { + "note": "Create `RevertTraceSubprovider` which prints a stack trace when a `REVERT` is detected", + "pr": 705 + }, + { + "note": "Add source code snippets to stack traces printed by `RevertTraceSubprovider`", + "pr": 725 + } + ] + }, + { + "timestamp": 1531919263, + "version": "0.1.3", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1531149657, + "version": "0.1.2", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1529397769, + "version": "0.1.1", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "version": "0.1.0", + "changes": [ + { + "note": "Incorrect publish that was unpublished" + } + ], + "timestamp": 1527810075 + }, + { + "timestamp": 1527009134, + "version": "0.0.11", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1525477860, + "version": "0.0.10", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1525428773, + "version": "0.0.9", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1524044013, + "version": "0.0.8", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1523462196, + "version": "0.0.7", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1522673609, + "version": "0.0.6", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1522658513, + "version": "0.0.5", + "changes": [ + { + "note": "Dependencies updated" + } + ] + } +] diff --git a/packages/sol-trace-based-tools-common/CHANGELOG.md b/packages/sol-trace-based-tools-common/CHANGELOG.md new file mode 100644 index 000000000..879ef9c95 --- /dev/null +++ b/packages/sol-trace-based-tools-common/CHANGELOG.md @@ -0,0 +1,158 @@ + + +CHANGELOG + +## v2.1.16 - _December 13, 2018_ + + * Dependencies updated + +## v2.1.15 - _December 11, 2018_ + + * Dependencies updated + +## v2.1.14 - _November 28, 2018_ + + * Dependencies updated + +## v2.1.13 - _November 21, 2018_ + + * Dependencies updated + +## v2.1.12 - _November 14, 2018_ + + * Dependencies updated + +## v2.1.11 - _November 13, 2018_ + + * Dependencies updated + +## v2.1.10 - _November 12, 2018_ + + * Dependencies updated + +## v2.1.9 - _November 9, 2018_ + + * Dependencies updated + +## v2.1.8 - _October 18, 2018_ + + * Make @types/solidity-parser-antlr a 'dependency' so it's available to users of the library (#1105) + +## v2.1.7 - _October 4, 2018_ + + * Dependencies updated + +## v2.1.6 - _September 28, 2018_ + + * Dependencies updated + +## v2.1.5 - _September 25, 2018_ + + * Dependencies updated + +## v2.1.4 - _September 25, 2018_ + + * Dependencies updated + +## v2.1.3 - _September 21, 2018_ + + * Dependencies updated + +## v2.1.2 - _September 5, 2018_ + + * Dependencies updated + +## v2.1.1 - _August 27, 2018_ + + * Dependencies updated + +## v2.1.0 - _August 24, 2018_ + + * Export types: `JSONRPCRequestPayload`, `Provider`, `JSONRPCErrorCallback`, `JSONRPCResponsePayload`, `JSONRPCRequestPayloadWithMethod`, `NextCallback`, `ErrorCallback`, `OnNextCompleted` and `Callback` (#924) + +## v2.0.0 - _August 14, 2018_ + + * Fix a bug when eth_call coverage was not computed because of silent schema validation failures (#938) + * Make `TruffleArtifactAdapter` read the `truffle.js` config for `solc` settings (#938) + * Change the first param of `TruffleArtifactAdapter` to be the `projectRoot` instead of `sourcesDir` (#938) + * Throw a helpful error message if truffle artifacts were generated with a different solc version than the one passed in (#938) + +## v1.0.3 - _July 26, 2018_ + + * Dependencies updated + +## v1.0.2 - _July 26, 2018_ + + * Dependencies updated + +## v1.0.1 - _July 23, 2018_ + + * Dependencies updated + +## v1.0.0 - _July 19, 2018_ + + * Add artifact adapter as a parameter for `CoverageSubprovider`. Export `AbstractArtifactAdapter` (#589) + * Implement `SolCompilerArtifactAdapter` and `TruffleArtifactAdapter` (#589) + * Properly parse multi-level traces (#589) + * Add support for solidity libraries (#589) + * Fixed a bug causing `RegExp` to crash if contract code is longer that 32767 characters (#675) + * Fixed a bug caused by Geth debug trace depth being 1indexed (#675) + * Fixed a bug when the tool crashed on empty traces (#675) + * Use `BlockchainLifecycle` to support reverts on Geth (#675) + * Add `ProfilerSubprovider` as a hacky way to profile code using coverage tools (#675) + * Collect traces from `estimate_gas` calls (#675) + * Fix a race condition caused by not awaiting the transaction before getting a trace (#675) + * Add `start`/`stop` functionality to `CoverageSubprovider` and `ProfilerSubprovider` (#675) + * Skip interface artifacts with a warning instead of failing (#675) + * Fix `solcVersion` regex in parameter validation (#690) + * Fix a bug when in `TruffleArtifactsAdapter` causing it to throw if `compiler.json` is not there (#690) + * HUGE perf improvements (#690) + * Create `RevertTraceSubprovider` which prints a stack trace when a `REVERT` is detected (#705) + * Add source code snippets to stack traces printed by `RevertTraceSubprovider` (#725) + +## v0.1.3 - _July 18, 2018_ + + * Dependencies updated + +## v0.1.2 - _July 9, 2018_ + + * Dependencies updated + +## v0.1.1 - _June 19, 2018_ + + * Dependencies updated + +## v0.1.0 - _May 31, 2018_ + + * Incorrect publish that was unpublished + +## v0.0.11 - _May 22, 2018_ + + * Dependencies updated + +## v0.0.10 - _May 4, 2018_ + + * Dependencies updated + +## v0.0.9 - _May 4, 2018_ + + * Dependencies updated + +## v0.0.8 - _April 18, 2018_ + + * Dependencies updated + +## v0.0.7 - _April 11, 2018_ + + * Dependencies updated + +## v0.0.6 - _April 2, 2018_ + + * Dependencies updated + +## v0.0.5 - _April 2, 2018_ + + * Dependencies updated diff --git a/packages/sol-trace-based-tools-common/README.md b/packages/sol-trace-based-tools-common/README.md new file mode 100644 index 000000000..b6d75dd59 --- /dev/null +++ b/packages/sol-trace-based-tools-common/README.md @@ -0,0 +1,61 @@ +## @0x/sol-trace-based-tools-common + +Common code for all solidity trace-based tools (sol-coverage, sol-profiler, sol-trace). + +## Installation + +```bash +yarn add @0x/sol-sol-trace-based-tools-common +``` + +## Contributing + +We welcome improvements and fixes from the wider community! 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 +``` + +### Build + +To build this package and all other monorepo packages that it depends on, run the following from the monorepo root directory: + +```bash +PKG=@0x/sol-trace-based-tools-common yarn build +``` + +Or continuously rebuild on change: + +```bash +PKG=@0x/sol-trace-based-tools-common yarn watch +``` + +### Clean + +```bash +yarn clean +``` + +### Lint + +```bash +yarn lint +``` + +### Run Tests + +```bash +yarn test +``` diff --git a/packages/sol-trace-based-tools-common/compiler.json b/packages/sol-trace-based-tools-common/compiler.json new file mode 100644 index 000000000..a6a0c6d3a --- /dev/null +++ b/packages/sol-trace-based-tools-common/compiler.json @@ -0,0 +1,18 @@ +{ + "contracts": ["SimpleStorage"], + "contractsDir": "test/fixtures/contracts", + "artifactsDir": "test/fixtures/artifacts", + "compilerSettings": { + "outputSelection": { + "*": { + "*": [ + "abi", + "evm.bytecode.object", + "evm.bytecode.sourceMap", + "evm.deployedBytecode.object", + "evm.deployedBytecode.sourceMap" + ] + } + } + } +} diff --git a/packages/sol-trace-based-tools-common/coverage/.gitkeep b/packages/sol-trace-based-tools-common/coverage/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/packages/sol-trace-based-tools-common/package.json b/packages/sol-trace-based-tools-common/package.json new file mode 100644 index 000000000..6cfabe6d9 --- /dev/null +++ b/packages/sol-trace-based-tools-common/package.json @@ -0,0 +1,87 @@ +{ + "name": "@0x/sol-trace-based-tools-common", + "version": "2.1.16", + "engines": { + "node": ">=6.12" + }, + "description": "Common part of trace based solidity tools (sol-coverage, sol-trace, sol-profiler)", + "main": "lib/src/index.js", + "types": "lib/src/index.d.ts", + "scripts": { + "build": "yarn pre_build && tsc -b", + "build:ci": "yarn build", + "pre_build": "run-s copy_test_fixtures", + "lint": "tslint --format stylish --project .", + "test": "run-s compile_test run_mocha", + "rebuild_and_test": "run-s clean build test", + "test:coverage": "nyc npm run test --all && yarn coverage:report:lcov", + "coverage:report:lcov": "nyc report --reporter=text-lcov > coverage/lcov.info", + "test:circleci": "yarn test:coverage", + "run_mocha": "mocha --require source-map-support/register --require make-promises-safe lib/test/**/*_test.js --exit", + "clean": "shx rm -rf lib test/fixtures/artifacts src/artifacts generated_docs", + "copy_test_fixtures": "copyfiles 'test/fixtures/**/*' ./lib", + "compile_test": "sol-compiler compile", + "docs:json": "typedoc --excludePrivate --excludeExternals --target ES5 --tsconfig typedoc-tsconfig.json --json $JSON_FILE_PATH $PROJECT_FILES" + }, + "config": { + "postpublish": { + "assets": [], + "docOmitExports": [ + "ProfilerSubprovider", + "RevertTraceSubprovider" + ] + } + }, + "repository": { + "type": "git", + "url": "https://github.com/0xProject/0x-monorepo.git" + }, + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/0xProject/0x-monorepo/issues" + }, + "homepage": "https://github.com/0xProject/0x-monorepo/packages/sol-trace-based-tools-common/README.md", + "dependencies": { + "@0x/dev-utils": "^1.0.21", + "@0x/sol-compiler": "^1.1.16", + "@0x/subproviders": "^2.1.8", + "@0x/typescript-typings": "^3.0.6", + "@0x/utils": "^2.0.8", + "@0x/web3-wrapper": "^3.2.1", + "@types/solidity-parser-antlr": "^0.2.0", + "ethereum-types": "^1.1.4", + "ethereumjs-util": "^5.1.1", + "glob": "^7.1.2", + "istanbul": "^0.4.5", + "lodash": "^4.17.5", + "loglevel": "^1.6.1", + "mkdirp": "^0.5.1", + "rimraf": "^2.6.2", + "semaphore-async-await": "^1.5.1", + "solidity-parser-antlr": "^0.2.12" + }, + "devDependencies": { + "@0x/tslint-config": "^2.0.0", + "@types/istanbul": "^0.4.30", + "@types/loglevel": "^1.5.3", + "@types/mkdirp": "^0.5.1", + "@types/mocha": "^2.2.42", + "@types/node": "*", + "@types/rimraf": "^2.0.2", + "chai": "^4.0.1", + "copyfiles": "^2.0.0", + "dirty-chai": "^2.0.1", + "make-promises-safe": "^1.1.0", + "mocha": "^4.1.0", + "npm-run-all": "^4.1.2", + "nyc": "^11.0.1", + "shx": "^0.2.2", + "sinon": "^4.0.0", + "tslint": "5.11.0", + "typedoc": "0.13.0", + "typescript": "3.0.1" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/sol-trace-based-tools-common/src/artifact_adapters/abstract_artifact_adapter.ts b/packages/sol-trace-based-tools-common/src/artifact_adapters/abstract_artifact_adapter.ts new file mode 100644 index 000000000..fcc6562ad --- /dev/null +++ b/packages/sol-trace-based-tools-common/src/artifact_adapters/abstract_artifact_adapter.ts @@ -0,0 +1,5 @@ +import { ContractData } from '../types'; + +export abstract class AbstractArtifactAdapter { + public abstract async collectContractsDataAsync(): Promise; +} diff --git a/packages/sol-trace-based-tools-common/src/artifact_adapters/sol_compiler_artifact_adapter.ts b/packages/sol-trace-based-tools-common/src/artifact_adapters/sol_compiler_artifact_adapter.ts new file mode 100644 index 000000000..57391abbe --- /dev/null +++ b/packages/sol-trace-based-tools-common/src/artifact_adapters/sol_compiler_artifact_adapter.ts @@ -0,0 +1,61 @@ +import { logUtils } from '@0x/utils'; +import { CompilerOptions, ContractArtifact } from 'ethereum-types'; +import * as fs from 'fs'; +import * as glob from 'glob'; +import * as _ from 'lodash'; +import * as path from 'path'; + +import { ContractData } from '../types'; + +import { AbstractArtifactAdapter } from './abstract_artifact_adapter'; + +const CONFIG_FILE = 'compiler.json'; + +export class SolCompilerArtifactAdapter extends AbstractArtifactAdapter { + private readonly _artifactsPath: string; + private readonly _sourcesPath: string; + /** + * Instantiates a SolCompilerArtifactAdapter + * @param artifactsPath Path to your artifacts directory + * @param sourcesPath Path to your contract sources directory + */ + constructor(artifactsPath?: string, sourcesPath?: string) { + super(); + const config: CompilerOptions = fs.existsSync(CONFIG_FILE) + ? JSON.parse(fs.readFileSync(CONFIG_FILE).toString()) + : {}; + if (_.isUndefined(artifactsPath) && _.isUndefined(config.artifactsDir)) { + throw new Error(`artifactsDir not found in ${CONFIG_FILE}`); + } + this._artifactsPath = (artifactsPath || config.artifactsDir) as string; + if (_.isUndefined(sourcesPath) && _.isUndefined(config.contractsDir)) { + throw new Error(`contractsDir not found in ${CONFIG_FILE}`); + } + this._sourcesPath = (sourcesPath || config.contractsDir) as string; + } + public async collectContractsDataAsync(): Promise { + const artifactsGlob = `${this._artifactsPath}/**/*.json`; + const artifactFileNames = glob.sync(artifactsGlob, { absolute: true }); + const contractsData: ContractData[] = []; + for (const artifactFileName of artifactFileNames) { + const artifact: ContractArtifact = JSON.parse(fs.readFileSync(artifactFileName).toString()); + if (_.isUndefined(artifact.compilerOutput.evm)) { + logUtils.warn(`${artifactFileName} doesn't contain bytecode. Skipping...`); + continue; + } + let sources = _.keys(artifact.sources); + sources = _.map(sources, relativeFilePath => path.resolve(this._sourcesPath, relativeFilePath)); + const sourceCodes = _.map(sources, (source: string) => fs.readFileSync(source).toString()); + const contractData = { + sourceCodes, + sources, + bytecode: artifact.compilerOutput.evm.bytecode.object, + sourceMap: artifact.compilerOutput.evm.bytecode.sourceMap, + runtimeBytecode: artifact.compilerOutput.evm.deployedBytecode.object, + sourceMapRuntime: artifact.compilerOutput.evm.deployedBytecode.sourceMap, + }; + contractsData.push(contractData); + } + return contractsData; + } +} diff --git a/packages/sol-trace-based-tools-common/src/artifact_adapters/truffle_artifact_adapter.ts b/packages/sol-trace-based-tools-common/src/artifact_adapters/truffle_artifact_adapter.ts new file mode 100644 index 000000000..bb2b15153 --- /dev/null +++ b/packages/sol-trace-based-tools-common/src/artifact_adapters/truffle_artifact_adapter.ts @@ -0,0 +1,88 @@ +import { Compiler, CompilerOptions } from '@0x/sol-compiler'; +import * as fs from 'fs'; +import * as glob from 'glob'; +import * as path from 'path'; + +import { ContractData } from '../types'; + +import { AbstractArtifactAdapter } from './abstract_artifact_adapter'; +import { SolCompilerArtifactAdapter } from './sol_compiler_artifact_adapter'; + +const DEFAULT_TRUFFLE_ARTIFACTS_DIR = './build/contracts'; + +interface TruffleConfig { + solc?: any; + contracts_build_directory?: string; +} + +export class TruffleArtifactAdapter extends AbstractArtifactAdapter { + private readonly _solcVersion: string; + private readonly _projectRoot: string; + /** + * Instantiates a TruffleArtifactAdapter + * @param projectRoot Path to the truffle project's root directory + * @param solcVersion Solidity version with which to compile all the contracts + */ + constructor(projectRoot: string, solcVersion: string) { + super(); + this._solcVersion = solcVersion; + this._projectRoot = projectRoot; + } + public async collectContractsDataAsync(): Promise { + const artifactsDir = '.0x-artifacts'; + const contractsDir = path.join(this._projectRoot, 'contracts'); + const truffleConfig = this._getTruffleConfig(); + const solcConfig = truffleConfig.solc || {}; + const truffleArtifactsDirectory = truffleConfig.contracts_build_directory || DEFAULT_TRUFFLE_ARTIFACTS_DIR; + this._assertSolidityVersionIsCorrect(truffleArtifactsDirectory); + const compilerOptions: CompilerOptions = { + contractsDir, + artifactsDir, + compilerSettings: { + ...solcConfig, + outputSelection: { + ['*']: { + ['*']: ['abi', 'evm.bytecode.object', 'evm.deployedBytecode.object'], + }, + }, + }, + contracts: '*', + solcVersion: this._solcVersion, + }; + const compiler = new Compiler(compilerOptions); + await compiler.compileAsync(); + const solCompilerArtifactAdapter = new SolCompilerArtifactAdapter(artifactsDir, contractsDir); + const contractsDataFrom0xArtifacts = await solCompilerArtifactAdapter.collectContractsDataAsync(); + return contractsDataFrom0xArtifacts; + } + private _getTruffleConfig(): TruffleConfig { + const truffleConfigFileShort = path.resolve(path.join(this._projectRoot, 'truffle.js')); + const truffleConfigFileLong = path.resolve(path.join(this._projectRoot, 'truffle-config.js')); + if (fs.existsSync(truffleConfigFileShort)) { + const truffleConfig = require(truffleConfigFileShort); + return truffleConfig; + } else if (fs.existsSync(truffleConfigFileLong)) { + const truffleConfig = require(truffleConfigFileLong); + return truffleConfig; + } else { + throw new Error( + `Neither ${truffleConfigFileShort} nor ${truffleConfigFileLong} exists. Make sure the project root is correct`, + ); + } + } + private _assertSolidityVersionIsCorrect(truffleArtifactsDirectory: string): void { + const artifactsGlob = `${truffleArtifactsDirectory}/**/*.json`; + const artifactFileNames = glob.sync(artifactsGlob, { absolute: true }); + for (const artifactFileName of artifactFileNames) { + const artifact = JSON.parse(fs.readFileSync(artifactFileName).toString()); + const compilerVersion = artifact.compiler.version; + if (!compilerVersion.startsWith(this._solcVersion)) { + throw new Error( + `${artifact.contractName} was compiled with solidity ${compilerVersion} but specified version is ${ + this._solcVersion + } making it impossible to process traces`, + ); + } + } + } +} diff --git a/packages/sol-trace-based-tools-common/src/ast_visitor.ts b/packages/sol-trace-based-tools-common/src/ast_visitor.ts new file mode 100644 index 000000000..e55cdf6ec --- /dev/null +++ b/packages/sol-trace-based-tools-common/src/ast_visitor.ts @@ -0,0 +1,168 @@ +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 readonly _fnMap: FnMap = {}; + private readonly _branchMap: BranchMap = {}; + private readonly _modifiersStatementIds: number[] = []; + private readonly _statementMap: StatementMap = {}; + private readonly _locationByOffset: LocationByOffset; + private readonly _ignoreRangesBeginningAt: number[]; + // keep track of contract/function ranges that are to be ignored + // so we can also ignore any children nodes within the contract/function + private readonly _ignoreRangesWithin: Array<[number, number]> = []; + constructor(locationByOffset: LocationByOffset, ignoreRangesBeginningAt: number[] = []) { + this._locationByOffset = locationByOffset; + this._ignoreRangesBeginningAt = ignoreRangesBeginningAt; + } + 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 ContractDefinition(ast: Parser.ContractDefinition): void { + if (this._shouldIgnoreExpression(ast)) { + this._ignoreRangesWithin.push(ast.range as [number, number]); + } + } + 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 EmitStatement(ast: any /* TODO: Parser.EmitStatement */): 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)) { + if (this._shouldIgnoreExpression(ast)) { + return; + } + this._modifiersStatementIds.push(this._entryId); + this._visitStatement(ast); + } + } + private _visitBinaryBranch( + ast: Parser.ASTNode, + left: Parser.ASTNode, + right: Parser.ASTNode, + type: BranchType, + ): void { + if (this._shouldIgnoreExpression(ast)) { + return; + } + this._branchMap[this._entryId++] = { + line: this._getExpressionRange(ast).start.line, + type, + locations: [this._getExpressionRange(left), this._getExpressionRange(right)], + }; + } + private _visitStatement(ast: Parser.ASTNode): void { + if (this._shouldIgnoreExpression(ast)) { + return; + } + this._statementMap[this._entryId++] = this._getExpressionRange(ast); + } + private _getExpressionRange(ast: Parser.ASTNode): SingleFileSourceRange { + const astRange = ast.range as [number, number]; + const start = this._locationByOffset[astRange[0]]; + const end = this._locationByOffset[astRange[1] + 1]; + const range = { + start, + end, + }; + return range; + } + private _shouldIgnoreExpression(ast: Parser.ASTNode): boolean { + const [astStart, astEnd] = ast.range as [number, number]; + const isRangeIgnored = _.some( + this._ignoreRangesWithin, + ([rangeStart, rangeEnd]: [number, number]) => astStart >= rangeStart && astEnd <= rangeEnd, + ); + return this._ignoreRangesBeginningAt.includes(astStart) || isRangeIgnored; + } + private _visitFunctionLikeDefinition(ast: Parser.ModifierDefinition | Parser.FunctionDefinition): void { + if (this._shouldIgnoreExpression(ast)) { + this._ignoreRangesWithin.push(ast.range as [number, number]); + return; + } + const loc = this._getExpressionRange(ast); + this._fnMap[this._entryId++] = { + name: ast.name, + line: loc.start.line, + loc, + }; + this._visitStatement(ast); + } +} diff --git a/packages/sol-trace-based-tools-common/src/collect_coverage_entries.ts b/packages/sol-trace-based-tools-common/src/collect_coverage_entries.ts new file mode 100644 index 000000000..bdbcd613e --- /dev/null +++ b/packages/sol-trace-based-tools-common/src/collect_coverage_entries.ts @@ -0,0 +1,41 @@ +import * as ethUtil from 'ethereumjs-util'; +import * as _ from 'lodash'; +import * as parser from 'solidity-parser-antlr'; + +import { ASTVisitor, CoverageEntriesDescription } from './ast_visitor'; +import { getLocationByOffset } from './source_maps'; + +const IGNORE_RE = /\/\*\s*solcov\s+ignore\s+next\s*\*\/\s*/gm; + +// Parsing source code for each transaction/code is slow and therefore we cache it +const coverageEntriesBySourceHash: { [sourceHash: string]: CoverageEntriesDescription } = {}; + +export const collectCoverageEntries = (contractSource: string) => { + const sourceHash = ethUtil.sha3(contractSource).toString('hex'); + if (_.isUndefined(coverageEntriesBySourceHash[sourceHash]) && !_.isUndefined(contractSource)) { + const ast = parser.parse(contractSource, { range: true }); + const locationByOffset = getLocationByOffset(contractSource); + const ignoreRangesBegingingAt = gatherRangesToIgnore(contractSource); + const visitor = new ASTVisitor(locationByOffset, ignoreRangesBegingingAt); + parser.visit(ast, visitor); + coverageEntriesBySourceHash[sourceHash] = visitor.getCollectedCoverageEntries(); + } + const coverageEntriesDescription = coverageEntriesBySourceHash[sourceHash]; + return coverageEntriesDescription; +}; + +// Gather the start index of all code blocks preceeded by "/* solcov ignore next */" +function gatherRangesToIgnore(contractSource: string): number[] { + const ignoreRangesStart = []; + + let match; + do { + match = IGNORE_RE.exec(contractSource); + if (match) { + const matchLen = match[0].length; + ignoreRangesStart.push(match.index + matchLen); + } + } while (match); + + return ignoreRangesStart; +} diff --git a/packages/sol-trace-based-tools-common/src/constants.ts b/packages/sol-trace-based-tools-common/src/constants.ts new file mode 100644 index 000000000..34d62b537 --- /dev/null +++ b/packages/sol-trace-based-tools-common/src/constants.ts @@ -0,0 +1,8 @@ +// tslint:disable:number-literal-format +export const constants = { + NEW_CONTRACT: 'NEW_CONTRACT' as 'NEW_CONTRACT', + PUSH1: 0x60, + PUSH2: 0x61, + PUSH32: 0x7f, + TIMESTAMP: 0x42, +}; diff --git a/packages/sol-trace-based-tools-common/src/get_source_range_snippet.ts b/packages/sol-trace-based-tools-common/src/get_source_range_snippet.ts new file mode 100644 index 000000000..f578675d3 --- /dev/null +++ b/packages/sol-trace-based-tools-common/src/get_source_range_snippet.ts @@ -0,0 +1,185 @@ +import * as ethUtil from 'ethereumjs-util'; +import * as _ from 'lodash'; +import * as Parser from 'solidity-parser-antlr'; + +import { SingleFileSourceRange, SourceRange, SourceSnippet } from './types'; +import { utils } from './utils'; + +interface ASTInfo { + type: string; + node: Parser.ASTNode; + name: string | null; + range?: SingleFileSourceRange; +} + +// Parsing source code for each transaction/code is slow and therefore we cache it +const parsedSourceByHash: { [sourceHash: string]: Parser.ASTNode } = {}; + +/** + * Gets the source range snippet by source range to be used by revert trace. + * @param sourceRange source range + * @param sourceCode source code + */ +export function getSourceRangeSnippet(sourceRange: SourceRange, sourceCode: string): SourceSnippet | null { + const sourceHash = ethUtil.sha3(sourceCode).toString('hex'); + if (_.isUndefined(parsedSourceByHash[sourceHash])) { + parsedSourceByHash[sourceHash] = Parser.parse(sourceCode, { loc: true }); + } + const astNode = parsedSourceByHash[sourceHash]; + const visitor = new ASTInfoVisitor(); + Parser.visit(astNode, visitor); + const astInfo = visitor.getASTInfoForRange(sourceRange); + if (astInfo === null) { + return null; + } + const sourceCodeInRange = utils.getRange(sourceCode, sourceRange.location); + return { + ...astInfo, + range: astInfo.range as SingleFileSourceRange, + source: sourceCodeInRange, + fileName: sourceRange.fileName, + }; +} + +// A visitor which collects ASTInfo for most nodes in the AST. +class ASTInfoVisitor { + private readonly _astInfos: ASTInfo[] = []; + public getASTInfoForRange(sourceRange: SourceRange): ASTInfo | null { + // HACK(albrow): Sometimes the source range doesn't exactly match that + // of astInfo. To work around that we try with a +/-1 offset on + // end.column. If nothing matches even with the offset, we return null. + const offset = { + start: { + line: 0, + column: 0, + }, + end: { + line: 0, + column: 0, + }, + }; + let astInfo = this._getASTInfoForRange(sourceRange, offset); + if (astInfo !== null) { + return astInfo; + } + offset.end.column += 1; + astInfo = this._getASTInfoForRange(sourceRange, offset); + if (astInfo !== null) { + return astInfo; + } + offset.end.column -= 2; + astInfo = this._getASTInfoForRange(sourceRange, offset); + if (astInfo !== null) { + return astInfo; + } + return null; + } + public ContractDefinition(ast: Parser.ContractDefinition): void { + this._visitContractDefinition(ast); + } + public IfStatement(ast: Parser.IfStatement): void { + this._visitStatement(ast); + } + 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 EmitStatement(ast: any /* TODO: Parser.EmitStatement */): 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 ModifierInvocation(ast: Parser.ModifierInvocation): void { + const BUILTIN_MODIFIERS = ['public', 'view', 'payable', 'external', 'internal', 'pure', 'constant']; + if (!_.includes(BUILTIN_MODIFIERS, ast.name)) { + this._visitStatement(ast); + } + } + private _visitStatement(ast: Parser.ASTNode): void { + this._astInfos.push({ + type: ast.type, + node: ast, + name: null, + range: ast.loc, + }); + } + private _visitFunctionLikeDefinition(ast: Parser.ModifierDefinition | Parser.FunctionDefinition): void { + this._astInfos.push({ + type: ast.type, + node: ast, + name: ast.name, + range: ast.loc, + }); + } + private _visitContractDefinition(ast: Parser.ContractDefinition): void { + this._astInfos.push({ + type: ast.type, + node: ast, + name: ast.name, + range: ast.loc, + }); + } + private _getASTInfoForRange(sourceRange: SourceRange, offset: SingleFileSourceRange): ASTInfo | null { + const offsetSourceRange = { + ...sourceRange, + location: { + start: { + line: sourceRange.location.start.line + offset.start.line, + column: sourceRange.location.start.column + offset.start.column, + }, + end: { + line: sourceRange.location.end.line + offset.end.line, + column: sourceRange.location.end.column + offset.end.column, + }, + }, + }; + for (const astInfo of this._astInfos) { + const astInfoRange = astInfo.range as SingleFileSourceRange; + if ( + astInfoRange.start.column === offsetSourceRange.location.start.column && + astInfoRange.start.line === offsetSourceRange.location.start.line && + astInfoRange.end.column === offsetSourceRange.location.end.column && + astInfoRange.end.line === offsetSourceRange.location.end.line + ) { + return astInfo; + } + } + return null; + } +} diff --git a/packages/sol-trace-based-tools-common/src/globals.d.ts b/packages/sol-trace-based-tools-common/src/globals.d.ts new file mode 100644 index 000000000..e799b3529 --- /dev/null +++ b/packages/sol-trace-based-tools-common/src/globals.d.ts @@ -0,0 +1,7 @@ +// tslint:disable:completed-docs +declare module '*.json' { + const json: any; + /* tslint:disable */ + export default json; + /* tslint:enable */ +} diff --git a/packages/sol-trace-based-tools-common/src/index.ts b/packages/sol-trace-based-tools-common/src/index.ts new file mode 100644 index 000000000..413e5305e --- /dev/null +++ b/packages/sol-trace-based-tools-common/src/index.ts @@ -0,0 +1,39 @@ +export { SolCompilerArtifactAdapter } from './artifact_adapters/sol_compiler_artifact_adapter'; +export { TruffleArtifactAdapter } from './artifact_adapters/truffle_artifact_adapter'; +export { AbstractArtifactAdapter } from './artifact_adapters/abstract_artifact_adapter'; + +export { + ContractData, + EvmCallStack, + SourceRange, + SourceSnippet, + StatementCoverage, + StatementDescription, + BranchCoverage, + BranchDescription, + Subtrace, + TraceInfo, + Coverage, + LineColumn, + LineCoverage, + FunctionCoverage, + FunctionDescription, + SingleFileSourceRange, + BranchMap, + EvmCallStackEntry, + FnMap, + LocationByOffset, + StatementMap, + TraceInfoBase, + TraceInfoExistingContract, + TraceInfoNewContract, +} from './types'; +export { collectCoverageEntries } from './collect_coverage_entries'; +export { TraceCollector, SingleFileSubtraceHandler } from './trace_collector'; +export { TraceInfoSubprovider } from './trace_info_subprovider'; +export { utils } from './utils'; +export { constants } from './constants'; +export { parseSourceMap } from './source_maps'; +export { getSourceRangeSnippet } from './get_source_range_snippet'; +export { getRevertTrace } from './revert_trace'; +export { TraceCollectionSubprovider } from './trace_collection_subprovider'; diff --git a/packages/sol-trace-based-tools-common/src/instructions.ts b/packages/sol-trace-based-tools-common/src/instructions.ts new file mode 100644 index 000000000..40987dbe5 --- /dev/null +++ b/packages/sol-trace-based-tools-common/src/instructions.ts @@ -0,0 +1,23 @@ +import { constants } from './constants'; + +const isPush = (inst: number) => inst >= constants.PUSH1 && inst <= constants.PUSH32; + +const pushDataLength = (inst: number) => inst - constants.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; +}; diff --git a/packages/sol-trace-based-tools-common/src/revert_trace.ts b/packages/sol-trace-based-tools-common/src/revert_trace.ts new file mode 100644 index 000000000..4d474120c --- /dev/null +++ b/packages/sol-trace-based-tools-common/src/revert_trace.ts @@ -0,0 +1,95 @@ +import { logUtils } from '@0x/utils'; +import { OpCode, StructLog } from 'ethereum-types'; + +import * as _ from 'lodash'; + +import { EvmCallStack } from './types'; +import { utils } from './utils'; + +/** + * Converts linear trace to a call stack by following calls and returns + * @param structLogs Linear trace + * @param startAddress The address of initial context + */ +export function getRevertTrace(structLogs: StructLog[], startAddress: string): EvmCallStack { + const evmCallStack: EvmCallStack = []; + const addressStack = [startAddress]; + if (_.isEmpty(structLogs)) { + return []; + } + const normalizedStructLogs = utils.normalizeStructLogs(structLogs); + // tslint:disable-next-line:prefer-for-of + for (let i = 0; i < normalizedStructLogs.length; i++) { + const structLog = normalizedStructLogs[i]; + if (structLog.depth !== addressStack.length - 1) { + throw new Error("Malformed trace. Trace depth doesn't match call stack depth"); + } + // After that check we have a guarantee that call stack is never empty + // If it would: callStack.length - 1 === structLog.depth === -1 + // That means that we can always safely pop from it + + if (utils.isCallLike(structLog.op)) { + const currentAddress = _.last(addressStack) as string; + const jumpAddressOffset = 1; + const newAddress = utils.getAddressFromStackEntry( + structLog.stack[structLog.stack.length - jumpAddressOffset - 1], + ); + + // Sometimes calls don't change the execution context (current address). When we do a transfer to an + // externally owned account - it does the call and immediately returns because there is no fallback + // function. We manually check if the call depth had changed to handle that case. + const nextStructLog = normalizedStructLogs[i + 1]; + if (nextStructLog.depth !== structLog.depth) { + addressStack.push(newAddress); + evmCallStack.push({ + address: currentAddress, + structLog, + }); + } + } else if (utils.isEndOpcode(structLog.op) && structLog.op !== OpCode.Revert) { + // Just like with calls, sometimes returns/stops don't change the execution context (current address). + const nextStructLog = normalizedStructLogs[i + 1]; + if (_.isUndefined(nextStructLog) || nextStructLog.depth !== structLog.depth) { + evmCallStack.pop(); + addressStack.pop(); + } + if (structLog.op === OpCode.SelfDestruct) { + // After contract execution, we look at all sub-calls to external contracts, and for each one, fetch + // the bytecode and compute the coverage for the call. If the contract is destroyed with a call + // to `selfdestruct`, we are unable to fetch it's bytecode and compute coverage. + // TODO: Refactor this logic to fetch the sub-called contract bytecode before the selfdestruct is called + // in order to handle this edge-case. + logUtils.warn( + "Detected a selfdestruct. We currently do not support that scenario. We'll just skip the trace part for a destructed contract", + ); + } + } else if (structLog.op === OpCode.Revert) { + evmCallStack.push({ + address: _.last(addressStack) as string, + structLog, + }); + return evmCallStack; + } else if (structLog.op === OpCode.Create) { + // TODO: Extract the new contract address from the stack and handle that scenario + logUtils.warn( + "Detected a contract created from within another contract. We currently do not support that scenario. We'll just skip that trace", + ); + return []; + } else { + if (structLog !== _.last(normalizedStructLogs)) { + const nextStructLog = normalizedStructLogs[i + 1]; + if (nextStructLog.depth === structLog.depth) { + continue; + } else if (nextStructLog.depth === structLog.depth - 1) { + addressStack.pop(); + } else { + throw new Error('Malformed trace. Unexpected call depth change'); + } + } + } + } + if (evmCallStack.length !== 0) { + logUtils.warn('Malformed trace. Call stack non empty at the end. (probably out of gas)'); + } + return []; +} diff --git a/packages/sol-trace-based-tools-common/src/source_maps.ts b/packages/sol-trace-based-tools-common/src/source_maps.ts new file mode 100644 index 000000000..7191fb712 --- /dev/null +++ b/packages/sol-trace-based-tools-common/src/source_maps.ts @@ -0,0 +1,91 @@ +import * as _ from 'lodash'; + +import { getPcToInstructionIndexMapping } from './instructions'; +import { LocationByOffset, SourceRange } from './types'; + +const RADIX = 10; + +export interface SourceLocation { + offset: number; + length: number; + fileIndex: number; +} + +/** + * Receives a string with newlines and returns a hash of byte offset to LineColumn + * @param str A string to process + */ +export function getLocationByOffset(str: string): LocationByOffset { + const locationByOffset: LocationByOffset = { 0: { line: 1, column: 0 } }; + let currentOffset = 0; + for (const char of str.split('')) { + const location = locationByOffset[currentOffset]; + const isNewline = char === '\n'; + locationByOffset[currentOffset + 1] = { + 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 + * @param sourceCodes sources contents + * @param srcMap source map string + * @param bytecodeHex contract bytecode + * @param sources sources file names + */ +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, s => (_.isUndefined(s) ? {} : getLocationByOffset(s))); + const entries = srcMap.split(';'); + let lastParsedEntry: SourceLocation = {} as any; + const instructionIndexToSourceRange: { [instructionIndex: number]: SourceRange } = {}; + _.each(entries, (entry: string, i: number) => { + // tslint:disable-next-line:no-unused-variable + 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 && !_.isUndefined(locationByOffsetByFileIndex[parsedEntry.fileIndex])) { + const sourceRange = { + location: { + start: locationByOffsetByFileIndex[parsedEntry.fileIndex][parsedEntry.offset], + end: locationByOffsetByFileIndex[parsedEntry.fileIndex][parsedEntry.offset + parsedEntry.length], + }, + 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; +} diff --git a/packages/sol-trace-based-tools-common/src/trace.ts b/packages/sol-trace-based-tools-common/src/trace.ts new file mode 100644 index 000000000..770080af3 --- /dev/null +++ b/packages/sol-trace-based-tools-common/src/trace.ts @@ -0,0 +1,104 @@ +import { logUtils } from '@0x/utils'; +import { OpCode, StructLog } from 'ethereum-types'; +import * as _ from 'lodash'; + +import { utils } from './utils'; + +export interface TraceByContractAddress { + [contractAddress: string]: StructLog[]; +} + +/** + * Converts linear stack trace to `TraceByContractAddress`. + * @param structLogs stack trace + * @param startAddress initial context address + */ +export function getTracesByContractAddress(structLogs: StructLog[], startAddress: string): TraceByContractAddress { + const traceByContractAddress: TraceByContractAddress = {}; + let currentTraceSegment = []; + const addressStack = [startAddress]; + if (_.isEmpty(structLogs)) { + return traceByContractAddress; + } + const normalizedStructLogs = utils.normalizeStructLogs(structLogs); + // tslint:disable-next-line:prefer-for-of + for (let i = 0; i < normalizedStructLogs.length; i++) { + const structLog = normalizedStructLogs[i]; + if (structLog.depth !== addressStack.length - 1) { + throw new Error("Malformed trace. Trace depth doesn't match call stack depth"); + } + // After that check we have a guarantee that call stack is never empty + // If it would: callStack.length - 1 === structLog.depth === -1 + // That means that we can always safely pop from it + currentTraceSegment.push(structLog); + + if (utils.isCallLike(structLog.op)) { + const currentAddress = _.last(addressStack) as string; + const jumpAddressOffset = 1; + const newAddress = utils.getAddressFromStackEntry( + structLog.stack[structLog.stack.length - jumpAddressOffset - 1], + ); + + // Sometimes calls don't change the execution context (current address). When we do a transfer to an + // externally owned account - it does the call and immediately returns because there is no fallback + // function. We manually check if the call depth had changed to handle that case. + const nextStructLog = normalizedStructLogs[i + 1]; + if (nextStructLog.depth !== structLog.depth) { + addressStack.push(newAddress); + traceByContractAddress[currentAddress] = (traceByContractAddress[currentAddress] || []).concat( + currentTraceSegment, + ); + currentTraceSegment = []; + } + } else if (utils.isEndOpcode(structLog.op)) { + const currentAddress = addressStack.pop() as string; + traceByContractAddress[currentAddress] = (traceByContractAddress[currentAddress] || []).concat( + currentTraceSegment, + ); + currentTraceSegment = []; + if (structLog.op === OpCode.SelfDestruct) { + // After contract execution, we look at all sub-calls to external contracts, and for each one, fetch + // the bytecode and compute the coverage for the call. If the contract is destroyed with a call + // to `selfdestruct`, we are unable to fetch it's bytecode and compute coverage. + // TODO: Refactor this logic to fetch the sub-called contract bytecode before the selfdestruct is called + // in order to handle this edge-case. + logUtils.warn( + "Detected a selfdestruct. We currently do not support that scenario. We'll just skip the trace part for a destructed contract", + ); + } + } else if (structLog.op === OpCode.Create) { + // TODO: Extract the new contract address from the stack and handle that scenario + logUtils.warn( + "Detected a contract created from within another contract. We currently do not support that scenario. We'll just skip that trace", + ); + return traceByContractAddress; + } else { + if (structLog !== _.last(normalizedStructLogs)) { + const nextStructLog = normalizedStructLogs[i + 1]; + if (nextStructLog.depth === structLog.depth) { + continue; + } else if (nextStructLog.depth === structLog.depth - 1) { + const currentAddress = addressStack.pop() as string; + traceByContractAddress[currentAddress] = (traceByContractAddress[currentAddress] || []).concat( + currentTraceSegment, + ); + currentTraceSegment = []; + } else { + throw new Error('Malformed trace. Unexpected call depth change'); + } + } + } + } + if (addressStack.length !== 0) { + logUtils.warn('Malformed trace. Call stack non empty at the end'); + } + if (currentTraceSegment.length !== 0) { + const currentAddress = addressStack.pop() as string; + traceByContractAddress[currentAddress] = (traceByContractAddress[currentAddress] || []).concat( + currentTraceSegment, + ); + currentTraceSegment = []; + logUtils.warn('Malformed trace. Current trace segment non empty at the end'); + } + return traceByContractAddress; +} diff --git a/packages/sol-trace-based-tools-common/src/trace_collection_subprovider.ts b/packages/sol-trace-based-tools-common/src/trace_collection_subprovider.ts new file mode 100644 index 000000000..25e38768d --- /dev/null +++ b/packages/sol-trace-based-tools-common/src/trace_collection_subprovider.ts @@ -0,0 +1,188 @@ +import { BlockchainLifecycle } from '@0x/dev-utils'; +import { Callback, ErrorCallback, NextCallback, Subprovider } from '@0x/subproviders'; +import { CallDataRPC, marshaller, Web3Wrapper } from '@0x/web3-wrapper'; +import { JSONRPCRequestPayload, Provider, TxData } from 'ethereum-types'; +import * as _ from 'lodash'; +import { Lock } from 'semaphore-async-await'; + +import { constants } from './constants'; +import { BlockParamLiteral } from './types'; + +interface MaybeFakeTxData extends TxData { + isFakeTransaction?: boolean; +} + +const BLOCK_GAS_LIMIT = 6000000; + +export interface TraceCollectionSubproviderConfig { + shouldCollectTransactionTraces: boolean; + shouldCollectCallTraces: boolean; + shouldCollectGasEstimateTraces: boolean; +} + +// Because there is no notion of a call trace in the Ethereum rpc - we collect them in a 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 avoid influencing test behaviour. + +/** + * This class implements the [web3-provider-engine](https://github.com/MetaMask/provider-engine) subprovider interface. + * It collects traces of all transactions that were sent and all calls that were executed through JSON RPC. It must + * be extended by implementing the _recordTxTraceAsync method which is called for every transaction. + */ +export abstract class TraceCollectionSubprovider extends Subprovider { + protected _web3Wrapper!: Web3Wrapper; + // Lock is used to not accept normal transactions while doing call/snapshot magic because they'll be reverted later otherwise + private readonly _lock = new Lock(); + private readonly _defaultFromAddress: string; + private _isEnabled = true; + private readonly _config: TraceCollectionSubproviderConfig; + /** + * Instantiates a TraceCollectionSubprovider instance + * @param defaultFromAddress default from address to use when sending transactions + */ + constructor(defaultFromAddress: string, config: TraceCollectionSubproviderConfig) { + super(); + this._defaultFromAddress = defaultFromAddress; + this._config = config; + } + /** + * Starts trace collection + */ + public start(): void { + this._isEnabled = true; + } + /** + * Stops trace collection + */ + public stop(): void { + this._isEnabled = false; + } + /** + * This method conforms to the web3-provider-engine interface. + * It is called internally by the ProviderEngine when it is this subproviders + * turn to handle a JSON RPC request. + * @param payload JSON RPC payload + * @param next Callback to call if this subprovider decides not to handle the request + * @param _end Callback to call if subprovider handled the request and wants to pass back the request. + */ + // tslint:disable-next-line:prefer-function-over-method async-suffix + public async handleRequest(payload: JSONRPCRequestPayload, next: NextCallback, _end: ErrorCallback): Promise { + if (this._isEnabled) { + switch (payload.method) { + case 'eth_sendTransaction': + if (!this._config.shouldCollectTransactionTraces) { + next(); + } else { + const txData = payload.params[0]; + next(this._onTransactionSentAsync.bind(this, txData)); + } + return; + + case 'eth_call': + if (!this._config.shouldCollectCallTraces) { + next(); + } else { + const callData = payload.params[0]; + next(this._onCallOrGasEstimateExecutedAsync.bind(this, callData)); + } + return; + + case 'eth_estimateGas': + if (!this._config.shouldCollectGasEstimateTraces) { + next(); + } else { + const estimateGasData = payload.params[0]; + next(this._onCallOrGasEstimateExecutedAsync.bind(this, estimateGasData)); + } + return; + + default: + next(); + return; + } + } else { + next(); + return; + } + } + /** + * Set's the subprovider's engine to the ProviderEngine it is added to. + * This is only called within the ProviderEngine source code, do not call + * directly. + * @param engine The ProviderEngine this subprovider is added to + */ + public setEngine(engine: Provider): void { + super.setEngine(engine); + this._web3Wrapper = new Web3Wrapper(engine); + } + protected abstract async _recordTxTraceAsync( + address: string, + data: string | undefined, + txHash: string, + ): Promise; + private async _onTransactionSentAsync( + txData: MaybeFakeTxData, + err: Error | null, + txHash: string | undefined, + cb: Callback, + ): Promise { + if (!txData.isFakeTransaction) { + // This transaction is a usual transaction. Not a call executed as one. + // And we don't want it to be executed within a snapshotting period + await this._lock.acquire(); + } + const NULL_ADDRESS = '0x0'; + if (_.isNull(err)) { + const toAddress = + _.isUndefined(txData.to) || txData.to === NULL_ADDRESS ? constants.NEW_CONTRACT : txData.to; + await this._recordTxTraceAsync(toAddress, txData.data, txHash as string); + } else { + const latestBlock = await this._web3Wrapper.getBlockWithTransactionDataAsync(BlockParamLiteral.Latest); + const transactions = latestBlock.transactions; + for (const transaction of transactions) { + const toAddress = + _.isUndefined(txData.to) || txData.to === NULL_ADDRESS ? constants.NEW_CONTRACT : txData.to; + await this._recordTxTraceAsync(toAddress, transaction.input, transaction.hash); + } + } + if (!txData.isFakeTransaction) { + // This transaction is a usual transaction. 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 _onCallOrGasEstimateExecutedAsync( + callData: Partial, + _err: Error | null, + _callResult: string, + cb: Callback, + ): Promise { + await this._recordCallOrGasEstimateTraceAsync(callData); + cb(); + } + private async _recordCallOrGasEstimateTraceAsync(callData: Partial): Promise { + // 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 blockchainLifecycle = new BlockchainLifecycle(this._web3Wrapper); + await blockchainLifecycle.startAsync(); + const fakeTxData = { + gas: BLOCK_GAS_LIMIT.toString(16), // tslint:disable-line:custom-no-magic-numbers + isFakeTransaction: true, // This transaction (and only it) is allowed to come through when the lock is locked + ...callData, + from: callData.from || this._defaultFromAddress, + }; + try { + const txData = marshaller.unmarshalTxData(fakeTxData); + const txHash = await this._web3Wrapper.sendTransactionAsync(txData); + await this._web3Wrapper.awaitTransactionMinedAsync(txHash, 0); + } catch (err) { + // TODO(logvinov) Check that transaction failed and not some other exception + // Even if this transaction failed - we've already recorded it's trace. + _.noop(); + } + await blockchainLifecycle.revertAsync(); + this._lock.release(); + } +} diff --git a/packages/sol-trace-based-tools-common/src/trace_collector.ts b/packages/sol-trace-based-tools-common/src/trace_collector.ts new file mode 100644 index 000000000..8ba71f4df --- /dev/null +++ b/packages/sol-trace-based-tools-common/src/trace_collector.ts @@ -0,0 +1,93 @@ +import { promisify } from '@0x/utils'; +import { stripHexPrefix } from 'ethereumjs-util'; +import * as fs from 'fs'; +import { Collector } from 'istanbul'; +import * as _ from 'lodash'; +import { getLogger, levels, Logger } from 'loglevel'; +import * as mkdirp from 'mkdirp'; + +import { AbstractArtifactAdapter } from './artifact_adapters/abstract_artifact_adapter'; +import { constants } from './constants'; +import { parseSourceMap } from './source_maps'; +import { + ContractData, + Coverage, + SourceRange, + Subtrace, + TraceInfo, + TraceInfoExistingContract, + TraceInfoNewContract, +} from './types'; +import { utils } from './utils'; + +const mkdirpAsync = promisify(mkdirp); + +export type SingleFileSubtraceHandler = ( + contractData: ContractData, + subtrace: Subtrace, + pcToSourceRange: { [programCounter: number]: SourceRange }, + fileIndex: number, +) => Coverage; + +/** + * TraceCollector is used by CoverageSubprovider to compute code coverage based on collected trace data. + */ +export class TraceCollector { + private readonly _artifactAdapter: AbstractArtifactAdapter; + private readonly _logger: Logger; + private _contractsData!: ContractData[]; + private readonly _collector = new Collector(); + private readonly _singleFileSubtraceHandler: SingleFileSubtraceHandler; + + /** + * Instantiates a TraceCollector instance + * @param artifactAdapter Adapter for used artifacts format (0x, truffle, giveth, etc.) + * @param isVerbose If true, we will log any unknown transactions. Otherwise we will ignore them + * @param singleFileSubtraceHandler A handler function for computing partial coverage for a single file & subtrace + */ + constructor( + artifactAdapter: AbstractArtifactAdapter, + isVerbose: boolean, + singleFileSubtraceHandler: SingleFileSubtraceHandler, + ) { + this._artifactAdapter = artifactAdapter; + this._logger = getLogger('sol-trace-based-tools-common'); + this._logger.setLevel(isVerbose ? levels.TRACE : levels.ERROR); + this._singleFileSubtraceHandler = singleFileSubtraceHandler; + } + public async writeOutputAsync(): Promise { + const finalCoverage = this._collector.getFinalCoverage(); + const stringifiedCoverage = JSON.stringify(finalCoverage, null, '\t'); + await mkdirpAsync('coverage'); + fs.writeFileSync('coverage/coverage.json', stringifiedCoverage); + } + public async computeSingleTraceCoverageAsync(traceInfo: TraceInfo): Promise { + if (_.isUndefined(this._contractsData)) { + this._contractsData = await this._artifactAdapter.collectContractsDataAsync(); + } + const isContractCreation = traceInfo.address === constants.NEW_CONTRACT; + const bytecode = isContractCreation + ? (traceInfo as TraceInfoNewContract).bytecode + : (traceInfo as TraceInfoExistingContract).runtimeBytecode; + const contractData = utils.getContractDataIfExists(this._contractsData, bytecode); + if (_.isUndefined(contractData)) { + const errMsg = isContractCreation + ? `Unknown contract creation transaction` + : `Transaction to an unknown address: ${traceInfo.address}`; + this._logger.warn(errMsg); + return; + } + const bytecodeHex = stripHexPrefix(bytecode); + const sourceMap = isContractCreation ? contractData.sourceMap : contractData.sourceMapRuntime; + const pcToSourceRange = parseSourceMap(contractData.sourceCodes, sourceMap, bytecodeHex, contractData.sources); + for (let fileIndex = 0; fileIndex < contractData.sources.length; fileIndex++) { + const singleFileCoverageForTrace = this._singleFileSubtraceHandler( + contractData, + traceInfo.subtrace, + pcToSourceRange, + fileIndex, + ); + this._collector.add(singleFileCoverageForTrace); + } + } +} diff --git a/packages/sol-trace-based-tools-common/src/trace_info_subprovider.ts b/packages/sol-trace-based-tools-common/src/trace_info_subprovider.ts new file mode 100644 index 000000000..635a68f58 --- /dev/null +++ b/packages/sol-trace-based-tools-common/src/trace_info_subprovider.ts @@ -0,0 +1,59 @@ +import * as _ from 'lodash'; + +import { constants } from './constants'; +import { getTracesByContractAddress } from './trace'; +import { TraceCollectionSubprovider } from './trace_collection_subprovider'; +import { TraceInfo, TraceInfoExistingContract, TraceInfoNewContract } from './types'; + +// TraceInfoSubprovider is extended by subproviders which need to work with one +// TraceInfo at a time. It has one abstract method: _handleTraceInfoAsync, which +// is called for each TraceInfo. +export abstract class TraceInfoSubprovider extends TraceCollectionSubprovider { + protected abstract _handleTraceInfoAsync(traceInfo: TraceInfo): Promise; + protected async _recordTxTraceAsync(address: string, data: string | undefined, txHash: string): Promise { + await this._web3Wrapper.awaitTransactionMinedAsync(txHash, 0); + const trace = await this._web3Wrapper.getTransactionTraceAsync(txHash, { + disableMemory: true, + disableStack: false, + disableStorage: true, + }); + const tracesByContractAddress = getTracesByContractAddress(trace.structLogs, address); + const subcallAddresses = _.keys(tracesByContractAddress); + if (address === constants.NEW_CONTRACT) { + for (const subcallAddress of subcallAddresses) { + let traceInfo: TraceInfoNewContract | TraceInfoExistingContract; + if (subcallAddress === 'NEW_CONTRACT') { + const traceForThatSubcall = tracesByContractAddress[subcallAddress]; + traceInfo = { + subtrace: traceForThatSubcall, + txHash, + address: subcallAddress, + bytecode: data as string, + }; + } else { + const runtimeBytecode = await this._web3Wrapper.getContractCodeAsync(subcallAddress); + const traceForThatSubcall = tracesByContractAddress[subcallAddress]; + traceInfo = { + subtrace: traceForThatSubcall, + txHash, + address: subcallAddress, + runtimeBytecode, + }; + } + await this._handleTraceInfoAsync(traceInfo); + } + } else { + for (const subcallAddress of subcallAddresses) { + const runtimeBytecode = await this._web3Wrapper.getContractCodeAsync(subcallAddress); + const traceForThatSubcall = tracesByContractAddress[subcallAddress]; + const traceInfo: TraceInfoExistingContract = { + subtrace: traceForThatSubcall, + txHash, + address: subcallAddress, + runtimeBytecode, + }; + await this._handleTraceInfoAsync(traceInfo); + } + } + } +} diff --git a/packages/sol-trace-based-tools-common/src/types.ts b/packages/sol-trace-based-tools-common/src/types.ts new file mode 100644 index 000000000..54ade0400 --- /dev/null +++ b/packages/sol-trace-based-tools-common/src/types.ts @@ -0,0 +1,126 @@ +import { StructLog } from 'ethereum-types'; +import * as Parser from 'solidity-parser-antlr'; + +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]: number; +} + +export interface FunctionCoverage { + [functionId: string]: number; +} + +export interface StatementCoverage { + [statementId: string]: number; +} + +export interface BranchCoverage { + [branchId: string]: number[]; +} + +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[]; + sources: string[]; +} + +// Part of the trace executed within the same context +export type Subtrace = StructLog[]; + +export interface TraceInfoBase { + subtrace: Subtrace; + 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; + +export enum BlockParamLiteral { + Latest = 'latest', +} + +export interface EvmCallStackEntry { + structLog: StructLog; + address: string; +} + +export type EvmCallStack = EvmCallStackEntry[]; + +export interface SourceSnippet { + source: string; + fileName: string; + type: string; + node: Parser.ASTNode; + name: string | null; + range: SingleFileSourceRange; +} diff --git a/packages/sol-trace-based-tools-common/src/utils.ts b/packages/sol-trace-based-tools-common/src/utils.ts new file mode 100644 index 000000000..d8bc65e73 --- /dev/null +++ b/packages/sol-trace-based-tools-common/src/utils.ts @@ -0,0 +1,87 @@ +import { addressUtils, BigNumber } from '@0x/utils'; +import { OpCode, StructLog } from 'ethereum-types'; +import { addHexPrefix } from 'ethereumjs-util'; +import * as _ from 'lodash'; + +import { ContractData, LineColumn, SingleFileSourceRange } from './types'; + +// This is the minimum length of valid contract bytecode. The Solidity compiler +// metadata is 86 bytes. If you add the '0x' prefix, we get 88. +const MIN_CONTRACT_BYTECODE_LENGTH = 88; + +export const utils = { + compareLineColumn(lhs: LineColumn, rhs: LineColumn): number { + return lhs.line !== rhs.line ? lhs.line - rhs.line : lhs.column - rhs.column; + }, + removeHexPrefix(hex: string): string { + const hexPrefix = '0x'; + return hex.startsWith(hexPrefix) ? hex.slice(hexPrefix.length) : hex; + }, + isRangeInside(childRange: SingleFileSourceRange, parentRange: SingleFileSourceRange): boolean { + return ( + utils.compareLineColumn(parentRange.start, childRange.start) <= 0 && + utils.compareLineColumn(childRange.end, parentRange.end) <= 0 + ); + }, + bytecodeToBytecodeRegex(bytecode: string): string { + const bytecodeRegex = bytecode + // Library linking placeholder: __ConvertLib____________________________ + .replace(/_.*_/, '.*') + // Last 86 characters is solidity compiler metadata that's different between compilations + .replace(/.{86}$/, '') + // Libraries contain their own address at the beginning of the code and it's impossible to know it in advance + .replace(/^0x730000000000000000000000000000000000000000/, '0x73........................................'); + // HACK: Node regexes can't be longer that 32767 characters. Contracts bytecode can. We just truncate the regexes. It's safe in practice. + const MAX_REGEX_LENGTH = 32767; + const truncatedBytecodeRegex = bytecodeRegex.slice(0, MAX_REGEX_LENGTH); + return truncatedBytecodeRegex; + }, + getContractDataIfExists(contractsData: ContractData[], bytecode: string): ContractData | undefined { + if (!bytecode.startsWith('0x')) { + throw new Error(`0x hex prefix missing: ${bytecode}`); + } + const contractData = _.find(contractsData, contractDataCandidate => { + const bytecodeRegex = utils.bytecodeToBytecodeRegex(contractDataCandidate.bytecode); + // If the bytecode is less than the minimum length, we are probably + // dealing with an interface. This isn't what we're looking for. + if (bytecodeRegex.length < MIN_CONTRACT_BYTECODE_LENGTH) { + return false; + } + const runtimeBytecodeRegex = utils.bytecodeToBytecodeRegex(contractDataCandidate.runtimeBytecode); + if (runtimeBytecodeRegex.length < MIN_CONTRACT_BYTECODE_LENGTH) { + return false; + } + // We use that function to find by bytecode or runtimeBytecode. Those are quasi-random strings so + // collisions are practically impossible and it allows us to reuse that code + return !_.isNull(bytecode.match(bytecodeRegex)) || !_.isNull(bytecode.match(runtimeBytecodeRegex)); + }); + return contractData; + }, + isCallLike(op: OpCode): boolean { + return _.includes([OpCode.CallCode, OpCode.StaticCall, OpCode.Call, OpCode.DelegateCall], op); + }, + isEndOpcode(op: OpCode): boolean { + return _.includes([OpCode.Return, OpCode.Stop, OpCode.Revert, OpCode.Invalid, OpCode.SelfDestruct], op); + }, + getAddressFromStackEntry(stackEntry: string): string { + const hexBase = 16; + return addressUtils.padZeros(new BigNumber(addHexPrefix(stackEntry)).toString(hexBase)); + }, + normalizeStructLogs(structLogs: StructLog[]): StructLog[] { + if (structLogs[0].depth === 1) { + // Geth uses 1-indexed depth counter whilst ganache starts from 0 + const newStructLogs = _.map(structLogs, structLog => ({ + ...structLog, + depth: structLog.depth - 1, + })); + return newStructLogs; + } + return structLogs; + }, + getRange(sourceCode: string, range: SingleFileSourceRange): string { + const lines = sourceCode.split('\n').slice(range.start.line - 1, range.end.line); + lines[lines.length - 1] = lines[lines.length - 1].slice(0, range.end.column); + lines[0] = lines[0].slice(range.start.column); + return lines.join('\n'); + }, +}; diff --git a/packages/sol-trace-based-tools-common/test/collect_coverage_entries_test.ts b/packages/sol-trace-based-tools-common/test/collect_coverage_entries_test.ts new file mode 100644 index 000000000..7832ec316 --- /dev/null +++ b/packages/sol-trace-based-tools-common/test/collect_coverage_entries_test.ts @@ -0,0 +1,155 @@ +import * as chai from 'chai'; +import * as fs from 'fs'; +import * as _ from 'lodash'; +import 'mocha'; +import * as path from 'path'; + +import { collectCoverageEntries } from '../src/collect_coverage_entries'; +import { utils } from '../src/utils'; + +const expect = chai.expect; + +describe('Collect coverage entries', () => { + describe('#collectCoverageEntries', () => { + it('correctly collects coverage entries for Simplest contract', () => { + const simplestContractBaseName = 'Simplest.sol'; + const simplestContractFileName = path.resolve(__dirname, 'fixtures/contracts', simplestContractBaseName); + const simplestContract = fs.readFileSync(simplestContractFileName).toString(); + const coverageEntries = collectCoverageEntries(simplestContract); + expect(coverageEntries.fnMap).to.be.deep.equal({}); + expect(coverageEntries.branchMap).to.be.deep.equal({}); + expect(coverageEntries.statementMap).to.be.deep.equal({}); + expect(coverageEntries.modifiersStatementIds).to.be.deep.equal([]); + }); + it('correctly collects coverage entries for SimpleStorage contract', () => { + const simpleStorageContractBaseName = 'SimpleStorage.sol'; + const simpleStorageContractFileName = path.resolve( + __dirname, + 'fixtures/contracts', + simpleStorageContractBaseName, + ); + const simpleStorageContract = fs.readFileSync(simpleStorageContractFileName).toString(); + const coverageEntries = collectCoverageEntries(simpleStorageContract); + const fnIds = _.keys(coverageEntries.fnMap); + expect(coverageEntries.fnMap[fnIds[0]].name).to.be.equal('set'); + // tslint:disable-next-line:custom-no-magic-numbers + expect(coverageEntries.fnMap[fnIds[0]].line).to.be.equal(5); + const setFunction = `function set(uint x) { + storedData = x; + }`; + expect(utils.getRange(simpleStorageContract, coverageEntries.fnMap[fnIds[0]].loc)).to.be.equal(setFunction); + expect(coverageEntries.fnMap[fnIds[1]].name).to.be.equal('get'); + // tslint:disable-next-line:custom-no-magic-numbers + expect(coverageEntries.fnMap[fnIds[1]].line).to.be.equal(8); + const getFunction = `function get() constant returns (uint retVal) { + return storedData; + }`; + expect(utils.getRange(simpleStorageContract, coverageEntries.fnMap[fnIds[1]].loc)).to.be.equal(getFunction); + expect(coverageEntries.branchMap).to.be.deep.equal({}); + const statementIds = _.keys(coverageEntries.statementMap); + expect(utils.getRange(simpleStorageContract, coverageEntries.statementMap[statementIds[1]])).to.be.equal( + 'storedData = x', + ); + expect(utils.getRange(simpleStorageContract, coverageEntries.statementMap[statementIds[3]])).to.be.equal( + 'return storedData;', + ); + expect(coverageEntries.modifiersStatementIds).to.be.deep.equal([]); + }); + it('correctly collects coverage entries for AllSolidityFeatures contract', () => { + const simpleStorageContractBaseName = 'AllSolidityFeatures.sol'; + const simpleStorageContractFileName = path.resolve( + __dirname, + 'fixtures/contracts', + simpleStorageContractBaseName, + ); + const simpleStorageContract = fs.readFileSync(simpleStorageContractFileName).toString(); + const coverageEntries = collectCoverageEntries(simpleStorageContract); + const fnDescriptions = _.values(coverageEntries.fnMap); + const fnNames = _.map(fnDescriptions, fnDescription => fnDescription.name); + const expectedFnNames = [ + 'f', + 'c', + 'test', + 'getChoice', + 'Base', + 'Derived', + 'f', + 'f', + '', + 'g', + 'setData', + 'getData', + 'sendHalf', + 'insert', + 'remove', + 'contains', + 'iterate_start', + 'iterate_valid', + 'iterate_advance', + 'iterate_get', + 'insert', + 'sum', + 'restricted', + 'DualIndex', + 'set', + 'transfer_ownership', + 'lookup', + '', + '', + 'sum', + 'someFunction', + 'fun', + 'at', + 'test', + 'get', + 'returnNumber', + 'alloc', + 'ham', + 'getMyTuple', + 'ham', + 'abstain', + 'foobar', + 'foobar', + 'a', + ]; + expect(fnNames).to.be.deep.equal(expectedFnNames); + + const branchDescriptions = _.values(coverageEntries.branchMap); + const branchLines = _.map(branchDescriptions, branchDescription => branchDescription.line); + // tslint:disable-next-line:custom-no-magic-numbers + expect(branchLines).to.be.deep.equal([94, 115, 119, 130, 151, 187]); + const branchTypes = _.map(branchDescriptions, branchDescription => branchDescription.type); + expect(branchTypes).to.be.deep.equal(['if', 'if', 'if', 'if', 'binary-expr', 'if']); + }); + + it('correctly ignores all coverage entries for Ignore contract', () => { + const solcovIgnoreContractBaseName = 'SolcovIgnore.sol'; + const solcovIgnoreContractFileName = path.resolve( + __dirname, + 'fixtures/contracts', + solcovIgnoreContractBaseName, + ); + const solcovIgnoreContract = fs.readFileSync(solcovIgnoreContractFileName).toString(); + const coverageEntries = collectCoverageEntries(solcovIgnoreContract); + const fnIds = _.keys(coverageEntries.fnMap); + + expect(fnIds.length).to.be.equal(1); + expect(coverageEntries.fnMap[fnIds[0]].name).to.be.equal('set'); + // tslint:disable-next-line:custom-no-magic-numbers + expect(coverageEntries.fnMap[fnIds[0]].line).to.be.equal(6); + const setFunction = `function set(uint x) public { + /* solcov ignore next */ + storedData = x; + }`; + expect(utils.getRange(solcovIgnoreContract, coverageEntries.fnMap[fnIds[0]].loc)).to.be.equal(setFunction); + + expect(coverageEntries.branchMap).to.be.deep.equal({}); + const statementIds = _.keys(coverageEntries.statementMap); + expect(utils.getRange(solcovIgnoreContract, coverageEntries.statementMap[statementIds[0]])).to.be.equal( + setFunction, + ); + expect(statementIds.length).to.be.equal(1); + expect(coverageEntries.modifiersStatementIds.length).to.be.equal(0); + }); + }); +}); diff --git a/packages/sol-trace-based-tools-common/test/fixtures/contracts/AllSolidityFeatures.sol b/packages/sol-trace-based-tools-common/test/fixtures/contracts/AllSolidityFeatures.sol new file mode 100644 index 000000000..21137347e --- /dev/null +++ b/packages/sol-trace-based-tools-common/test/fixtures/contracts/AllSolidityFeatures.sol @@ -0,0 +1,413 @@ +// Examples taken from the Solidity documentation online. + +// for pragma version numbers, see https://docs.npmjs.com/misc/semver#versions +pragma solidity 0.4.0; +pragma solidity ^0.4.0; + +import "SomeFile.sol"; +import "SomeFile.sol" as SomeOtherFile; +import * as SomeSymbol from "AnotherFile.sol"; +import {symbol1 as alias, symbol2} from "File.sol"; + +interface i { + function f(); +} + +contract c { + function c() + { + val1 = 1 wei; // 1 + val2 = 1 szabo; // 1 * 10 ** 12 + val3 = 1 finney; // 1 * 10 ** 15 + val4 = 1 ether; // 1 * 10 ** 18 + } + uint256 val1; + uint256 val2; + uint256 val3; + uint256 val4; +} + +contract test { + enum ActionChoices { GoLeft, GoRight, GoStraight, SitStill } + + function test() + { + choices = ActionChoices.GoStraight; + } + function getChoice() returns (uint d) + { + d = uint256(choices); + } + ActionChoices choices; +} + +contract Base { + function Base(uint i) + { + m_i = i; + } + uint public m_i; +} +contract Derived is Base(0) { + function Derived(uint i) Base(i) {} +} + +contract C { + uint248 x; // 31 bytes: slot 0, offset 0 + uint16 y; // 2 bytes: slot 1, offset 0 (does not fit in slot 0) + uint240 z; // 30 bytes: slot 1, offset 2 bytes + uint8 a; // 1 byte: slot 2, offset 0 bytes + struct S { + uint8 a; // 1 byte, slot +0, offset 0 bytes + uint256 b; // 32 bytes, slot +1, offset 0 bytes (does not fit) + } + S structData; // 2 slots, slot 3, offset 0 bytes (does not really apply) + uint8 alpha; // 1 byte, slot 4 (start new slot after struct) + uint16[3] beta; // 3*16 bytes, slots 5+6 (start new slot for array) + uint8 gamma; // 1 byte, slot 7 (start new slot after array) +} + +contract test { + function f(uint x, uint y) returns (uint z) { + var c = x + 3; + var b = 7 + (c * (8 - 7)) - x; + return -(-b | 0); + } +} + +contract test { + function f(uint x, uint y) returns (uint z) { + return 10; + } +} + +contract c { + function () returns (uint) { return g(8); } + function g(uint pos) internal returns (uint) { setData(pos, 8); return getData(pos); } + function setData(uint pos, uint value) internal { data[pos] = value; } + function getData(uint pos) internal { return data[pos]; } + mapping(uint => uint) data; +} + +contract Sharer { + function sendHalf(address addr) returns (uint balance) { + if (!addr.send(msg.value/2)) + throw; // also reverts the transfer to Sharer + return address(this).balance; + } +} + +/// @dev Models a modifiable and iterable set of uint values. +library IntegerSet +{ + struct data + { + /// Mapping item => index (or zero if not present) + mapping(uint => uint) index; + /// Items by index (index 0 is invalid), items with index[item] == 0 are invalid. + uint[] items; + /// Number of stored items. + uint size; + } + function insert(data storage self, uint value) returns (bool alreadyPresent) + { + uint index = self.index[value]; + if (index > 0) + return true; + else + { + if (self.items.length == 0) self.items.length = 1; + index = self.items.length++; + self.items[index] = value; + self.index[value] = index; + self.size++; + return false; + } + } + function remove(data storage self, uint value) returns (bool success) + { + uint index = self.index[value]; + if (index == 0) + return false; + delete self.index[value]; + delete self.items[index]; + self.size --; + } + function contains(data storage self, uint value) returns (bool) + { + return self.index[value] > 0; + } + function iterate_start(data storage self) returns (uint index) + { + return iterate_advance(self, 0); + } + function iterate_valid(data storage self, uint index) returns (bool) + { + return index < self.items.length; + } + function iterate_advance(data storage self, uint index) returns (uint r_index) + { + index++; + while (iterate_valid(self, index) && self.index[self.items[index]] == index) + index++; + return index; + } + function iterate_get(data storage self, uint index) returns (uint value) + { + return self.items[index]; + } +} + +/// How to use it: +contract User +{ + /// Just a struct holding our data. + IntegerSet.data data; + /// Insert something + function insert(uint v) returns (uint size) + { + /// Sends `data` via reference, so IntegerSet can modify it. + IntegerSet.insert(data, v); + /// We can access members of the struct - but we should take care not to mess with them. + return data.size; + } + /// Computes the sum of all stored data. + function sum() returns (uint s) + { + for (var i = IntegerSet.iterate_start(data); IntegerSet.iterate_valid(data, i); i = IntegerSet.iterate_advance(data, i)) + s += IntegerSet.iterate_get(data, i); + } +} + +// This broke it at one point (namely the modifiers). +contract DualIndex { + mapping(uint => mapping(uint => uint)) data; + address public admin; + + modifier restricted { if (msg.sender == admin) _; } + + function DualIndex() { + admin = msg.sender; + } + + function set(uint key1, uint key2, uint value) restricted { + uint[2][4] memory defaults; // "memory" broke things at one time. + data[key1][key2] = value; + } + + function transfer_ownership(address _admin) restricted { + admin = _admin; + } + + function lookup(uint key1, uint key2) returns(uint) { + return data[key1][key2]; + } +} + +contract A { + +} + +contract B { + +} + +contract C is A, B { + +} + +contract TestPrivate +{ + uint private value; +} + +contract TestInternal +{ + uint internal value; +} + +contract FromSolparse is A, B, TestPrivate, TestInternal { + function() { + uint a = 6 ** 9; + var (x) = 100; + uint y = 2 days; + } +} + +contract CommentedOutFunction { + // FYI: This empty function, as well as the commented + // out function below (bad code) is important to this test. + function() { + + } + + // function something() + // uint x = 10; + // } +} + +library VarHasBrackets { + string constant specialRight = "}"; + //string storage specialLeft = "{"; +} + +library UsingExampleLibrary { + function sum(uint[] storage self) returns (uint s) { + for (uint i = 0; i < self.length; i++) + s += self[i]; + } +} + +contract UsingExampleContract { + using UsingExampleLibrary for uint[]; +} + +contract NewStuff { + uint[] b; + + function someFunction() payable { + string storage a = hex"ab1248fe"; + b[2+2]; + } +} + +// modifier with expression +contract MyContract { + function fun() mymodifier(foo.bar()) {} +} + +library GetCode { + function at(address _addr) returns (bytes o_code) { + assembly { + // retrieve the size of the code, this needs assembly + let size := extcodesize(_addr) + // allocate output byte array - this could also be done without assembly + // by using o_code = new bytes(size) + o_code := mload(0x40) + // new "memory end" including padding + mstore(0x40, add(o_code, and(add(add(size, 0x20), 0x1f), not(0x1f)))) + // store length in memory + mstore(o_code, size) + // actually retrieve the code, this needs assembly + extcodecopy(_addr, add(o_code, 0x20), 0, size) + } + } +} + +contract assemblyLocalBinding { + function test(){ + assembly { + let v := 1 + let x := 0x00 + let y := x + let z := "hello" + } + } +} + +contract assemblyReturn { + uint a = 10; + + function get() constant returns(uint) { + assembly { + mstore(0x40, sload(0)) + byte(0) + address(0) + return(0x40,32) + } + } +} + +contract usesConst { + uint const = 0; +} + +contract memoryArrays { + uint seven = 7; + + function returnNumber(uint number) returns (uint){ + return number; + } + + function alloc() { + uint[] memory a = new uint[](7); + uint[] memory b = new uint[](returnNumber(seven)); + } +} + +contract DeclarativeExpressions { + uint a; + uint b = 7; + uint b2=0; + uint public c; + uint constant public d; + uint public constant e; + uint private constant f = 7; + struct S { uint q;} + + function ham(S storage s1, uint[] storage arr) internal { + uint x; + uint y = 7; + S storage s2 = s1; + uint[] memory stor; + uint[] storage stor2 = arr; + } +} + +contract VariableDeclarationTuple { + function getMyTuple() returns (bool, bool){ + return (true, false); + } + + function ham (){ + var (x, y) = (10, 20); + var (a, b) = getMyTuple(); + var (,c) = (10, 20); + var (d,,) = (10, 20, 30); + var (,e,,f,) = (10, 20, 30, 40, 50); + + var ( + num1, num2, + num3, ,num5 + ) = (10, 20, 30, 40, 50); + } +} + +contract TypeIndexSpacing { + uint [ 7 ] x; + uint [] y; +} + +contract Ballot { + + struct Voter { + uint weight; + bool voted; + } + + function abstain() returns (bool) { + return false; + } + + function foobar() payable owner (myPrice) returns (uint[], address myAdd, string[] names) {} + function foobar() payable owner (myPrice) returns (uint[], address myAdd, string[] names); + + Voter you = Voter(1, true); + + Voter me = Voter({ + weight: 2, + voted: abstain() + }); + + Voter airbnb = Voter({ + weight: 2, + voted: true, + }); +} + +contract multilineReturn { + function a() returns (uint x) { + return + 5; + } +} diff --git a/packages/sol-trace-based-tools-common/test/fixtures/contracts/SimpleStorage.sol b/packages/sol-trace-based-tools-common/test/fixtures/contracts/SimpleStorage.sol new file mode 100644 index 000000000..e4b4ac246 --- /dev/null +++ b/packages/sol-trace-based-tools-common/test/fixtures/contracts/SimpleStorage.sol @@ -0,0 +1,11 @@ +pragma solidity ^0.4.21; + +contract SimpleStorage { + uint public storedData; + function set(uint x) { + storedData = x; + } + function get() constant returns (uint retVal) { + return storedData; + } +} diff --git a/packages/sol-trace-based-tools-common/test/fixtures/contracts/Simplest.sol b/packages/sol-trace-based-tools-common/test/fixtures/contracts/Simplest.sol new file mode 100644 index 000000000..d71016e07 --- /dev/null +++ b/packages/sol-trace-based-tools-common/test/fixtures/contracts/Simplest.sol @@ -0,0 +1,2 @@ +contract Simplest { +} diff --git a/packages/sol-trace-based-tools-common/test/fixtures/contracts/SolcovIgnore.sol b/packages/sol-trace-based-tools-common/test/fixtures/contracts/SolcovIgnore.sol new file mode 100644 index 000000000..a7977ffb4 --- /dev/null +++ b/packages/sol-trace-based-tools-common/test/fixtures/contracts/SolcovIgnore.sol @@ -0,0 +1,22 @@ +pragma solidity ^0.4.21; + +contract SolcovIgnore { + uint public storedData; + + function set(uint x) public { + /* solcov ignore next */ + storedData = x; + } + + /* solcov ignore next */ + function get() constant public returns (uint retVal) { + return storedData; + } +} + +/* solcov ignore next */ +contract Ignore { + function ignored() public returns (bool) { + return false; + } +} diff --git a/packages/sol-trace-based-tools-common/test/instructions_test.ts b/packages/sol-trace-based-tools-common/test/instructions_test.ts new file mode 100644 index 000000000..058053cf9 --- /dev/null +++ b/packages/sol-trace-based-tools-common/test/instructions_test.ts @@ -0,0 +1,19 @@ +import * as chai from 'chai'; +import 'mocha'; + +import { constants } from '../src/constants'; +import { getPcToInstructionIndexMapping } from '../src/instructions'; + +const expect = chai.expect; + +describe('instructions', () => { + describe('#getPcToInstructionIndexMapping', () => { + it('correctly maps pcs to instruction indexed', () => { + // tslint:disable-next-line:custom-no-magic-numbers + const bytecode = new Uint8Array([constants.PUSH1, 42, constants.PUSH2, 1, 2, constants.TIMESTAMP]); + const pcToInstruction = getPcToInstructionIndexMapping(bytecode); + const expectedPcToInstruction = { '0': 0, '2': 1, '5': 2 }; + expect(pcToInstruction).to.be.deep.equal(expectedPcToInstruction); + }); + }); +}); diff --git a/packages/sol-trace-based-tools-common/test/sol_compiler_artifact_adapter_test.ts b/packages/sol-trace-based-tools-common/test/sol_compiler_artifact_adapter_test.ts new file mode 100644 index 000000000..9c58d2cef --- /dev/null +++ b/packages/sol-trace-based-tools-common/test/sol_compiler_artifact_adapter_test.ts @@ -0,0 +1,29 @@ +import * as chai from 'chai'; +import * as _ from 'lodash'; +import 'mocha'; +import * as path from 'path'; + +import { SolCompilerArtifactAdapter } from '../src/artifact_adapters/sol_compiler_artifact_adapter'; + +const expect = chai.expect; + +describe('SolCompilerArtifactAdapter', () => { + describe('#collectContractsData', () => { + it('correctly collects contracts data', async () => { + const artifactsPath = path.resolve(__dirname, 'fixtures/artifacts'); + const sourcesPath = path.resolve(__dirname, 'fixtures/contracts'); + const zeroExArtifactsAdapter = new SolCompilerArtifactAdapter(artifactsPath, sourcesPath); + const contractsData = await zeroExArtifactsAdapter.collectContractsDataAsync(); + _.forEach(contractsData, contractData => { + expect(contractData).to.have.keys([ + 'sourceCodes', + 'sources', + 'sourceMap', + 'sourceMapRuntime', + 'bytecode', + 'runtimeBytecode', + ]); + }); + }); + }); +}); diff --git a/packages/sol-trace-based-tools-common/test/source_maps_test.ts b/packages/sol-trace-based-tools-common/test/source_maps_test.ts new file mode 100644 index 000000000..5820bedd7 --- /dev/null +++ b/packages/sol-trace-based-tools-common/test/source_maps_test.ts @@ -0,0 +1,71 @@ +import * as chai from 'chai'; +import * as fs from 'fs'; +import * as _ from 'lodash'; +import 'mocha'; +import * as path from 'path'; + +import { getLocationByOffset, parseSourceMap } from '../src/source_maps'; + +const expect = chai.expect; + +const simplestContractBaseName = 'Simplest.sol'; +const simplestContractFileName = path.resolve(__dirname, 'fixtures/contracts', simplestContractBaseName); +const simplestContract = fs.readFileSync(simplestContractFileName).toString(); + +describe('source maps', () => { + describe('#getLocationByOffset', () => { + it('correctly computes location by offset', () => { + const locationByOffset = getLocationByOffset(simplestContract); + const expectedLocationByOffset = { + '0': { line: 1, column: 0 }, + '1': { line: 1, column: 1 }, + '2': { line: 1, column: 2 }, + '3': { line: 1, column: 3 }, + '4': { line: 1, column: 4 }, + '5': { line: 1, column: 5 }, + '6': { line: 1, column: 6 }, + '7': { line: 1, column: 7 }, + '8': { line: 1, column: 8 }, + '9': { line: 1, column: 9 }, + '10': { line: 1, column: 10 }, + '11': { line: 1, column: 11 }, + '12': { line: 1, column: 12 }, + '13': { line: 1, column: 13 }, + '14': { line: 1, column: 14 }, + '15': { line: 1, column: 15 }, + '16': { line: 1, column: 16 }, + '17': { line: 1, column: 17 }, + '18': { line: 1, column: 18 }, + '19': { line: 1, column: 19 }, + '20': { line: 2, column: 0 }, + '21': { line: 2, column: 1 }, + '22': { line: 3, column: 0 }, + }; + expect(locationByOffset).to.be.deep.equal(expectedLocationByOffset); + }); + }); + describe('#parseSourceMap', () => { + it('correctly parses the source map', () => { + // This is the source map and bytecode for an empty contract like Example.sol + const srcMap = '0:21:0:-;;;;;;;;;;;;;;;;;'; + const bytecodeHex = + '60606040523415600e57600080fd5b603580601b6000396000f3006060604052600080fd00a165627a7a72305820377cdef690e46589f40efeef14d8ef73504af059fb3fd46f1da3cd2fc52ef7890029'; + const sources = [simplestContractBaseName]; + const pcToSourceRange = parseSourceMap([simplestContract], srcMap, bytecodeHex, sources); + const expectedSourceRange = { + location: { + start: { line: 1, column: 0 }, + end: { line: 2, column: 1 }, + }, + fileName: simplestContractBaseName, + }; + _.forEach(pcToSourceRange, sourceRange => { + // Solidity source maps are too short and we map some instructions to undefined + // Source: https://github.com/ethereum/solidity/issues/3741 + if (!_.isUndefined(sourceRange)) { + expect(sourceRange).to.be.deep.equal(expectedSourceRange); + } + }); + }); + }); +}); diff --git a/packages/sol-trace-based-tools-common/test/trace_test.ts b/packages/sol-trace-based-tools-common/test/trace_test.ts new file mode 100644 index 000000000..7a034362c --- /dev/null +++ b/packages/sol-trace-based-tools-common/test/trace_test.ts @@ -0,0 +1,55 @@ +import * as chai from 'chai'; +import { OpCode, StructLog } from 'ethereum-types'; +import * as _ from 'lodash'; +import 'mocha'; + +import { getTracesByContractAddress } from '../src/trace'; + +const expect = chai.expect; + +const DEFAULT_STRUCT_LOG: StructLog = { + depth: 0, + error: '', + gas: 0, + gasCost: 0, + memory: [], + op: OpCode.Invalid, + pc: 0, + stack: [], + storage: {}, +}; + +function addDefaultStructLogFields(compactStructLog: Partial & { op: OpCode; depth: number }): StructLog { + return { ...DEFAULT_STRUCT_LOG, ...compactStructLog }; +} + +describe('Trace', () => { + describe('#getTracesByContractAddress', () => { + it('correctly splits trace by contract address', () => { + const delegateCallAddress = '0x0000000000000000000000000000000000000002'; + const trace = [ + { + op: OpCode.DelegateCall, + stack: [delegateCallAddress, '0x'], + depth: 0, + }, + { + op: OpCode.Return, + depth: 1, + }, + { + op: OpCode.Return, + depth: 0, + }, + ]; + const fullTrace = _.map(trace, compactStructLog => addDefaultStructLogFields(compactStructLog)); + const startAddress = '0x0000000000000000000000000000000000000001'; + const traceByContractAddress = getTracesByContractAddress(fullTrace, startAddress); + const expectedTraceByContractAddress = { + [startAddress]: [fullTrace[0], fullTrace[2]], + [delegateCallAddress]: [fullTrace[1]], + }; + expect(traceByContractAddress).to.be.deep.equal(expectedTraceByContractAddress); + }); + }); +}); diff --git a/packages/sol-trace-based-tools-common/test/utils_test.ts b/packages/sol-trace-based-tools-common/test/utils_test.ts new file mode 100644 index 000000000..6fc8fcfe1 --- /dev/null +++ b/packages/sol-trace-based-tools-common/test/utils_test.ts @@ -0,0 +1,53 @@ +import * as chai from 'chai'; +import * as dirtyChai from 'dirty-chai'; +import 'mocha'; + +import { utils } from '../src/utils'; + +chai.use(dirtyChai); +const expect = chai.expect; + +describe('utils', () => { + describe('#compareLineColumn', () => { + it('correctly compares LineColumns', () => { + expect(utils.compareLineColumn({ line: 1, column: 3 }, { line: 1, column: 4 })).to.be.lessThan(0); + expect(utils.compareLineColumn({ line: 1, column: 4 }, { line: 1, column: 3 })).to.be.greaterThan(0); + expect(utils.compareLineColumn({ line: 1, column: 3 }, { line: 1, column: 3 })).to.be.equal(0); + expect(utils.compareLineColumn({ line: 0, column: 2 }, { line: 1, column: 0 })).to.be.lessThan(0); + expect(utils.compareLineColumn({ line: 1, column: 0 }, { line: 0, column: 2 })).to.be.greaterThan(0); + }); + }); + + describe('#isRangeInside', () => { + it('returns true if inside', () => { + expect( + utils.isRangeInside( + { start: { line: 1, column: 3 }, end: { line: 1, column: 4 } }, + { start: { line: 1, column: 2 }, end: { line: 1, column: 5 } }, + ), + ).to.be.true(); + }); + it('returns true if the same', () => { + expect( + utils.isRangeInside( + { start: { line: 1, column: 3 }, end: { line: 1, column: 4 } }, + { start: { line: 1, column: 3 }, end: { line: 1, column: 4 } }, + ), + ).to.be.true(); + }); + it('returns false if not inside', () => { + expect( + utils.isRangeInside( + { start: { line: 1, column: 3 }, end: { line: 1, column: 4 } }, + { start: { line: 1, column: 4 }, end: { line: 1, column: 4 } }, + ), + ).to.be.false(); + expect( + utils.isRangeInside( + { start: { line: 1, column: 3 }, end: { line: 1, column: 4 } }, + { start: { line: 1, column: 4 }, end: { line: 1, column: 5 } }, + ), + ).to.be.false(); + }); + }); +}); diff --git a/packages/sol-trace-based-tools-common/tsconfig.json b/packages/sol-trace-based-tools-common/tsconfig.json new file mode 100644 index 000000000..2ee711adc --- /dev/null +++ b/packages/sol-trace-based-tools-common/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig", + "compilerOptions": { + "outDir": "lib", + "rootDir": "." + }, + "include": ["./src/**/*", "./test/**/*"] +} diff --git a/packages/sol-trace-based-tools-common/tslint.json b/packages/sol-trace-based-tools-common/tslint.json new file mode 100644 index 000000000..dd9053357 --- /dev/null +++ b/packages/sol-trace-based-tools-common/tslint.json @@ -0,0 +1,3 @@ +{ + "extends": ["@0x/tslint-config"] +} diff --git a/packages/sol-trace/.npmignore b/packages/sol-trace/.npmignore new file mode 100644 index 000000000..037786e46 --- /dev/null +++ b/packages/sol-trace/.npmignore @@ -0,0 +1,6 @@ +.* +yarn-error.log +/src/ +/scripts/ +tsconfig.json +/lib/src/monorepo_scripts/ diff --git a/packages/sol-trace/CHANGELOG.json b/packages/sol-trace/CHANGELOG.json new file mode 100644 index 000000000..ca6048d99 --- /dev/null +++ b/packages/sol-trace/CHANGELOG.json @@ -0,0 +1,12 @@ +[ + { + "version": "1.0.0", + "changes": [ + { + "note": + "Initial release as a separate package. For historic entries check @0x/sol-trace-based-tools-common", + "pr": 1492 + } + ] + } +] diff --git a/packages/sol-trace/README.md b/packages/sol-trace/README.md new file mode 100644 index 000000000..dba193426 --- /dev/null +++ b/packages/sol-trace/README.md @@ -0,0 +1,75 @@ +## @0x/sol-trace + +Prints code traces when revert happens. + +### Read the [Documentation](https://0xproject.com/docs/sol-trace). + +## Installation + +```bash +yarn add @0x/sol-trace +``` + +**Import** + +```javascript +import { RevertTraceSubprovider } from '@0x/sol-trace'; +``` + +or + +```javascript +var RevertTraceSubprovider = require('@0x/sol-trace').RevertTraceSubprovider; +``` + +## Contributing + +We welcome improvements and fixes from the wider community! 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 +``` + +### Build + +To build this package and all other monorepo packages that it depends on, run the following from the monorepo root directory: + +```bash +PKG=@0x/sol-trace yarn build +``` + +Or continuously rebuild on change: + +```bash +PKG=@0x/sol-trace yarn watch +``` + +### Clean + +```bash +yarn clean +``` + +### Lint + +```bash +yarn lint +``` + +### Run Tests + +```bash +yarn test +``` diff --git a/packages/sol-trace/package.json b/packages/sol-trace/package.json new file mode 100644 index 000000000..fd80c541e --- /dev/null +++ b/packages/sol-trace/package.json @@ -0,0 +1,52 @@ +{ + "name": "@0x/sol-trace", + "version": "1.0.0", + "engines": { + "node": ">=6.12" + }, + "description": "Prints stack trace on Solidity revert", + "main": "lib/src/index.js", + "types": "lib/src/index.d.ts", + "scripts": { + "build": "tsc -b", + "build:ci": "yarn build", + "lint": "tslint --format stylish --project .", + "clean": "shx rm -rf lib src/artifacts generated_docs", + "docs:json": "typedoc --excludePrivate --excludeExternals --target ES5 --tsconfig typedoc-tsconfig.json --json $JSON_FILE_PATH $PROJECT_FILES" + }, + "config": { + "postpublish": { + "assets": [] + } + }, + "repository": { + "type": "git", + "url": "https://github.com/0xProject/0x-monorepo.git" + }, + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/0xProject/0x-monorepo/issues" + }, + "homepage": "https://github.com/0xProject/0x-monorepo/packages/sol-trace/README.md", + "dependencies": { + "@0x/subproviders": "^2.1.8", + "@0x/typescript-typings": "^3.0.6", + "@0x/sol-trace-based-tools-common": "^2.1.16", + "ethereum-types": "^1.1.4", + "ethereumjs-util": "^5.1.1", + "lodash": "^4.17.5", + "loglevel": "^1.6.1" + }, + "devDependencies": { + "@0x/tslint-config": "^2.0.0", + "@types/loglevel": "^1.5.3", + "@types/node": "*", + "npm-run-all": "^4.1.2", + "shx": "^0.2.2", + "tslint": "5.11.0", + "typescript": "3.0.1" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/sol-trace/src/globals.d.ts b/packages/sol-trace/src/globals.d.ts new file mode 100644 index 000000000..e799b3529 --- /dev/null +++ b/packages/sol-trace/src/globals.d.ts @@ -0,0 +1,7 @@ +// tslint:disable:completed-docs +declare module '*.json' { + const json: any; + /* tslint:disable */ + export default json; + /* tslint:enable */ +} diff --git a/packages/sol-trace/src/index.ts b/packages/sol-trace/src/index.ts new file mode 100644 index 000000000..1f3ca3a99 --- /dev/null +++ b/packages/sol-trace/src/index.ts @@ -0,0 +1,24 @@ +export { + AbstractArtifactAdapter, + TruffleArtifactAdapter, + SolCompilerArtifactAdapter, + ContractData, +} from '@0x/sol-trace-based-tools-common'; + +export { RevertTraceSubprovider } from './revert_trace_subprovider'; + +export { + JSONRPCRequestPayload, + Provider, + JSONRPCErrorCallback, + JSONRPCResponsePayload, + JSONRPCResponseError, +} from 'ethereum-types'; + +export { + JSONRPCRequestPayloadWithMethod, + NextCallback, + ErrorCallback, + OnNextCompleted, + Callback, +} from '@0x/subproviders'; diff --git a/packages/sol-trace/src/revert_trace_subprovider.ts b/packages/sol-trace/src/revert_trace_subprovider.ts new file mode 100644 index 000000000..37e62b3f8 --- /dev/null +++ b/packages/sol-trace/src/revert_trace_subprovider.ts @@ -0,0 +1,167 @@ +import { + AbstractArtifactAdapter, + constants, + ContractData, + EvmCallStack, + getRevertTrace, + getSourceRangeSnippet, + parseSourceMap, + SourceRange, + SourceSnippet, + TraceCollectionSubprovider, + utils, +} from '@0x/sol-trace-based-tools-common'; +import { stripHexPrefix } from 'ethereumjs-util'; +import * as _ from 'lodash'; +import { getLogger, levels, Logger } from 'loglevel'; + +/** + * This class implements the [web3-provider-engine](https://github.com/MetaMask/provider-engine) subprovider interface. + * It is used to report call stack traces whenever a revert occurs. + */ +export class RevertTraceSubprovider extends TraceCollectionSubprovider { + // Lock is used to not accept normal transactions while doing call/snapshot magic because they'll be reverted later otherwise + private _contractsData!: ContractData[]; + private readonly _artifactAdapter: AbstractArtifactAdapter; + private readonly _logger: Logger; + + /** + * Instantiates a RevertTraceSubprovider instance + * @param artifactAdapter Adapter for used artifacts format (0x, truffle, giveth, etc.) + * @param defaultFromAddress default from address to use when sending transactions + * @param isVerbose If true, we will log any unknown transactions. Otherwise we will ignore them + */ + constructor(artifactAdapter: AbstractArtifactAdapter, defaultFromAddress: string, isVerbose: boolean = true) { + const traceCollectionSubproviderConfig = { + shouldCollectTransactionTraces: true, + shouldCollectGasEstimateTraces: true, + shouldCollectCallTraces: true, + }; + super(defaultFromAddress, traceCollectionSubproviderConfig); + this._artifactAdapter = artifactAdapter; + this._logger = getLogger('sol-trace'); + this._logger.setLevel(isVerbose ? levels.TRACE : levels.ERROR); + } + // tslint:disable-next-line:no-unused-variable + protected async _recordTxTraceAsync(address: string, data: string | undefined, txHash: string): Promise { + await this._web3Wrapper.awaitTransactionMinedAsync(txHash, 0); + const trace = await this._web3Wrapper.getTransactionTraceAsync(txHash, { + disableMemory: true, + disableStack: false, + disableStorage: true, + }); + const evmCallStack = getRevertTrace(trace.structLogs, address); + if (evmCallStack.length > 0) { + // if getRevertTrace returns a call stack it means there was a + // revert. + await this._printStackTraceAsync(evmCallStack); + } + } + private async _printStackTraceAsync(evmCallStack: EvmCallStack): Promise { + const sourceSnippets: SourceSnippet[] = []; + if (_.isUndefined(this._contractsData)) { + this._contractsData = await this._artifactAdapter.collectContractsDataAsync(); + } + for (const evmCallStackEntry of evmCallStack) { + const isContractCreation = evmCallStackEntry.address === constants.NEW_CONTRACT; + if (isContractCreation) { + this._logger.error('Contract creation not supported'); + continue; + } + const bytecode = await this._web3Wrapper.getContractCodeAsync(evmCallStackEntry.address); + const contractData = utils.getContractDataIfExists(this._contractsData, bytecode); + if (_.isUndefined(contractData)) { + const errMsg = isContractCreation + ? `Unknown contract creation transaction` + : `Transaction to an unknown address: ${evmCallStackEntry.address}`; + this._logger.warn(errMsg); + continue; + } + const bytecodeHex = stripHexPrefix(bytecode); + const sourceMap = isContractCreation ? contractData.sourceMap : contractData.sourceMapRuntime; + + const pcToSourceRange = parseSourceMap( + contractData.sourceCodes, + sourceMap, + bytecodeHex, + contractData.sources, + ); + // tslint:disable-next-line:no-unnecessary-initializer + let sourceRange: SourceRange | undefined = undefined; + let pc = evmCallStackEntry.structLog.pc; + // Sometimes there is not a mapping for this pc (e.g. if the revert + // actually happens in assembly). In that case, we want to keep + // searching backwards by decrementing the pc until we find a + // mapped source range. + while (_.isUndefined(sourceRange) && pc > 0) { + sourceRange = pcToSourceRange[pc]; + pc -= 1; + } + if (_.isUndefined(sourceRange)) { + this._logger.warn( + `could not find matching sourceRange for structLog: ${JSON.stringify( + _.omit(evmCallStackEntry.structLog, 'stack'), + )}`, + ); + continue; + } + + const fileIndex = contractData.sources.indexOf(sourceRange.fileName); + const sourceSnippet = getSourceRangeSnippet(sourceRange, contractData.sourceCodes[fileIndex]); + if (sourceSnippet !== null) { + sourceSnippets.push(sourceSnippet); + } + } + const filteredSnippets = filterSnippets(sourceSnippets); + if (filteredSnippets.length > 0) { + this._logger.error('\n\nStack trace for REVERT:\n'); + _.forEach(_.reverse(filteredSnippets), snippet => { + const traceString = getStackTraceString(snippet); + this._logger.error(traceString); + }); + this._logger.error('\n'); + } else { + this._logger.error('REVERT detected but could not determine stack trace'); + } + } +} + +// removes duplicates and if statements +function filterSnippets(sourceSnippets: SourceSnippet[]): SourceSnippet[] { + if (sourceSnippets.length === 0) { + return []; + } + const results: SourceSnippet[] = [sourceSnippets[0]]; + let prev = sourceSnippets[0]; + for (const sourceSnippet of sourceSnippets) { + if (sourceSnippet.type === 'IfStatement') { + continue; + } else if (sourceSnippet.source === prev.source) { + prev = sourceSnippet; + continue; + } + results.push(sourceSnippet); + prev = sourceSnippet; + } + return results; +} + +function getStackTraceString(sourceSnippet: SourceSnippet): string { + let result = `${sourceSnippet.fileName}:${sourceSnippet.range.start.line}:${sourceSnippet.range.start.column}`; + const snippetString = getSourceSnippetString(sourceSnippet); + if (snippetString !== '') { + result += `:\n ${snippetString}`; + } + return result; +} + +function getSourceSnippetString(sourceSnippet: SourceSnippet): string { + switch (sourceSnippet.type) { + case 'ContractDefinition': + return `contract ${sourceSnippet.name}`; + case 'FunctionDefinition': + return `function ${sourceSnippet.name}`; + default: + return `${sourceSnippet.source}`; + } +} diff --git a/packages/sol-trace/tsconfig.json b/packages/sol-trace/tsconfig.json new file mode 100644 index 000000000..233008d61 --- /dev/null +++ b/packages/sol-trace/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig", + "compilerOptions": { + "outDir": "lib", + "rootDir": "." + }, + "include": ["./src/**/*"] +} diff --git a/packages/sol-trace/tslint.json b/packages/sol-trace/tslint.json new file mode 100644 index 000000000..dd9053357 --- /dev/null +++ b/packages/sol-trace/tslint.json @@ -0,0 +1,3 @@ +{ + "extends": ["@0x/tslint-config"] +} diff --git a/packages/sol-trace/typedoc-tsconfig.json b/packages/sol-trace/typedoc-tsconfig.json new file mode 100644 index 000000000..a4c669cb6 --- /dev/null +++ b/packages/sol-trace/typedoc-tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../typedoc-tsconfig", + "compilerOptions": { + "outDir": "lib" + }, + "include": ["./src/**/*"] +} diff --git a/packages/sra-spec/src/md/introduction.md b/packages/sra-spec/src/md/introduction.md index 824df8998..ce61abff6 100644 --- a/packages/sra-spec/src/md/introduction.md +++ b/packages/sra-spec/src/md/introduction.md @@ -1,12 +1,12 @@ # Schemas -The [JSON schemas](http://json-schema.org/) for the API payloads and responses can be found in [@0xproject/json-schemas](https://github.com/0xProject/0x.js/tree/development/packages/json-schemas). Examples of each payload and response can be found in the library's [test suite](https://github.com/0xProject/0x.js/blob/development/packages/json-schemas/test/schema_test.ts#L1). +The [JSON schemas](http://json-schema.org/) for the API payloads and responses can be found in [@0xproject/json-schemas](https://github.com/0xProject/0x-monorepo/tree/development/packages/json-schemas). Examples of each payload and response can be found in the library's [test suite](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/test/schema_test.ts#L1). ```bash npm install @0xproject/json-schemas --save ``` -You can easily validate your API's payloads and responses using the [@0xproject/json-schemas](https://github.com/0xProject/0x.js/tree/development/packages/json-schemas) package: +You can easily validate your API's payloads and responses using the [@0xproject/json-schemas](https://github.com/0xProject/0x-monorepo/tree/development/packages/json-schemas) package: ```js import {SchemaValidator, ValidatorResult, schemas} from '@0xproject/json-schemas'; diff --git a/packages/website/md/docs/sol_cov/1/installation.md b/packages/website/md/docs/sol_cov/1/installation.md deleted file mode 100644 index b9ce25a5f..000000000 --- a/packages/website/md/docs/sol_cov/1/installation.md +++ /dev/null @@ -1,17 +0,0 @@ -**Install** - -```bash -yarn add @0xproject/sol-cov -``` - -**Import** - -```javascript -import { CoverageSubprovider } from '@0xproject/sol-cov'; -``` - -or - -```javascript -var CoverageSubprovider = require('@0xproject/sol-cov').CoverageSubprovider; -``` diff --git a/packages/website/md/docs/sol_cov/1/introduction.md b/packages/website/md/docs/sol_cov/1/introduction.md deleted file mode 100644 index 7064a3554..000000000 --- a/packages/website/md/docs/sol_cov/1/introduction.md +++ /dev/null @@ -1 +0,0 @@ -Welcome to the [@0xproject/sol-cov](https://github.com/0xProject/0x-monorepo/tree/development/packages/sol-cov) documentation! Sol-cov is a Solidity coverage tool for your smart contract tests. diff --git a/packages/website/md/docs/sol_cov/1/usage.md b/packages/website/md/docs/sol_cov/1/usage.md deleted file mode 100644 index c2b8404af..000000000 --- a/packages/website/md/docs/sol_cov/1/usage.md +++ /dev/null @@ -1,62 +0,0 @@ -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://0x.org/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. - -Coverage subprovider needs some info about your contracts (`srcMap`, `bytecode`). It gets that info from your project's artifacts. Some frameworks have their own artifact format. Some artifact formats don't actually contain all the neccessary data. - -In order to use `CoverageSubprovider` with your favorite framework you need to pass an `artifactsAdapter` to it. - -### Sol-compiler - -If you are generating your artifacts with [@0xproject/sol-compiler](https://0x.org/docs/sol-compiler) you can use the `SolCompilerArtifactsAdapter` we've implemented for you. - -```typescript -import { SolCompilerArtifactsAdapter } from '@0xproject/sol-cov'; -const artifactsPath = 'src/artifacts'; -const contractsPath = 'src/contracts'; -const artifactsAdapter = new SolCompilerArtifactsAdapter(artifactsPath, contractsPath); -``` - -### Truffle - -If your project is using [Truffle](https://truffleframework.com/), we've written a `TruffleArtifactsAdapter`for you. - -```typescript -import { TruffleArtifactAdapter } from '@0xproject/sol-cov'; -const contractsPath = 'src/contracts'; -const artifactAdapter = new TruffleArtifactAdapter(contractsDir); -``` - -Because truffle artifacts don't have all the data we need - we actually will recompile your contracts under the hood. That's why you don't need to pass an `artifactsPath`. - -### Other framework/toolset - -You'll need to write your own artifacts adapter. It should extend `AbstractArtifactsAdapter`. -Look at the code of the two adapters above for examples. - -### Usage - -```typescript -import { CoverageSubprovider } from '@0xproject/sol-cov'; -import ProviderEngine = require('web3-provider-engine'); - -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 isVerbose = true; -const coverageSubprovider = new CoverageSubprovider(artifactsAdapter, defaultFromAddress, isVerbose); - -provider.addProvider(coverageSubprovider); -``` - -After your test suite is complete (e.g in the Mocha global `after` hook), you'll need to call: - -```typescript -await coverageSubprovider.writeCoverageAsync(); -``` - -This will create a `coverage.json` file in a `coverage` directory. This file has an [Istanbul format](https://github.com/gotwarlost/istanbul/blob/master/coverage.json.md) - so you can use it with any of the existing Istanbul reporters. diff --git a/packages/website/md/docs/sol_cov/2/installation.md b/packages/website/md/docs/sol_cov/2/installation.md deleted file mode 100644 index 1d4557cf5..000000000 --- a/packages/website/md/docs/sol_cov/2/installation.md +++ /dev/null @@ -1,17 +0,0 @@ -**Install** - -```bash -yarn add @0x/sol-cov -``` - -**Import** - -```javascript -import { CoverageSubprovider } from '@0x/sol-cov'; -``` - -or - -```javascript -var CoverageSubprovider = require('@0x/sol-cov').CoverageSubprovider; -``` diff --git a/packages/website/md/docs/sol_cov/2/introduction.md b/packages/website/md/docs/sol_cov/2/introduction.md deleted file mode 100644 index ac3256845..000000000 --- a/packages/website/md/docs/sol_cov/2/introduction.md +++ /dev/null @@ -1 +0,0 @@ -Welcome to the [sol-cov](https://github.com/0xProject/0x-monorepo/tree/development/packages/sol-cov) documentation! Sol-cov is a Solidity coverage tool for your smart contract tests. diff --git a/packages/website/md/docs/sol_cov/2/usage.md b/packages/website/md/docs/sol_cov/2/usage.md deleted file mode 100644 index 8e33f3bf5..000000000 --- a/packages/website/md/docs/sol_cov/2/usage.md +++ /dev/null @@ -1,62 +0,0 @@ -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://0x.org/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. - -Coverage subprovider needs some info about your contracts (`srcMap`, `bytecode`). It gets that info from your project's artifacts. Some frameworks have their own artifact format. Some artifact formats don't actually contain all the neccessary data. - -In order to use `CoverageSubprovider` with your favorite framework you need to pass an `artifactsAdapter` to it. - -### Sol-compiler - -If you are generating your artifacts with [@0x/sol-compiler](https://0x.org/docs/sol-compiler) you can use the `SolCompilerArtifactsAdapter` we've implemented for you. - -```typescript -import { SolCompilerArtifactsAdapter } from '@0x/sol-cov'; -const artifactsPath = 'src/artifacts'; -const contractsPath = 'src/contracts'; -const artifactsAdapter = new SolCompilerArtifactsAdapter(artifactsPath, contractsPath); -``` - -### Truffle - -If your project is using [Truffle](https://truffleframework.com/), we've written a `TruffleArtifactsAdapter`for you. - -```typescript -import { TruffleArtifactAdapter } from '@0x/sol-cov'; -const contractsPath = 'src/contracts'; -const artifactAdapter = new TruffleArtifactAdapter(contractsDir); -``` - -Because truffle artifacts don't have all the data we need - we actually will recompile your contracts under the hood. That's why you don't need to pass an `artifactsPath`. - -### Other framework/toolset - -You'll need to write your own artifacts adapter. It should extend `AbstractArtifactsAdapter`. -Look at the code of the two adapters above for examples. - -### Usage - -```typescript -import { CoverageSubprovider } from '@0x/sol-cov'; -import ProviderEngine = require('web3-provider-engine'); - -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 isVerbose = true; -const coverageSubprovider = new CoverageSubprovider(artifactsAdapter, defaultFromAddress, isVerbose); - -provider.addProvider(coverageSubprovider); -``` - -After your test suite is complete (e.g in the Mocha global `after` hook), you'll need to call: - -```typescript -await coverageSubprovider.writeCoverageAsync(); -``` - -This will create a `coverage.json` file in a `coverage` directory. This file has an [Istanbul format](https://github.com/gotwarlost/istanbul/blob/master/coverage.json.md) - so you can use it with any of the existing Istanbul reporters. diff --git a/packages/website/md/docs/sol_coverage/installation.md b/packages/website/md/docs/sol_coverage/installation.md new file mode 100644 index 000000000..c7aaf07e9 --- /dev/null +++ b/packages/website/md/docs/sol_coverage/installation.md @@ -0,0 +1,17 @@ +**Install** + +```bash +yarn add @0x/sol-coverage +``` + +**Import** + +```javascript +import { CoverageSubprovider } from '@0x/sol-coverage'; +``` + +or + +```javascript +var CoverageSubprovider = require('@0x/sol-coverage').CoverageSubprovider; +``` diff --git a/packages/website/md/docs/sol_coverage/introduction.md b/packages/website/md/docs/sol_coverage/introduction.md new file mode 100644 index 000000000..3214e93a9 --- /dev/null +++ b/packages/website/md/docs/sol_coverage/introduction.md @@ -0,0 +1 @@ +Welcome to the [sol-coverage](https://github.com/0xProject/0x-monorepo/tree/development/packages/sol-coverage) documentation! Sol-coverage is a Solidity coverage tool for your smart contract tests. diff --git a/packages/website/md/docs/sol_coverage/usage.md b/packages/website/md/docs/sol_coverage/usage.md new file mode 100644 index 000000000..dd3cdf597 --- /dev/null +++ b/packages/website/md/docs/sol_coverage/usage.md @@ -0,0 +1,62 @@ +Sol-coverage 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://0x.org/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. + +Coverage subprovider needs some info about your contracts (`srcMap`, `bytecode`). It gets that info from your project's artifacts. Some frameworks have their own artifact format. Some artifact formats don't actually contain all the neccessary data. + +In order to use `CoverageSubprovider` with your favorite framework you need to pass an `artifactsAdapter` to it. + +### Sol-compiler + +If you are generating your artifacts with [@0x/sol-compiler](https://0x.org/docs/sol-compiler) you can use the `SolCompilerArtifactsAdapter` we've implemented for you. + +```typescript +import { SolCompilerArtifactsAdapter } from '@0x/sol-coverage'; +const artifactsPath = 'src/artifacts'; +const contractsPath = 'src/contracts'; +const artifactsAdapter = new SolCompilerArtifactsAdapter(artifactsPath, contractsPath); +``` + +### Truffle + +If your project is using [Truffle](https://truffleframework.com/), we've written a `TruffleArtifactsAdapter`for you. + +```typescript +import { TruffleArtifactAdapter } from '@0x/sol-coverage'; +const contractsPath = 'src/contracts'; +const artifactAdapter = new TruffleArtifactAdapter(contractsDir); +``` + +Because truffle artifacts don't have all the data we need - we actually will recompile your contracts under the hood. That's why you don't need to pass an `artifactsPath`. + +### Other framework/toolset + +You'll need to write your own artifacts adapter. It should extend `AbstractArtifactsAdapter`. +Look at the code of the two adapters above for examples. + +### Usage + +```typescript +import { CoverageSubprovider } from '@0x/sol-coverage'; +import ProviderEngine = require('web3-provider-engine'); + +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 isVerbose = true; +const coverageSubprovider = new CoverageSubprovider(artifactsAdapter, defaultFromAddress, isVerbose); + +provider.addProvider(coverageSubprovider); +``` + +After your test suite is complete (e.g in the Mocha global `after` hook), you'll need to call: + +```typescript +await coverageSubprovider.writeCoverageAsync(); +``` + +This will create a `coverage.json` file in a `coverage` directory. This file has an [Istanbul format](https://github.com/gotwarlost/istanbul/blob/master/coverage.json.md) - so you can use it with any of the existing Istanbul reporters. diff --git a/packages/website/md/docs/sol_profiler/installation.md b/packages/website/md/docs/sol_profiler/installation.md new file mode 100644 index 000000000..be9a365f5 --- /dev/null +++ b/packages/website/md/docs/sol_profiler/installation.md @@ -0,0 +1,17 @@ +**Install** + +```bash +yarn add @0x/sol-profiler +``` + +**Import** + +```javascript +import { ProfilerSubprovider } from '@0x/sol-profiler'; +``` + +or + +```javascript +var ProfilerSubprovider = require('@0x/sol-profiler').ProfilerSubprovider; +``` diff --git a/packages/website/md/docs/sol_profiler/introduction.md b/packages/website/md/docs/sol_profiler/introduction.md new file mode 100644 index 000000000..bd53fb0fe --- /dev/null +++ b/packages/website/md/docs/sol_profiler/introduction.md @@ -0,0 +1 @@ +Welcome to the [sol-profiler](https://github.com/0xProject/0x-monorepo/tree/development/packages/sol-profiler) documentation! Sol-profiler is a Solidity profiler tool. diff --git a/packages/website/md/docs/sol_profiler/usage.md b/packages/website/md/docs/sol_profiler/usage.md new file mode 100644 index 000000000..35ea140da --- /dev/null +++ b/packages/website/md/docs/sol_profiler/usage.md @@ -0,0 +1,62 @@ +Sol-profiler 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 `ProfilerSubprovider` 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://0x.org/wiki#Web3-Provider-Explained) wiki article. + +The ProfilerSubprovider 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. + +Profiler subprovider needs some info about your contracts (`srcMap`, `bytecode`). It gets that info from your project's artifacts. Some frameworks have their own artifact format. Some artifact formats don't actually contain all the neccessary data. + +In order to use `ProfilerSubprovider` with your favorite framework you need to pass an `artifactsAdapter` to it. + +### Sol-compiler + +If you are generating your artifacts with [@0x/sol-compiler](https://0x.org/docs/sol-compiler) you can use the `SolCompilerArtifactsAdapter` we've implemented for you. + +```typescript +import { SolCompilerArtifactsAdapter } from '@0x/sol-profiler'; +const artifactsPath = 'src/artifacts'; +const contractsPath = 'src/contracts'; +const artifactsAdapter = new SolCompilerArtifactsAdapter(artifactsPath, contractsPath); +``` + +### Truffle + +If your project is using [Truffle](https://truffleframework.com/), we've written a `TruffleArtifactsAdapter`for you. + +```typescript +import { TruffleArtifactAdapter } from '@0x/sol-profiler'; +const contractsPath = 'src/contracts'; +const artifactAdapter = new TruffleArtifactAdapter(contractsDir); +``` + +Because truffle artifacts don't have all the data we need - we actually will recompile your contracts under the hood. That's why you don't need to pass an `artifactsPath`. + +### Other framework/toolset + +You'll need to write your own artifacts adapter. It should extend `AbstractArtifactsAdapter`. +Look at the code of the two adapters above for examples. + +### Usage + +```typescript +import { ProfilerSubprovider } from '@0x/sol-profiler'; +import ProviderEngine = require('web3-provider-engine'); + +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 isVerbose = true; +const profilerSubprovider = new ProfilerSubprovider(artifactsAdapter, defaultFromAddress, isVerbose); + +provider.addProvider(profilerSubprovider); +``` + +After your test suite is complete (e.g in the Mocha global `after` hook), you'll need to call: + +```typescript +await profilerSubprovider.writeProfilerAsync(); +``` + +This will create a `profiler.json` file in a `profiler` directory. This file has an [Istanbul format](https://github.com/gotwarlost/istanbul/blob/master/profiler.json.md) - so you can use it with any of the existing Istanbul reporters. diff --git a/packages/website/md/docs/sol_trace/installation.md b/packages/website/md/docs/sol_trace/installation.md new file mode 100644 index 000000000..2f794b2f8 --- /dev/null +++ b/packages/website/md/docs/sol_trace/installation.md @@ -0,0 +1,17 @@ +**Install** + +```bash +yarn add @0x/sol-trace +``` + +**Import** + +```javascript +import { TraceSubprovider } from '@0x/sol-trace'; +``` + +or + +```javascript +var TraceSubprovider = require('@0x/sol-trace').TraceSubprovider; +``` diff --git a/packages/website/md/docs/sol_trace/introduction.md b/packages/website/md/docs/sol_trace/introduction.md new file mode 100644 index 000000000..21fea764e --- /dev/null +++ b/packages/website/md/docs/sol_trace/introduction.md @@ -0,0 +1 @@ +Welcome to the [sol-trace](https://github.com/0xProject/0x-monorepo/tree/development/packages/sol-trace) documentation! Sol-trace is a Solidity trace tool for your smart contract tests. diff --git a/packages/website/md/docs/sol_trace/usage.md b/packages/website/md/docs/sol_trace/usage.md new file mode 100644 index 000000000..f3aa6fc35 --- /dev/null +++ b/packages/website/md/docs/sol_trace/usage.md @@ -0,0 +1,62 @@ +Sol-trace 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 `TraceSubprovider` 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://0x.org/wiki#Web3-Provider-Explained) wiki article. + +The TraceSubprovider 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. + +Trace subprovider needs some info about your contracts (`srcMap`, `bytecode`). It gets that info from your project's artifacts. Some frameworks have their own artifact format. Some artifact formats don't actually contain all the neccessary data. + +In order to use `TraceSubprovider` with your favorite framework you need to pass an `artifactsAdapter` to it. + +### Sol-compiler + +If you are generating your artifacts with [@0x/sol-compiler](https://0x.org/docs/sol-compiler) you can use the `SolCompilerArtifactsAdapter` we've implemented for you. + +```typescript +import { SolCompilerArtifactsAdapter } from '@0x/sol-trace'; +const artifactsPath = 'src/artifacts'; +const contractsPath = 'src/contracts'; +const artifactsAdapter = new SolCompilerArtifactsAdapter(artifactsPath, contractsPath); +``` + +### Truffle + +If your project is using [Truffle](https://truffleframework.com/), we've written a `TruffleArtifactsAdapter`for you. + +```typescript +import { TruffleArtifactAdapter } from '@0x/sol-trace'; +const contractsPath = 'src/contracts'; +const artifactAdapter = new TruffleArtifactAdapter(contractsDir); +``` + +Because truffle artifacts don't have all the data we need - we actually will recompile your contracts under the hood. That's why you don't need to pass an `artifactsPath`. + +### Other framework/toolset + +You'll need to write your own artifacts adapter. It should extend `AbstractArtifactsAdapter`. +Look at the code of the two adapters above for examples. + +### Usage + +```typescript +import { TraceSubprovider } from '@0x/sol-trace'; +import ProviderEngine = require('web3-provider-engine'); + +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 isVerbose = true; +const traceSubprovider = new TraceSubprovider(artifactsAdapter, defaultFromAddress, isVerbose); + +provider.addProvider(traceSubprovider); +``` + +After your test suite is complete (e.g in the Mocha global `after` hook), you'll need to call: + +```typescript +await traceSubprovider.writeTraceAsync(); +``` + +This will create a `trace.json` file in a `trace` directory. This file has an [Istanbul format](https://github.com/gotwarlost/istanbul/blob/master/trace.json.md) - so you can use it with any of the existing Istanbul reporters. diff --git a/packages/website/ts/containers/asset_buyer_documentation.ts b/packages/website/ts/containers/asset_buyer_documentation.ts index c93b9332d..815176e47 100644 --- a/packages/website/ts/containers/asset_buyer_documentation.ts +++ b/packages/website/ts/containers/asset_buyer_documentation.ts @@ -1,12 +1,10 @@ -import { DocsInfo, DocsInfoConfig, SupportedDocJson } from '@0x/react-docs'; +import { DocsInfoConfig, SupportedDocJson } from '@0x/react-docs'; import * as React from 'react'; import { connect } from 'react-redux'; -import { Dispatch } from 'redux'; import { DocPage as DocPageComponent, DocPageProps } from 'ts/pages/documentation/doc_page'; -import { Dispatcher } from 'ts/redux/dispatcher'; -import { State } from 'ts/redux/reducer'; -import { DocPackages, ScreenWidths } from 'ts/types'; -import { Translate } from 'ts/utils/translate'; +import { DocPackages } from 'ts/types'; + +import { getMapStateToProps, mapDispatchToProps } from '../utils/documentation_container'; /* tslint:disable:no-var-requires */ const IntroMarkdown = require('md/docs/asset_buyer/introduction'); @@ -25,7 +23,7 @@ const docsInfoConfig: DocsInfoConfig = { packageName: '@0x/asset-buyer', type: SupportedDocJson.TypeDoc, displayName: 'AssetBuyer', - packageUrl: 'https://github.com/0xProject/0x-monorepo', + packageUrl: 'https://github.com/0xProject/0x-monorepo/packages/asset-buyer', markdownMenu: { introduction: [markdownSections.introduction], install: [markdownSections.installation], @@ -40,31 +38,7 @@ const docsInfoConfig: DocsInfoConfig = { }, markdownSections, }; -const docsInfo = new DocsInfo(docsInfoConfig); - -interface ConnectedState { - docsVersion: string; - availableDocVersions: string[]; - docsInfo: DocsInfo; - translate: Translate; - screenWidth: ScreenWidths; -} - -interface ConnectedDispatch { - dispatcher: Dispatcher; -} - -const mapStateToProps = (state: State, _ownProps: DocPageProps): ConnectedState => ({ - docsVersion: state.docsVersion, - availableDocVersions: state.availableDocVersions, - translate: state.translate, - docsInfo, - screenWidth: state.screenWidth, -}); - -const mapDispatchToProps = (dispatch: Dispatch): ConnectedDispatch => ({ - dispatcher: new Dispatcher(dispatch), -}); +const mapStateToProps = getMapStateToProps(docsInfoConfig); export const Documentation: React.ComponentClass = connect(mapStateToProps, mapDispatchToProps)( DocPageComponent, diff --git a/packages/website/ts/containers/connect_documentation.ts b/packages/website/ts/containers/connect_documentation.ts index 0f11e0809..19fd0ee56 100644 --- a/packages/website/ts/containers/connect_documentation.ts +++ b/packages/website/ts/containers/connect_documentation.ts @@ -1,12 +1,10 @@ -import { DocsInfo, DocsInfoConfig, SupportedDocJson } from '@0x/react-docs'; +import { DocsInfoConfig, SupportedDocJson } from '@0x/react-docs'; import * as React from 'react'; import { connect } from 'react-redux'; -import { Dispatch } from 'redux'; import { DocPage as DocPageComponent, DocPageProps } from 'ts/pages/documentation/doc_page'; -import { Dispatcher } from 'ts/redux/dispatcher'; -import { State } from 'ts/redux/reducer'; -import { DocPackages, ScreenWidths } from 'ts/types'; -import { Translate } from 'ts/utils/translate'; +import { DocPackages } from 'ts/types'; + +import { getMapStateToProps, mapDispatchToProps } from '../utils/documentation_container'; /* tslint:disable:no-var-requires */ const IntroMarkdown1 = require('md/docs/connect/1/introduction'); @@ -25,7 +23,7 @@ const docsInfoConfig: DocsInfoConfig = { packageName: '@0x/connect', type: SupportedDocJson.TypeDoc, displayName: '0x Connect', - packageUrl: 'https://github.com/0xProject/0x-monorepo', + packageUrl: 'https://github.com/0xProject/0x-monorepo/packages/connect', markdownMenu: { 'getting-started': [markdownSections.introduction, markdownSections.installation], }, @@ -45,31 +43,7 @@ const docsInfoConfig: DocsInfoConfig = { }, markdownSections, }; -const docsInfo = new DocsInfo(docsInfoConfig); - -interface ConnectedState { - docsVersion: string; - availableDocVersions: string[]; - docsInfo: DocsInfo; - translate: Translate; - screenWidth: ScreenWidths; -} - -interface ConnectedDispatch { - dispatcher: Dispatcher; -} - -const mapStateToProps = (state: State, _ownProps: DocPageProps): ConnectedState => ({ - docsVersion: state.docsVersion, - availableDocVersions: state.availableDocVersions, - translate: state.translate, - docsInfo, - screenWidth: state.screenWidth, -}); - -const mapDispatchToProps = (dispatch: Dispatch): ConnectedDispatch => ({ - dispatcher: new Dispatcher(dispatch), -}); +const mapStateToProps = getMapStateToProps(docsInfoConfig); export const Documentation: React.ComponentClass = connect(mapStateToProps, mapDispatchToProps)( DocPageComponent, diff --git a/packages/website/ts/containers/contract_wrappers_documentation.ts b/packages/website/ts/containers/contract_wrappers_documentation.ts index 4c05605e3..dec95ae62 100644 --- a/packages/website/ts/containers/contract_wrappers_documentation.ts +++ b/packages/website/ts/containers/contract_wrappers_documentation.ts @@ -1,12 +1,10 @@ -import { DocsInfo, DocsInfoConfig, SupportedDocJson } from '@0x/react-docs'; +import { DocsInfoConfig, SupportedDocJson } from '@0x/react-docs'; import * as React from 'react'; import { connect } from 'react-redux'; -import { Dispatch } from 'redux'; import { DocPage as DocPageComponent, DocPageProps } from 'ts/pages/documentation/doc_page'; -import { Dispatcher } from 'ts/redux/dispatcher'; -import { State } from 'ts/redux/reducer'; -import { DocPackages, ScreenWidths } from 'ts/types'; -import { Translate } from 'ts/utils/translate'; +import { DocPackages } from 'ts/types'; + +import { getMapStateToProps, mapDispatchToProps } from '../utils/documentation_container'; /* tslint:disable:no-var-requires */ const IntroMarkdown1 = require('md/docs/contract_wrappers/1/introduction'); @@ -24,7 +22,7 @@ const docsInfoConfig: DocsInfoConfig = { packageName: '@0x/contract-wrappers', type: SupportedDocJson.TypeDoc, displayName: 'Contract Wrappers', - packageUrl: 'https://github.com/0xProject/0x-monorepo', + packageUrl: 'https://github.com/0xProject/0x-monorepo/packages/contract-wrappers', markdownMenu: { 'getting-started': [markdownSections.introduction, markdownSections.installation], }, @@ -40,31 +38,7 @@ const docsInfoConfig: DocsInfoConfig = { }, markdownSections, }; -const docsInfo = new DocsInfo(docsInfoConfig); - -interface ConnectedState { - docsVersion: string; - availableDocVersions: string[]; - docsInfo: DocsInfo; - translate: Translate; - screenWidth: ScreenWidths; -} - -interface ConnectedDispatch { - dispatcher: Dispatcher; -} - -const mapStateToProps = (state: State, _ownProps: DocPageProps): ConnectedState => ({ - docsVersion: state.docsVersion, - availableDocVersions: state.availableDocVersions, - docsInfo, - translate: state.translate, - screenWidth: state.screenWidth, -}); - -const mapDispatchToProps = (dispatch: Dispatch): ConnectedDispatch => ({ - dispatcher: new Dispatcher(dispatch), -}); +const mapStateToProps = getMapStateToProps(docsInfoConfig); export const Documentation: React.ComponentClass = connect(mapStateToProps, mapDispatchToProps)( DocPageComponent, diff --git a/packages/website/ts/containers/ethereum_types_documentation.ts b/packages/website/ts/containers/ethereum_types_documentation.ts index 9d1df1d1f..9b668d515 100644 --- a/packages/website/ts/containers/ethereum_types_documentation.ts +++ b/packages/website/ts/containers/ethereum_types_documentation.ts @@ -1,12 +1,10 @@ -import { constants as docConstants, DocsInfo, DocsInfoConfig, SupportedDocJson } from '@0x/react-docs'; +import { constants as docConstants, DocsInfoConfig, SupportedDocJson } from '@0x/react-docs'; import * as React from 'react'; import { connect } from 'react-redux'; -import { Dispatch } from 'redux'; import { DocPage as DocPageComponent, DocPageProps } from 'ts/pages/documentation/doc_page'; -import { Dispatcher } from 'ts/redux/dispatcher'; -import { State } from 'ts/redux/reducer'; -import { DocPackages, ScreenWidths } from 'ts/types'; -import { Translate } from 'ts/utils/translate'; +import { DocPackages } from 'ts/types'; + +import { getMapStateToProps, mapDispatchToProps } from '../utils/documentation_container'; /* tslint:disable:no-var-requires */ const IntroMarkdown = require('md/docs/ethereum_types/introduction'); @@ -36,31 +34,7 @@ const docsInfoConfig: DocsInfoConfig = { }, markdownSections, }; -const docsInfo = new DocsInfo(docsInfoConfig); - -interface ConnectedState { - docsVersion: string; - availableDocVersions: string[]; - docsInfo: DocsInfo; - translate: Translate; - screenWidth: ScreenWidths; -} - -interface ConnectedDispatch { - dispatcher: Dispatcher; -} - -const mapStateToProps = (state: State, _ownProps: DocPageProps): ConnectedState => ({ - docsVersion: state.docsVersion, - availableDocVersions: state.availableDocVersions, - translate: state.translate, - docsInfo, - screenWidth: state.screenWidth, -}); - -const mapDispatchToProps = (dispatch: Dispatch): ConnectedDispatch => ({ - dispatcher: new Dispatcher(dispatch), -}); +const mapStateToProps = getMapStateToProps(docsInfoConfig); export const Documentation: React.ComponentClass = connect(mapStateToProps, mapDispatchToProps)( DocPageComponent, diff --git a/packages/website/ts/containers/json_schemas_documentation.ts b/packages/website/ts/containers/json_schemas_documentation.ts index 9c4bb8e26..3862ba878 100644 --- a/packages/website/ts/containers/json_schemas_documentation.ts +++ b/packages/website/ts/containers/json_schemas_documentation.ts @@ -1,12 +1,10 @@ -import { DocsInfo, DocsInfoConfig, SupportedDocJson } from '@0x/react-docs'; +import { DocsInfoConfig, SupportedDocJson } from '@0x/react-docs'; import * as React from 'react'; import { connect } from 'react-redux'; -import { Dispatch } from 'redux'; import { DocPage as DocPageComponent, DocPageProps } from 'ts/pages/documentation/doc_page'; -import { Dispatcher } from 'ts/redux/dispatcher'; -import { State } from 'ts/redux/reducer'; -import { DocPackages, ScreenWidths } from 'ts/types'; -import { Translate } from 'ts/utils/translate'; +import { DocPackages } from 'ts/types'; + +import { getMapStateToProps, mapDispatchToProps } from '../utils/documentation_container'; /* tslint:disable:no-var-requires */ const IntroMarkdown1 = require('md/docs/json_schemas/1/introduction'); @@ -32,7 +30,7 @@ const docsInfoConfig: DocsInfoConfig = { packageName: '@0x/json-schemas', type: SupportedDocJson.TypeDoc, displayName: 'JSON Schemas', - packageUrl: 'https://github.com/0xProject/0x-monorepo', + packageUrl: 'https://github.com/0xProject/0x-monorepo/packages/json-schemas', markdownMenu: { 'getting-started': [markdownSections.introduction, markdownSections.installation, markdownSections.usage], schemas: [markdownSections.schemas], @@ -65,31 +63,7 @@ const docsInfoConfig: DocsInfoConfig = { }, markdownSections, }; -const docsInfo = new DocsInfo(docsInfoConfig); - -interface ConnectedState { - docsVersion: string; - availableDocVersions: string[]; - docsInfo: DocsInfo; - translate: Translate; - screenWidth: ScreenWidths; -} - -interface ConnectedDispatch { - dispatcher: Dispatcher; -} - -const mapStateToProps = (state: State, _ownProps: DocPageProps): ConnectedState => ({ - docsVersion: state.docsVersion, - availableDocVersions: state.availableDocVersions, - translate: state.translate, - docsInfo, - screenWidth: state.screenWidth, -}); - -const mapDispatchToProps = (dispatch: Dispatch): ConnectedDispatch => ({ - dispatcher: new Dispatcher(dispatch), -}); +const mapStateToProps = getMapStateToProps(docsInfoConfig); export const Documentation: React.ComponentClass = connect(mapStateToProps, mapDispatchToProps)( DocPageComponent, diff --git a/packages/website/ts/containers/migrations_documentation.ts b/packages/website/ts/containers/migrations_documentation.ts index 02919e06e..dd5db3ac6 100644 --- a/packages/website/ts/containers/migrations_documentation.ts +++ b/packages/website/ts/containers/migrations_documentation.ts @@ -23,7 +23,7 @@ const docsInfoConfig: DocsInfoConfig = { packageName: '@0x/migrations', type: SupportedDocJson.TypeDoc, displayName: 'Migrations', - packageUrl: 'https://github.com/0xProject/0x-monorepo', + packageUrl: 'https://github.com/0xProject/0x-monorepo/packages/migrations', markdownMenu: { 'getting-started': [markdownSections.introduction, markdownSections.installation], }, diff --git a/packages/website/ts/containers/order_utils_documentation.ts b/packages/website/ts/containers/order_utils_documentation.ts index f1d794988..4996c329a 100644 --- a/packages/website/ts/containers/order_utils_documentation.ts +++ b/packages/website/ts/containers/order_utils_documentation.ts @@ -1,12 +1,10 @@ -import { DocsInfo, DocsInfoConfig, SupportedDocJson } from '@0x/react-docs'; +import { DocsInfoConfig, SupportedDocJson } from '@0x/react-docs'; import * as React from 'react'; import { connect } from 'react-redux'; -import { Dispatch } from 'redux'; import { DocPage as DocPageComponent, DocPageProps } from 'ts/pages/documentation/doc_page'; -import { Dispatcher } from 'ts/redux/dispatcher'; -import { State } from 'ts/redux/reducer'; -import { DocPackages, ScreenWidths } from 'ts/types'; -import { Translate } from 'ts/utils/translate'; +import { DocPackages } from 'ts/types'; + +import { getMapStateToProps, mapDispatchToProps } from '../utils/documentation_container'; /* tslint:disable:no-var-requires */ const IntroMarkdown1 = require('md/docs/order_utils/1/introduction'); @@ -25,7 +23,7 @@ const docsInfoConfig: DocsInfoConfig = { packageName: '@0x/order-utils', type: SupportedDocJson.TypeDoc, displayName: 'Order utils', - packageUrl: 'https://github.com/0xProject/0x-monorepo', + packageUrl: 'https://github.com/0xProject/0x-monorepo/packages/order-utils', markdownMenu: { 'getting-started': [markdownSections.introduction, markdownSections.installation], }, @@ -41,31 +39,7 @@ const docsInfoConfig: DocsInfoConfig = { }, markdownSections, }; -const docsInfo = new DocsInfo(docsInfoConfig); - -interface ConnectedState { - docsVersion: string; - availableDocVersions: string[]; - docsInfo: DocsInfo; - translate: Translate; - screenWidth: ScreenWidths; -} - -interface ConnectedDispatch { - dispatcher: Dispatcher; -} - -const mapStateToProps = (state: State, _ownProps: DocPageProps): ConnectedState => ({ - docsVersion: state.docsVersion, - availableDocVersions: state.availableDocVersions, - translate: state.translate, - docsInfo, - screenWidth: state.screenWidth, -}); - -const mapDispatchToProps = (dispatch: Dispatch): ConnectedDispatch => ({ - dispatcher: new Dispatcher(dispatch), -}); +const mapStateToProps = getMapStateToProps(docsInfoConfig); export const Documentation: React.ComponentClass = connect(mapStateToProps, mapDispatchToProps)( DocPageComponent, diff --git a/packages/website/ts/containers/order_watcher_documentation.ts b/packages/website/ts/containers/order_watcher_documentation.ts index 683e1fe9f..f3d3bada8 100644 --- a/packages/website/ts/containers/order_watcher_documentation.ts +++ b/packages/website/ts/containers/order_watcher_documentation.ts @@ -1,12 +1,10 @@ -import { DocsInfo, DocsInfoConfig, SupportedDocJson } from '@0x/react-docs'; +import { DocsInfoConfig, SupportedDocJson } from '@0x/react-docs'; import * as React from 'react'; import { connect } from 'react-redux'; -import { Dispatch } from 'redux'; import { DocPage as DocPageComponent, DocPageProps } from 'ts/pages/documentation/doc_page'; -import { Dispatcher } from 'ts/redux/dispatcher'; -import { State } from 'ts/redux/reducer'; -import { DocPackages, ScreenWidths } from 'ts/types'; -import { Translate } from 'ts/utils/translate'; +import { DocPackages } from 'ts/types'; + +import { getMapStateToProps, mapDispatchToProps } from '../utils/documentation_container'; /* tslint:disable:no-var-requires */ const IntroMarkdown1 = require('md/docs/order_watcher/1/introduction'); @@ -25,7 +23,7 @@ const docsInfoConfig: DocsInfoConfig = { packageName: '@0x/order-watcher', type: SupportedDocJson.TypeDoc, displayName: 'Order Watcher', - packageUrl: 'https://github.com/0xProject/0x-monorepo', + packageUrl: 'https://github.com/0xProject/0x-monorepo/packages/order-watcher', markdownMenu: { 'getting-started': [markdownSections.introduction, markdownSections.installation], }, @@ -41,31 +39,7 @@ const docsInfoConfig: DocsInfoConfig = { }, markdownSections, }; -const docsInfo = new DocsInfo(docsInfoConfig); - -interface ConnectedState { - docsVersion: string; - availableDocVersions: string[]; - docsInfo: DocsInfo; - translate: Translate; - screenWidth: ScreenWidths; -} - -interface ConnectedDispatch { - dispatcher: Dispatcher; -} - -const mapStateToProps = (state: State, _ownProps: DocPageProps): ConnectedState => ({ - docsVersion: state.docsVersion, - availableDocVersions: state.availableDocVersions, - translate: state.translate, - docsInfo, - screenWidth: state.screenWidth, -}); - -const mapDispatchToProps = (dispatch: Dispatch): ConnectedDispatch => ({ - dispatcher: new Dispatcher(dispatch), -}); +const mapStateToProps = getMapStateToProps(docsInfoConfig); export const Documentation: React.ComponentClass = connect(mapStateToProps, mapDispatchToProps)( DocPageComponent, diff --git a/packages/website/ts/containers/smart_contracts_documentation.ts b/packages/website/ts/containers/smart_contracts_documentation.ts index 57c98fa3b..7c0de5515 100644 --- a/packages/website/ts/containers/smart_contracts_documentation.ts +++ b/packages/website/ts/containers/smart_contracts_documentation.ts @@ -1,13 +1,11 @@ -import { DocsInfo, DocsInfoConfig, SupportedDocJson } from '@0x/react-docs'; +import { DocsInfoConfig, SupportedDocJson } from '@0x/react-docs'; import { Networks } from '@0x/react-shared'; import * as React from 'react'; import { connect } from 'react-redux'; -import { Dispatch } from 'redux'; import { DocPage as DocPageComponent, DocPageProps } from 'ts/pages/documentation/doc_page'; -import { Dispatcher } from 'ts/redux/dispatcher'; -import { State } from 'ts/redux/reducer'; -import { DocPackages, ScreenWidths, SmartContractDocSections as Sections } from 'ts/types'; -import { Translate } from 'ts/utils/translate'; +import { DocPackages, SmartContractDocSections as Sections } from 'ts/types'; + +import { getMapStateToProps, mapDispatchToProps } from '../utils/documentation_container'; /* tslint:disable:no-var-requires */ const IntroMarkdown1 = require('md/docs/smart_contracts/1/introduction'); @@ -91,31 +89,7 @@ const docsInfoConfig: DocsInfoConfig = { }, }, }; -const docsInfo = new DocsInfo(docsInfoConfig); - -interface ConnectedState { - docsVersion: string; - availableDocVersions: string[]; - translate: Translate; - screenWidth: ScreenWidths; -} - -interface ConnectedDispatch { - dispatcher: Dispatcher; - docsInfo: DocsInfo; -} - -const mapStateToProps = (state: State, _ownProps: DocPageProps): ConnectedState => ({ - docsVersion: state.docsVersion, - availableDocVersions: state.availableDocVersions, - translate: state.translate, - screenWidth: state.screenWidth, -}); - -const mapDispatchToProps = (dispatch: Dispatch): ConnectedDispatch => ({ - dispatcher: new Dispatcher(dispatch), - docsInfo, -}); +const mapStateToProps = getMapStateToProps(docsInfoConfig); export const Documentation: React.ComponentClass = connect(mapStateToProps, mapDispatchToProps)( DocPageComponent, diff --git a/packages/website/ts/containers/sol_compiler_documentation.ts b/packages/website/ts/containers/sol_compiler_documentation.ts index f3a793e80..e899e9627 100644 --- a/packages/website/ts/containers/sol_compiler_documentation.ts +++ b/packages/website/ts/containers/sol_compiler_documentation.ts @@ -1,12 +1,10 @@ -import { DocsInfo, DocsInfoConfig, SupportedDocJson } from '@0x/react-docs'; +import { DocsInfoConfig, SupportedDocJson } from '@0x/react-docs'; import * as React from 'react'; import { connect } from 'react-redux'; -import { Dispatch } from 'redux'; import { DocPage as DocPageComponent, DocPageProps } from 'ts/pages/documentation/doc_page'; -import { Dispatcher } from 'ts/redux/dispatcher'; -import { State } from 'ts/redux/reducer'; -import { DocPackages, ScreenWidths } from 'ts/types'; -import { Translate } from 'ts/utils/translate'; +import { DocPackages } from 'ts/types'; + +import { getMapStateToProps, mapDispatchToProps } from '../utils/documentation_container'; /* tslint:disable:no-var-requires */ const IntroMarkdown1 = require('md/docs/sol-compiler/1/introduction'); @@ -27,7 +25,7 @@ const docsInfoConfig: DocsInfoConfig = { packageName: '@0x/sol-compiler', type: SupportedDocJson.TypeDoc, displayName: 'Solidity Compiler', - packageUrl: 'https://github.com/0xProject/0x-monorepo', + packageUrl: 'https://github.com/0xProject/0x-monorepo/packages/sol-compiler', markdownMenu: { 'getting-started': [markdownSections.introduction, markdownSections.installation, markdownSections.usage], }, @@ -45,31 +43,7 @@ const docsInfoConfig: DocsInfoConfig = { }, markdownSections, }; -const docsInfo = new DocsInfo(docsInfoConfig); - -interface ConnectedState { - docsVersion: string; - availableDocVersions: string[]; - docsInfo: DocsInfo; - translate: Translate; - screenWidth: ScreenWidths; -} - -interface ConnectedDispatch { - dispatcher: Dispatcher; -} - -const mapStateToProps = (state: State, _ownProps: DocPageProps): ConnectedState => ({ - docsVersion: state.docsVersion, - availableDocVersions: state.availableDocVersions, - translate: state.translate, - docsInfo, - screenWidth: state.screenWidth, -}); - -const mapDispatchToProps = (dispatch: Dispatch): ConnectedDispatch => ({ - dispatcher: new Dispatcher(dispatch), -}); +const mapStateToProps = getMapStateToProps(docsInfoConfig); export const Documentation: React.ComponentClass = connect(mapStateToProps, mapDispatchToProps)( DocPageComponent, diff --git a/packages/website/ts/containers/sol_cov_documentation.ts b/packages/website/ts/containers/sol_cov_documentation.ts deleted file mode 100644 index 8944ec70a..000000000 --- a/packages/website/ts/containers/sol_cov_documentation.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { DocsInfo, DocsInfoConfig, SupportedDocJson } from '@0x/react-docs'; -import * as React from 'react'; -import { connect } from 'react-redux'; -import { Dispatch } from 'redux'; -import { DocPage as DocPageComponent, DocPageProps } from 'ts/pages/documentation/doc_page'; -import { Dispatcher } from 'ts/redux/dispatcher'; -import { State } from 'ts/redux/reducer'; -import { DocPackages, ScreenWidths } from 'ts/types'; -import { Translate } from 'ts/utils/translate'; - -/* tslint:disable:no-var-requires */ -const IntroMarkdown1 = require('md/docs/sol_cov/1/introduction'); -const InstallationMarkdown1 = require('md/docs/sol_cov/1/installation'); -const UsageMarkdown1 = require('md/docs/sol_cov/1/usage'); -const IntroMarkdown2 = require('md/docs/sol_cov/2/introduction'); -const InstallationMarkdown2 = require('md/docs/sol_cov/2/installation'); -const UsageMarkdown2 = require('md/docs/sol_cov/2/usage'); -/* tslint:enable:no-var-requires */ - -const markdownSections = { - introduction: 'introduction', - installation: 'installation', - usage: 'usage', -}; - -const docsInfoConfig: DocsInfoConfig = { - id: DocPackages.SolCov, - packageName: '@0x/sol-cov', - type: SupportedDocJson.TypeDoc, - displayName: 'Sol-cov', - packageUrl: 'https://github.com/0xProject/0x-monorepo', - markdownMenu: { - 'getting-started': [markdownSections.introduction, markdownSections.installation, markdownSections.usage], - }, - sectionNameToMarkdownByVersion: { - '0.0.1': { - [markdownSections.introduction]: IntroMarkdown1, - [markdownSections.installation]: InstallationMarkdown1, - [markdownSections.usage]: UsageMarkdown1, - }, - '2.1.8': { - [markdownSections.introduction]: IntroMarkdown2, - [markdownSections.installation]: InstallationMarkdown2, - [markdownSections.usage]: UsageMarkdown2, - }, - }, - markdownSections, -}; -const docsInfo = new DocsInfo(docsInfoConfig); - -interface ConnectedState { - docsVersion: string; - availableDocVersions: string[]; - docsInfo: DocsInfo; - translate: Translate; - screenWidth: ScreenWidths; -} - -interface ConnectedDispatch { - dispatcher: Dispatcher; -} - -const mapStateToProps = (state: State, _ownProps: DocPageProps): ConnectedState => ({ - docsVersion: state.docsVersion, - availableDocVersions: state.availableDocVersions, - translate: state.translate, - docsInfo, - screenWidth: state.screenWidth, -}); - -const mapDispatchToProps = (dispatch: Dispatch): ConnectedDispatch => ({ - dispatcher: new Dispatcher(dispatch), -}); - -export const Documentation: React.ComponentClass = connect(mapStateToProps, mapDispatchToProps)( - DocPageComponent, -); diff --git a/packages/website/ts/containers/sol_coverage_documentation.ts b/packages/website/ts/containers/sol_coverage_documentation.ts new file mode 100644 index 000000000..25d67e2d0 --- /dev/null +++ b/packages/website/ts/containers/sol_coverage_documentation.ts @@ -0,0 +1,43 @@ +import { DocsInfoConfig, SupportedDocJson } from '@0x/react-docs'; +import * as React from 'react'; +import { connect } from 'react-redux'; +import { DocPage as DocPageComponent, DocPageProps } from 'ts/pages/documentation/doc_page'; +import { DocPackages } from 'ts/types'; + +import { getMapStateToProps, mapDispatchToProps } from '../utils/documentation_container'; + +/* tslint:disable:no-var-requires */ +const IntroMarkdown = require('md/docs/sol_coverage/introduction'); +const InstallationMarkdown = require('md/docs/sol_coverage/installation'); +const UsageMarkdown = require('md/docs/sol_coverage/usage'); +/* tslint:enable:no-var-requires */ + +const markdownSections = { + introduction: 'introduction', + installation: 'installation', + usage: 'usage', +}; + +const docsInfoConfig: DocsInfoConfig = { + id: DocPackages.SolCoverage, + packageName: '@0x/sol-coverage', + type: SupportedDocJson.TypeDoc, + displayName: 'Sol-coverage', + packageUrl: 'https://github.com/0xProject/0x-monorepo/packages/sol-coverage', + markdownMenu: { + 'getting-started': [markdownSections.introduction, markdownSections.installation, markdownSections.usage], + }, + sectionNameToMarkdownByVersion: { + '1.0.0': { + [markdownSections.introduction]: IntroMarkdown, + [markdownSections.installation]: InstallationMarkdown, + [markdownSections.usage]: UsageMarkdown, + }, + }, + markdownSections, +}; +const mapStateToProps = getMapStateToProps(docsInfoConfig); + +export const Documentation: React.ComponentClass = connect(mapStateToProps, mapDispatchToProps)( + DocPageComponent, +); diff --git a/packages/website/ts/containers/sol_profiler_documentation.ts b/packages/website/ts/containers/sol_profiler_documentation.ts new file mode 100644 index 000000000..14839831e --- /dev/null +++ b/packages/website/ts/containers/sol_profiler_documentation.ts @@ -0,0 +1,43 @@ +import { DocsInfoConfig, SupportedDocJson } from '@0x/react-docs'; +import * as React from 'react'; +import { connect } from 'react-redux'; +import { DocPage as DocPageComponent, DocPageProps } from 'ts/pages/documentation/doc_page'; +import { DocPackages } from 'ts/types'; + +import { getMapStateToProps, mapDispatchToProps } from '../utils/documentation_container'; + +/* tslint:disable:no-var-requires */ +const IntroMarkdown = require('md/docs/sol_profiler/introduction'); +const InstallationMarkdown = require('md/docs/sol_profiler/installation'); +const UsageMarkdown = require('md/docs/sol_profiler/usage'); +/* tslint:enable:no-var-requires */ + +const markdownSections = { + introduction: 'introduction', + installation: 'installation', + usage: 'usage', +}; + +const docsInfoConfig: DocsInfoConfig = { + id: DocPackages.SolProfiler, + packageName: '@0x/sol-profiler', + type: SupportedDocJson.TypeDoc, + displayName: 'Sol-profiler', + packageUrl: 'https://github.com/0xProject/0x-monorepo/packages/sol-profiler', + markdownMenu: { + 'getting-started': [markdownSections.introduction, markdownSections.installation, markdownSections.usage], + }, + sectionNameToMarkdownByVersion: { + '1.0.0': { + [markdownSections.introduction]: IntroMarkdown, + [markdownSections.installation]: InstallationMarkdown, + [markdownSections.usage]: UsageMarkdown, + }, + }, + markdownSections, +}; +const mapStateToProps = getMapStateToProps(docsInfoConfig); + +export const Documentation: React.ComponentClass = connect(mapStateToProps, mapDispatchToProps)( + DocPageComponent, +); diff --git a/packages/website/ts/containers/sol_trace_documentation.ts b/packages/website/ts/containers/sol_trace_documentation.ts new file mode 100644 index 000000000..c5286f1ff --- /dev/null +++ b/packages/website/ts/containers/sol_trace_documentation.ts @@ -0,0 +1,43 @@ +import { DocsInfoConfig, SupportedDocJson } from '@0x/react-docs'; +import * as React from 'react'; +import { connect } from 'react-redux'; +import { DocPage as DocPageComponent, DocPageProps } from 'ts/pages/documentation/doc_page'; +import { DocPackages } from 'ts/types'; + +import { getMapStateToProps, mapDispatchToProps } from '../utils/documentation_container'; + +/* tslint:disable:no-var-requires */ +const IntroMarkdown = require('md/docs/sol_trace/introduction'); +const InstallationMarkdown = require('md/docs/sol_trace/installation'); +const UsageMarkdown = require('md/docs/sol_trace/usage'); +/* tslint:enable:no-var-requires */ + +const markdownSections = { + introduction: 'introduction', + installation: 'installation', + usage: 'usage', +}; + +const docsInfoConfig: DocsInfoConfig = { + id: DocPackages.SolTrace, + packageName: '@0x/sol-trace', + type: SupportedDocJson.TypeDoc, + displayName: 'Sol-trace', + packageUrl: 'https://github.com/0xProject/0x-monorepo/packages/sol-trace', + markdownMenu: { + 'getting-started': [markdownSections.introduction, markdownSections.installation, markdownSections.usage], + }, + sectionNameToMarkdownByVersion: { + '1.0.0': { + [markdownSections.introduction]: IntroMarkdown, + [markdownSections.installation]: InstallationMarkdown, + [markdownSections.usage]: UsageMarkdown, + }, + }, + markdownSections, +}; +const mapStateToProps = getMapStateToProps(docsInfoConfig); + +export const Documentation: React.ComponentClass = connect(mapStateToProps, mapDispatchToProps)( + DocPageComponent, +); diff --git a/packages/website/ts/containers/subproviders_documentation.ts b/packages/website/ts/containers/subproviders_documentation.ts index 0e421777b..c7fb807a6 100644 --- a/packages/website/ts/containers/subproviders_documentation.ts +++ b/packages/website/ts/containers/subproviders_documentation.ts @@ -1,12 +1,10 @@ -import { DocsInfo, DocsInfoConfig, SupportedDocJson } from '@0x/react-docs'; +import { DocsInfoConfig, SupportedDocJson } from '@0x/react-docs'; import * as React from 'react'; import { connect } from 'react-redux'; -import { Dispatch } from 'redux'; import { DocPage as DocPageComponent, DocPageProps } from 'ts/pages/documentation/doc_page'; -import { Dispatcher } from 'ts/redux/dispatcher'; -import { State } from 'ts/redux/reducer'; -import { DocPackages, ScreenWidths } from 'ts/types'; -import { Translate } from 'ts/utils/translate'; +import { DocPackages } from 'ts/types'; + +import { getMapStateToProps, mapDispatchToProps } from '../utils/documentation_container'; /* tslint:disable:no-var-requires */ const IntroMarkdown1 = require('md/docs/subproviders/1/introduction'); @@ -26,7 +24,7 @@ const docsInfoConfig: DocsInfoConfig = { packageName: '@0x/subproviders', type: SupportedDocJson.TypeDoc, displayName: 'Subproviders', - packageUrl: 'https://github.com/0xProject/0x-monorepo', + packageUrl: 'https://github.com/0xProject/0x-monorepo/packages/subproviders', markdownMenu: { 'getting-started': [docSections.introduction, docSections.installation, docSections.ledgerNodeHid], }, @@ -44,31 +42,7 @@ const docsInfoConfig: DocsInfoConfig = { }, markdownSections: docSections, }; -const docsInfo = new DocsInfo(docsInfoConfig); - -interface ConnectedState { - docsVersion: string; - availableDocVersions: string[]; - docsInfo: DocsInfo; - translate: Translate; - screenWidth: ScreenWidths; -} - -interface ConnectedDispatch { - dispatcher: Dispatcher; -} - -const mapStateToProps = (state: State, _ownProps: DocPageProps): ConnectedState => ({ - docsVersion: state.docsVersion, - availableDocVersions: state.availableDocVersions, - translate: state.translate, - docsInfo, - screenWidth: state.screenWidth, -}); - -const mapDispatchToProps = (dispatch: Dispatch): ConnectedDispatch => ({ - dispatcher: new Dispatcher(dispatch), -}); +const mapStateToProps = getMapStateToProps(docsInfoConfig); export const Documentation: React.ComponentClass = connect(mapStateToProps, mapDispatchToProps)( DocPageComponent, diff --git a/packages/website/ts/containers/web3_wrapper_documentation.ts b/packages/website/ts/containers/web3_wrapper_documentation.ts index 9c8c34621..ad286da1a 100644 --- a/packages/website/ts/containers/web3_wrapper_documentation.ts +++ b/packages/website/ts/containers/web3_wrapper_documentation.ts @@ -1,12 +1,10 @@ -import { DocsInfo, DocsInfoConfig, SupportedDocJson } from '@0x/react-docs'; +import { DocsInfoConfig, SupportedDocJson } from '@0x/react-docs'; import * as React from 'react'; import { connect } from 'react-redux'; -import { Dispatch } from 'redux'; import { DocPage as DocPageComponent, DocPageProps } from 'ts/pages/documentation/doc_page'; -import { Dispatcher } from 'ts/redux/dispatcher'; -import { State } from 'ts/redux/reducer'; -import { DocPackages, ScreenWidths } from 'ts/types'; -import { Translate } from 'ts/utils/translate'; +import { DocPackages } from 'ts/types'; + +import { getMapStateToProps, mapDispatchToProps } from '../utils/documentation_container'; /* tslint:disable:no-var-requires */ const IntroMarkdown1 = require('md/docs/web3_wrapper/1/introduction'); @@ -24,7 +22,7 @@ const docsInfoConfig: DocsInfoConfig = { packageName: '@0x/web3-wrapper', type: SupportedDocJson.TypeDoc, displayName: 'Web3Wrapper', - packageUrl: 'https://github.com/0xProject/0x-monorepo', + packageUrl: 'https://github.com/0xProject/0x-monorepo/packages/web3-wrapper', markdownMenu: { 'getting-started': [markdownSections.introduction, markdownSections.installation], }, @@ -40,31 +38,7 @@ const docsInfoConfig: DocsInfoConfig = { }, markdownSections, }; -const docsInfo = new DocsInfo(docsInfoConfig); - -interface ConnectedState { - docsVersion: string; - availableDocVersions: string[]; - docsInfo: DocsInfo; - translate: Translate; - screenWidth: ScreenWidths; -} - -interface ConnectedDispatch { - dispatcher: Dispatcher; -} - -const mapStateToProps = (state: State, _ownProps: DocPageProps): ConnectedState => ({ - docsVersion: state.docsVersion, - availableDocVersions: state.availableDocVersions, - translate: state.translate, - docsInfo, - screenWidth: state.screenWidth, -}); - -const mapDispatchToProps = (dispatch: Dispatch): ConnectedDispatch => ({ - dispatcher: new Dispatcher(dispatch), -}); +const mapStateToProps = getMapStateToProps(docsInfoConfig); export const Documentation: React.ComponentClass = connect(mapStateToProps, mapDispatchToProps)( DocPageComponent, diff --git a/packages/website/ts/containers/zero_ex_js_documentation.ts b/packages/website/ts/containers/zero_ex_js_documentation.ts index e0ea6e275..5da8d5e52 100644 --- a/packages/website/ts/containers/zero_ex_js_documentation.ts +++ b/packages/website/ts/containers/zero_ex_js_documentation.ts @@ -1,12 +1,10 @@ -import { DocsInfo, DocsInfoConfig, SupportedDocJson } from '@0x/react-docs'; +import { DocsInfoConfig, SupportedDocJson } from '@0x/react-docs'; import * as React from 'react'; import { connect } from 'react-redux'; -import { Dispatch } from 'redux'; import { DocPage as DocPageComponent, DocPageProps } from 'ts/pages/documentation/doc_page'; -import { Dispatcher } from 'ts/redux/dispatcher'; -import { State } from 'ts/redux/reducer'; -import { DocPackages, ScreenWidths } from 'ts/types'; -import { Translate } from 'ts/utils/translate'; +import { DocPackages } from 'ts/types'; + +import { getMapStateToProps, mapDispatchToProps } from '../utils/documentation_container'; /* tslint:disable:no-var-requires */ const IntroMarkdownV0 = require('md/docs/0xjs/0.0.1/introduction'); @@ -37,7 +35,7 @@ const docsInfoConfig: DocsInfoConfig = { packageName: '0x.js', type: SupportedDocJson.TypeDoc, displayName: '0x.js', - packageUrl: 'https://github.com/0xProject/0x-monorepo', + packageUrl: 'https://github.com/0xProject/0x-monorepo/packages/0x.js', markdownMenu: { 'getting-started': [ markdownSections.introduction, @@ -72,31 +70,7 @@ const docsInfoConfig: DocsInfoConfig = { }, markdownSections, }; -const docsInfo = new DocsInfo(docsInfoConfig); - -interface ConnectedState { - docsVersion: string; - availableDocVersions: string[]; - docsInfo: DocsInfo; - translate: Translate; - screenWidth: ScreenWidths; -} - -interface ConnectedDispatch { - dispatcher: Dispatcher; -} - -const mapStateToProps = (state: State, _ownProps: DocPageProps): ConnectedState => ({ - docsVersion: state.docsVersion, - availableDocVersions: state.availableDocVersions, - docsInfo, - translate: state.translate, - screenWidth: state.screenWidth, -}); - -const mapDispatchToProps = (dispatch: Dispatch): ConnectedDispatch => ({ - dispatcher: new Dispatcher(dispatch), -}); +const mapStateToProps = getMapStateToProps(docsInfoConfig); export const Documentation: React.ComponentClass = connect(mapStateToProps, mapDispatchToProps)( DocPageComponent, diff --git a/packages/website/ts/index.tsx b/packages/website/ts/index.tsx index 45054772c..4ed66c572 100644 --- a/packages/website/ts/index.tsx +++ b/packages/website/ts/index.tsx @@ -69,8 +69,14 @@ const LazySolCompilerDocumentation = createLazyComponent('Documentation', async const LazyJSONSchemasDocumentation = createLazyComponent('Documentation', async () => import(/* webpackChunkName: "jsonSchemasDocs" */ 'ts/containers/json_schemas_documentation'), ); -const LazySolCovDocumentation = createLazyComponent('Documentation', async () => - import(/* webpackChunkName: "solCovDocs" */ 'ts/containers/sol_cov_documentation'), +const LazySolCoverageDocumentation = createLazyComponent('Documentation', async () => + import(/* webpackChunkName: "solCoverageDocs" */ 'ts/containers/sol_coverage_documentation'), +); +const LazySolTraceDocumentation = createLazyComponent('Documentation', async () => + import(/* webpackChunkName: "solTraceDocs" */ 'ts/containers/sol_trace_documentation'), +); +const LazySolProfilerDocumentation = createLazyComponent('Documentation', async () => + import(/* webpackChunkName: "solProfilerDocs" */ 'ts/containers/sol_profiler_documentation'), ); const LazySubprovidersDocumentation = createLazyComponent('Documentation', async () => import(/* webpackChunkName: "subproviderDocs" */ 'ts/containers/subproviders_documentation'), @@ -149,7 +155,18 @@ render( path={`${WebsitePaths.SolCompiler}/:version?`} component={LazySolCompilerDocumentation} /> - + + + = { }, { description: - 'A Solidity code coverage tool. Sol-cov uses transaction traces to figure out which lines of your code has been covered by your tests.', + 'A Solidity code coverage tool. Sol-coverage uses transaction traces to figure out which lines of your code has been covered by your tests.', link: { - title: '@0x/sol-cov', - to: WebsitePaths.SolCov, + title: '@0x/sol-coverage', + to: WebsitePaths.SolCoverage, + }, + }, + { + description: + 'A Solidity profiler. Sol-profiler uses transaction traces to figure out line-by-line gas consumption.', + link: { + title: '@0x/sol-profiler', + to: WebsitePaths.SolProfiler, + }, + }, + { + description: + 'A Solidity revert trace tool. Sol-trace prints human-readable revert trace whenever the revert happens.', + link: { + title: '@0x/sol-trace', + to: WebsitePaths.SolTrace, }, }, { diff --git a/packages/website/ts/types.ts b/packages/website/ts/types.ts index 37bd73063..50114e2d6 100644 --- a/packages/website/ts/types.ts +++ b/packages/website/ts/types.ts @@ -370,7 +370,9 @@ export enum WebsitePaths { OrderWatcher = '/docs/order-watcher', SolCompiler = '/docs/sol-compiler', JSONSchemas = '/docs/json-schemas', - SolCov = '/docs/sol-cov', + SolCoverage = '/docs/sol-coverage', + SolProfiler = '/docs/sol-profiler', + SolTrace = '/docs/sol-trace', Subproviders = '/docs/subproviders', OrderUtils = '/docs/order-utils', EthereumTypes = '/docs/ethereum-types', @@ -386,7 +388,9 @@ export enum DocPackages { Web3Wrapper = 'WEB3_WRAPPER', SolCompiler = 'SOL_COMPILER', JSONSchemas = 'JSON_SCHEMAS', - SolCov = 'SOL_COV', + SolCoverage = 'SOL_COVERAGE', + SolTrace = 'SOL_TRACE', + SolProfiler = 'SOL_PROFILER', Subproviders = 'SUBPROVIDERS', OrderUtils = 'ORDER_UTILS', EthereumTypes = 'ETHEREUM_TYPES', diff --git a/packages/website/ts/utils/documentation_container.ts b/packages/website/ts/utils/documentation_container.ts new file mode 100644 index 000000000..54e8a2c1a --- /dev/null +++ b/packages/website/ts/utils/documentation_container.ts @@ -0,0 +1,35 @@ +import { DocsInfo, DocsInfoConfig } from '@0x/react-docs'; +import { Dispatch } from 'redux'; +import { DocPageProps } from 'ts/pages/documentation/doc_page'; +import { Dispatcher } from 'ts/redux/dispatcher'; +import { State } from 'ts/redux/reducer'; +import { ScreenWidths } from 'ts/types'; +import { Translate } from 'ts/utils/translate'; + +export interface ConnectedState { + docsVersion: string; + availableDocVersions: string[]; + docsInfo: DocsInfo; + translate: Translate; + screenWidth: ScreenWidths; +} + +export interface ConnectedDispatch { + dispatcher: Dispatcher; +} + +export const mapDispatchToProps = (dispatch: Dispatch): ConnectedDispatch => ({ + dispatcher: new Dispatcher(dispatch), +}); + +export const getMapStateToProps = (docsInfoConfig: DocsInfoConfig) => { + const docsInfo = new DocsInfo(docsInfoConfig); + const mapStateToProps = (state: State, _ownProps: DocPageProps): ConnectedState => ({ + docsVersion: state.docsVersion, + availableDocVersions: state.availableDocVersions, + translate: state.translate, + docsInfo, + screenWidth: state.screenWidth, + }); + return mapStateToProps; +}; -- cgit v1.2.3 From f05d2906e8d99bd12a07208903450d62ccbb7c80 Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Tue, 8 Jan 2019 16:00:05 +0100 Subject: Split installation tests in chunks of 10 to not run out of memory --- packages/monorepo-scripts/src/test_installation.ts | 73 ++++++++++++---------- 1 file changed, 39 insertions(+), 34 deletions(-) (limited to 'packages') diff --git a/packages/monorepo-scripts/src/test_installation.ts b/packages/monorepo-scripts/src/test_installation.ts index 5ae13b198..ed9b274e2 100644 --- a/packages/monorepo-scripts/src/test_installation.ts +++ b/packages/monorepo-scripts/src/test_installation.ts @@ -45,45 +45,50 @@ function logIfDefined(x: any): void { const IS_LOCAL_PUBLISH = process.env.IS_LOCAL_PUBLISH === 'true'; const registry = IS_LOCAL_PUBLISH ? 'http://localhost:4873/' : 'https://registry.npmjs.org/'; const monorepoRootPath = path.join(__dirname, '../../..'); - const packages = utils.getPackages(monorepoRootPath); + // We sort error messages according to package topology so that we can + // them in a more intuitive order. E.g. if package A has an error and + // package B imports it, the tests for both package A and package B will + // fail. But package B only fails because of an error in package A. + // Since the error in package A is the root cause, we log it first. + const packages = utils.getTopologicallySortedPackages(monorepoRootPath); const installablePackages = _.filter( packages, pkg => !pkg.packageJson.private && !_.isUndefined(pkg.packageJson.main) && pkg.packageJson.main.endsWith('.js'), ); - utils.log('Testing packages:'); - _.map(installablePackages, pkg => utils.log(`* ${pkg.packageJson.name}`)); - // Run all package tests asynchronously and push promises into an array so - // we can wait for all of them to resolve. - const promises: Array> = []; - const errors: PackageErr[] = []; - for (const installablePackage of installablePackages) { - const packagePromise = testInstallPackageAsync(monorepoRootPath, registry, installablePackage).catch(error => { - errors.push({ packageName: installablePackage.packageJson.name, error }); - }); - promises.push(packagePromise); - } - await Promise.all(promises); - if (errors.length > 0) { - // We sort error messages according to package topology so that we can - // them in a more intuitive order. E.g. if package A has an error and - // package B imports it, the tests for both package A and package B will - // fail. But package B only fails because of an error in package A. - // Since the error in package A is the root cause, we log it first. - const topologicallySortedPackages = utils.getTopologicallySortedPackages(monorepoRootPath); - const topologicallySortedErrors = _.sortBy(errors, packageErr => - findPackageIndex(topologicallySortedPackages, packageErr.packageName), - ); - _.forEach(topologicallySortedErrors, packageError => { - utils.log(`ERROR in package ${packageError.packageName}:`); - logIfDefined(packageError.error.message); - logIfDefined(packageError.error.stderr); - logIfDefined(packageError.error.stdout); - logIfDefined(packageError.error.stack); - }); - process.exit(1); - } else { - process.exit(0); + const CHUNK_SIZE = 10; + const chunkedInstallablePackages = _.chunk(installablePackages, CHUNK_SIZE); + utils.log(`Testing all packages in ${chunkedInstallablePackages.length} chunks`); + for (const installablePackagesChunk of chunkedInstallablePackages) { + utils.log('Testing packages:'); + _.map(installablePackagesChunk, pkg => utils.log(`* ${pkg.packageJson.name}`)); + // Run all package tests within that chunk asynchronously and push promises into an array so + // we can wait for all of them to resolve. + const promises: Array> = []; + const errors: PackageErr[] = []; + for (const installablePackage of installablePackagesChunk) { + const packagePromise = testInstallPackageAsync(monorepoRootPath, registry, installablePackage).catch( + error => { + errors.push({ packageName: installablePackage.packageJson.name, error }); + }, + ); + promises.push(packagePromise); + } + await Promise.all(promises); + if (errors.length > 0) { + const topologicallySortedErrors = _.sortBy(errors, packageErr => + findPackageIndex(packages, packageErr.packageName), + ); + _.forEach(topologicallySortedErrors, packageError => { + utils.log(`ERROR in package ${packageError.packageName}:`); + logIfDefined(packageError.error.message); + logIfDefined(packageError.error.stderr); + logIfDefined(packageError.error.stdout); + logIfDefined(packageError.error.stack); + }); + process.exit(1); + } } + process.exit(0); })().catch(err => { utils.log(`Unexpected error: ${err.message}`); process.exit(1); -- cgit v1.2.3 From 03248244ff8a7d8f276a86262f1b53e8dc9d5675 Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Tue, 8 Jan 2019 16:16:02 +0100 Subject: Bump up the chunk size --- packages/monorepo-scripts/src/test_installation.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/monorepo-scripts/src/test_installation.ts b/packages/monorepo-scripts/src/test_installation.ts index ed9b274e2..59647b836 100644 --- a/packages/monorepo-scripts/src/test_installation.ts +++ b/packages/monorepo-scripts/src/test_installation.ts @@ -55,7 +55,7 @@ function logIfDefined(x: any): void { packages, pkg => !pkg.packageJson.private && !_.isUndefined(pkg.packageJson.main) && pkg.packageJson.main.endsWith('.js'), ); - const CHUNK_SIZE = 10; + const CHUNK_SIZE = 15; const chunkedInstallablePackages = _.chunk(installablePackages, CHUNK_SIZE); utils.log(`Testing all packages in ${chunkedInstallablePackages.length} chunks`); for (const installablePackagesChunk of chunkedInstallablePackages) { -- cgit v1.2.3 From 16fc2c3776f43a1fb9cc1f608c26e924741ba8cd Mon Sep 17 00:00:00 2001 From: fragosti Date: Tue, 8 Jan 2019 10:34:40 -0800 Subject: fix: use new forum URL --- packages/website/ts/utils/constants.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'packages') diff --git a/packages/website/ts/utils/constants.ts b/packages/website/ts/utils/constants.ts index c62de4eb3..ada8de549 100644 --- a/packages/website/ts/utils/constants.ts +++ b/packages/website/ts/utils/constants.ts @@ -2,7 +2,7 @@ import { ALink } from '@0x/react-shared'; import { BigNumber } from '@0x/utils'; import { Key, WebsitePaths } from 'ts/types'; -const URL_FORUM = 'https://forum.0xproject.com'; +const URL_FORUM = 'https://forum.0x.org'; const URL_ZEROEX_CHAT = 'https://discord.gg/d3FTX3M'; export const constants = { @@ -75,7 +75,7 @@ export const constants = { URL_APACHE_LICENSE: 'http://www.apache.org/licenses/LICENSE-2.0', URL_BITLY_API: 'https://api-ssl.bitly.com', URL_BLOG: 'https://blog.0xproject.com/latest', - URL_DISCOURSE_FORUM: 'https://forum.0xproject.com', + URL_DISCOURSE_FORUM: 'https://forum.0x.org', URL_ECOSYSTEM_APPLY: 'https://0x.smapply.io/', URL_ECOSYSTEM_BLOG_POST: 'https://blog.0xproject.com/announcing-the-0x-ecosystem-acceleration-program-89d1cb89d565', URL_FIREFOX_U2F_ADDON: 'https://addons.mozilla.org/en-US/firefox/addon/u2f-support-add-on/', -- cgit v1.2.3 From d00dd07435ff4d87b0868cfe9c5426fc191d9b23 Mon Sep 17 00:00:00 2001 From: fragosti Date: Tue, 8 Jan 2019 16:45:35 -0800 Subject: fix: use getBackendBaseUrl instead of hardcoded string --- packages/website/ts/components/modals/modal_contact.tsx | 3 ++- packages/website/ts/components/newsletter_form.tsx | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) (limited to 'packages') diff --git a/packages/website/ts/components/modals/modal_contact.tsx b/packages/website/ts/components/modals/modal_contact.tsx index a3a1f13f5..7414df7d9 100644 --- a/packages/website/ts/components/modals/modal_contact.tsx +++ b/packages/website/ts/components/modals/modal_contact.tsx @@ -12,6 +12,7 @@ import { Icon } from 'ts/components/icon'; import { Input, InputWidth } from 'ts/components/modals/input'; import { Heading, Paragraph } from 'ts/components/text'; import { GlobalStyle } from 'ts/constants/globalStyle'; +import { utils } from 'ts/utils/utils'; export enum ModalContactType { General = 'GENERAL', @@ -279,7 +280,7 @@ export class ModalContact extends React.Component { try { // Disabling no-unbound method b/c no reason for _.isEmpty to be bound // tslint:disable:no-unbound-method - const response = await fetch(`https://website-api.0xproject.com${endpoint}`, { + const response = await fetch(`${utils.getBackendBaseUrl()}${endpoint}`, { method: 'post', mode: 'cors', credentials: 'same-origin', diff --git a/packages/website/ts/components/newsletter_form.tsx b/packages/website/ts/components/newsletter_form.tsx index 4a7abb7ec..bd61e3f4d 100644 --- a/packages/website/ts/components/newsletter_form.tsx +++ b/packages/website/ts/components/newsletter_form.tsx @@ -4,6 +4,7 @@ import styled, { withTheme } from 'styled-components'; import { ThemeValuesInterface } from 'ts/components/siteWrap'; import { colors } from 'ts/style/colors'; import { errorReporter } from 'ts/utils/error_reporter'; +import { utils } from 'ts/utils/utils'; interface FormProps { theme: ThemeValuesInterface; @@ -92,7 +93,7 @@ class Form extends React.Component { } try { - await fetch('https://website-api.0x.org/newsletter_subscriber/substack', { + await fetch(`${utils.getBackendBaseUrl()}/newsletter_subscriber/substack`, { method: 'post', mode: 'cors', headers: { -- cgit v1.2.3 From 7a114a6ef182b70a2f2b221cce2423aa1fffff9e Mon Sep 17 00:00:00 2001 From: fragosti Date: Wed, 9 Jan 2019 18:26:18 -0800 Subject: fix: account for undefined errors in promisify, not only null --- packages/utils/src/promisify.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/utils/src/promisify.ts b/packages/utils/src/promisify.ts index 29d626b61..e82251a0f 100644 --- a/packages/utils/src/promisify.ts +++ b/packages/utils/src/promisify.ts @@ -10,7 +10,7 @@ export function promisify(originalFn: (...args: any[]) => void, thisArg?: any const promisifiedFunction = async (...callArgs: any[]): Promise => { return new Promise((resolve, reject) => { const callback = (err: Error | null, data?: T) => { - _.isNull(err) ? resolve(data) : reject(err); + _.isNull(err) || _.isUndefined(err) ? resolve(data) : reject(err); }; originalFn.apply(thisArg, [...callArgs, callback]); }); -- cgit v1.2.3 From b639843115547fc3d259fcfb98599a16187b3d07 Mon Sep 17 00:00:00 2001 From: fragosti Date: Wed, 9 Jan 2019 18:31:23 -0800 Subject: chore: changelog update --- packages/utils/CHANGELOG.json | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'packages') diff --git a/packages/utils/CHANGELOG.json b/packages/utils/CHANGELOG.json index fb85c4174..d84e32dd2 100644 --- a/packages/utils/CHANGELOG.json +++ b/packages/utils/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "version": "3.0.0", + "changes": [ + { + "note": "Make `promisify` resolve when the callback error is undefined.", + "pr": 1501 + } + ] + }, { "version": "2.1.1", "changes": [ -- cgit v1.2.3 From 420333e3c35252639bca7a987029d65ad8322290 Mon Sep 17 00:00:00 2001 From: Fabio B Date: Thu, 10 Jan 2019 11:11:23 +0100 Subject: Update packages/monorepo-scripts/src/test_installation.ts Co-Authored-By: LogvinovLeon --- packages/monorepo-scripts/src/test_installation.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/monorepo-scripts/src/test_installation.ts b/packages/monorepo-scripts/src/test_installation.ts index 59647b836..822f48967 100644 --- a/packages/monorepo-scripts/src/test_installation.ts +++ b/packages/monorepo-scripts/src/test_installation.ts @@ -45,7 +45,7 @@ function logIfDefined(x: any): void { const IS_LOCAL_PUBLISH = process.env.IS_LOCAL_PUBLISH === 'true'; const registry = IS_LOCAL_PUBLISH ? 'http://localhost:4873/' : 'https://registry.npmjs.org/'; const monorepoRootPath = path.join(__dirname, '../../..'); - // We sort error messages according to package topology so that we can + // We sort error messages according to package topology so that we can see // them in a more intuitive order. E.g. if package A has an error and // package B imports it, the tests for both package A and package B will // fail. But package B only fails because of an error in package A. -- cgit v1.2.3 From 60cdbbccae1f33555edf0b68e8f7af9c8048c513 Mon Sep 17 00:00:00 2001 From: Fabio B Date: Thu, 10 Jan 2019 11:11:43 +0100 Subject: Update packages/sol-coverage/CHANGELOG.json Co-Authored-By: LogvinovLeon --- packages/sol-coverage/CHANGELOG.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/sol-coverage/CHANGELOG.json b/packages/sol-coverage/CHANGELOG.json index ca6048d99..938fa97a0 100644 --- a/packages/sol-coverage/CHANGELOG.json +++ b/packages/sol-coverage/CHANGELOG.json @@ -4,7 +4,7 @@ "changes": [ { "note": - "Initial release as a separate package. For historic entries check @0x/sol-trace-based-tools-common", + "Initial release as a separate package. For historical entries see @0x/sol-trace-based-tools-common", "pr": 1492 } ] -- cgit v1.2.3 From 36504646f93882b43da8918b59dfe40d09bbbfd4 Mon Sep 17 00:00:00 2001 From: Fabio B Date: Thu, 10 Jan 2019 11:11:52 +0100 Subject: Update packages/sol-profiler/CHANGELOG.json Co-Authored-By: LogvinovLeon --- packages/sol-profiler/CHANGELOG.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/sol-profiler/CHANGELOG.json b/packages/sol-profiler/CHANGELOG.json index ca6048d99..938fa97a0 100644 --- a/packages/sol-profiler/CHANGELOG.json +++ b/packages/sol-profiler/CHANGELOG.json @@ -4,7 +4,7 @@ "changes": [ { "note": - "Initial release as a separate package. For historic entries check @0x/sol-trace-based-tools-common", + "Initial release as a separate package. For historical entries see @0x/sol-trace-based-tools-common", "pr": 1492 } ] -- cgit v1.2.3 From 052ed21e9ccc909d981ea61ff29fc845bd976dc0 Mon Sep 17 00:00:00 2001 From: Fabio B Date: Thu, 10 Jan 2019 11:12:08 +0100 Subject: Update packages/sol-trace-based-tools-common/CHANGELOG.json Co-Authored-By: LogvinovLeon --- packages/sol-trace-based-tools-common/CHANGELOG.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/sol-trace-based-tools-common/CHANGELOG.json b/packages/sol-trace-based-tools-common/CHANGELOG.json index 4c56d547d..caccb3fff 100644 --- a/packages/sol-trace-based-tools-common/CHANGELOG.json +++ b/packages/sol-trace-based-tools-common/CHANGELOG.json @@ -3,7 +3,7 @@ "version": "3.0.0", "changes": [ { - "note": "Move out specific tools and leave just the common part", + "note": "Move out specific tools and leave just the shared parts of the codebase", "pr": 1492 } ] -- cgit v1.2.3 From d7df402c42f7964bd31a765f5ec92a40179b69d7 Mon Sep 17 00:00:00 2001 From: Fabio B Date: Thu, 10 Jan 2019 11:15:19 +0100 Subject: Update packages/sol-trace-based-tools-common/src/source_maps.ts Co-Authored-By: LogvinovLeon --- packages/sol-trace-based-tools-common/src/source_maps.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/sol-trace-based-tools-common/src/source_maps.ts b/packages/sol-trace-based-tools-common/src/source_maps.ts index 7191fb712..af0fb4035 100644 --- a/packages/sol-trace-based-tools-common/src/source_maps.ts +++ b/packages/sol-trace-based-tools-common/src/source_maps.ts @@ -12,7 +12,7 @@ export interface SourceLocation { } /** - * Receives a string with newlines and returns a hash of byte offset to LineColumn + * Receives a string with newlines and returns a map of byte offset to LineColumn * @param str A string to process */ export function getLocationByOffset(str: string): LocationByOffset { -- cgit v1.2.3 From 03dea585b4e0af9b3272d017e4832d4dd03dbbfe Mon Sep 17 00:00:00 2001 From: Fabio B Date: Thu, 10 Jan 2019 11:15:26 +0100 Subject: Update packages/sol-trace/CHANGELOG.json Co-Authored-By: LogvinovLeon --- packages/sol-trace/CHANGELOG.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/sol-trace/CHANGELOG.json b/packages/sol-trace/CHANGELOG.json index ca6048d99..938fa97a0 100644 --- a/packages/sol-trace/CHANGELOG.json +++ b/packages/sol-trace/CHANGELOG.json @@ -4,7 +4,7 @@ "changes": [ { "note": - "Initial release as a separate package. For historic entries check @0x/sol-trace-based-tools-common", + "Initial release as a separate package. For historical entries see @0x/sol-trace-based-tools-common", "pr": 1492 } ] -- cgit v1.2.3 From 15c9479ebeca57e7c275cd2e73ca3daad03a412f Mon Sep 17 00:00:00 2001 From: Fabio B Date: Thu, 10 Jan 2019 11:15:34 +0100 Subject: Update packages/sol-trace/README.md Co-Authored-By: LogvinovLeon --- packages/sol-trace/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/sol-trace/README.md b/packages/sol-trace/README.md index dba193426..86ca2cbd6 100644 --- a/packages/sol-trace/README.md +++ b/packages/sol-trace/README.md @@ -1,6 +1,6 @@ ## @0x/sol-trace -Prints code traces when revert happens. +Prints a stack trace when a revert is encountered. ### Read the [Documentation](https://0xproject.com/docs/sol-trace). -- cgit v1.2.3 From 7ae9e79235ed3b7eb110b0a1e88338b3965f44da Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Thu, 10 Jan 2019 11:21:05 +0100 Subject: Rename sol-trace-based-tools-common to sol-tracing-utils --- packages/sol-coverage/CHANGELOG.json | 2 +- packages/sol-coverage/package.json | 2 +- packages/sol-coverage/src/coverage_subprovider.ts | 2 +- packages/sol-coverage/src/index.ts | 2 +- packages/sol-profiler/CHANGELOG.json | 2 +- packages/sol-profiler/package.json | 2 +- packages/sol-profiler/src/index.ts | 2 +- packages/sol-profiler/src/profiler_subprovider.ts | 2 +- packages/sol-trace-based-tools-common/.npmignore | 6 - .../sol-trace-based-tools-common/CHANGELOG.json | 398 -------------------- packages/sol-trace-based-tools-common/CHANGELOG.md | 158 -------- packages/sol-trace-based-tools-common/README.md | 61 --- .../sol-trace-based-tools-common/compiler.json | 18 - .../sol-trace-based-tools-common/coverage/.gitkeep | 0 packages/sol-trace-based-tools-common/package.json | 87 ----- .../artifact_adapters/abstract_artifact_adapter.ts | 5 - .../sol_compiler_artifact_adapter.ts | 61 --- .../artifact_adapters/truffle_artifact_adapter.ts | 88 ----- .../src/ast_visitor.ts | 168 --------- .../src/collect_coverage_entries.ts | 41 -- .../sol-trace-based-tools-common/src/constants.ts | 8 - .../src/get_source_range_snippet.ts | 185 --------- .../sol-trace-based-tools-common/src/globals.d.ts | 7 - packages/sol-trace-based-tools-common/src/index.ts | 39 -- .../src/instructions.ts | 23 -- .../src/revert_trace.ts | 95 ----- .../src/source_maps.ts | 91 ----- packages/sol-trace-based-tools-common/src/trace.ts | 104 ------ .../src/trace_collection_subprovider.ts | 188 ---------- .../src/trace_collector.ts | 93 ----- .../src/trace_info_subprovider.ts | 59 --- packages/sol-trace-based-tools-common/src/types.ts | 126 ------- packages/sol-trace-based-tools-common/src/utils.ts | 87 ----- .../test/collect_coverage_entries_test.ts | 155 -------- .../fixtures/contracts/AllSolidityFeatures.sol | 413 --------------------- .../test/fixtures/contracts/SimpleStorage.sol | 11 - .../test/fixtures/contracts/Simplest.sol | 2 - .../test/fixtures/contracts/SolcovIgnore.sol | 22 -- .../test/instructions_test.ts | 19 - .../test/sol_compiler_artifact_adapter_test.ts | 29 -- .../test/source_maps_test.ts | 71 ---- .../test/trace_test.ts | 55 --- .../test/utils_test.ts | 53 --- .../sol-trace-based-tools-common/tsconfig.json | 8 - packages/sol-trace-based-tools-common/tslint.json | 3 - packages/sol-trace/CHANGELOG.json | 2 +- packages/sol-trace/package.json | 2 +- packages/sol-trace/src/index.ts | 2 +- packages/sol-trace/src/revert_trace_subprovider.ts | 2 +- packages/sol-tracing-utils/.npmignore | 6 + packages/sol-tracing-utils/CHANGELOG.json | 398 ++++++++++++++++++++ packages/sol-tracing-utils/CHANGELOG.md | 158 ++++++++ packages/sol-tracing-utils/README.md | 61 +++ packages/sol-tracing-utils/compiler.json | 18 + packages/sol-tracing-utils/package.json | 87 +++++ .../artifact_adapters/abstract_artifact_adapter.ts | 5 + .../sol_compiler_artifact_adapter.ts | 61 +++ .../artifact_adapters/truffle_artifact_adapter.ts | 88 +++++ packages/sol-tracing-utils/src/ast_visitor.ts | 168 +++++++++ .../src/collect_coverage_entries.ts | 41 ++ packages/sol-tracing-utils/src/constants.ts | 8 + .../src/get_source_range_snippet.ts | 185 +++++++++ packages/sol-tracing-utils/src/globals.d.ts | 7 + packages/sol-tracing-utils/src/index.ts | 39 ++ packages/sol-tracing-utils/src/instructions.ts | 23 ++ packages/sol-tracing-utils/src/revert_trace.ts | 95 +++++ packages/sol-tracing-utils/src/source_maps.ts | 91 +++++ packages/sol-tracing-utils/src/trace.ts | 104 ++++++ .../src/trace_collection_subprovider.ts | 188 ++++++++++ packages/sol-tracing-utils/src/trace_collector.ts | 93 +++++ .../src/trace_info_subprovider.ts | 59 +++ packages/sol-tracing-utils/src/types.ts | 126 +++++++ packages/sol-tracing-utils/src/utils.ts | 87 +++++ .../test/collect_coverage_entries_test.ts | 155 ++++++++ .../fixtures/contracts/AllSolidityFeatures.sol | 413 +++++++++++++++++++++ .../test/fixtures/contracts/SimpleStorage.sol | 11 + .../test/fixtures/contracts/Simplest.sol | 2 + .../test/fixtures/contracts/SolcovIgnore.sol | 22 ++ .../sol-tracing-utils/test/instructions_test.ts | 19 + .../test/sol_compiler_artifact_adapter_test.ts | 29 ++ .../sol-tracing-utils/test/source_maps_test.ts | 71 ++++ packages/sol-tracing-utils/test/trace_test.ts | 55 +++ packages/sol-tracing-utils/test/utils_test.ts | 53 +++ packages/sol-tracing-utils/tsconfig.json | 8 + packages/sol-tracing-utils/tslint.json | 3 + 85 files changed, 3049 insertions(+), 3049 deletions(-) delete mode 100644 packages/sol-trace-based-tools-common/.npmignore delete mode 100644 packages/sol-trace-based-tools-common/CHANGELOG.json delete mode 100644 packages/sol-trace-based-tools-common/CHANGELOG.md delete mode 100644 packages/sol-trace-based-tools-common/README.md delete mode 100644 packages/sol-trace-based-tools-common/compiler.json delete mode 100644 packages/sol-trace-based-tools-common/coverage/.gitkeep delete mode 100644 packages/sol-trace-based-tools-common/package.json delete mode 100644 packages/sol-trace-based-tools-common/src/artifact_adapters/abstract_artifact_adapter.ts delete mode 100644 packages/sol-trace-based-tools-common/src/artifact_adapters/sol_compiler_artifact_adapter.ts delete mode 100644 packages/sol-trace-based-tools-common/src/artifact_adapters/truffle_artifact_adapter.ts delete mode 100644 packages/sol-trace-based-tools-common/src/ast_visitor.ts delete mode 100644 packages/sol-trace-based-tools-common/src/collect_coverage_entries.ts delete mode 100644 packages/sol-trace-based-tools-common/src/constants.ts delete mode 100644 packages/sol-trace-based-tools-common/src/get_source_range_snippet.ts delete mode 100644 packages/sol-trace-based-tools-common/src/globals.d.ts delete mode 100644 packages/sol-trace-based-tools-common/src/index.ts delete mode 100644 packages/sol-trace-based-tools-common/src/instructions.ts delete mode 100644 packages/sol-trace-based-tools-common/src/revert_trace.ts delete mode 100644 packages/sol-trace-based-tools-common/src/source_maps.ts delete mode 100644 packages/sol-trace-based-tools-common/src/trace.ts delete mode 100644 packages/sol-trace-based-tools-common/src/trace_collection_subprovider.ts delete mode 100644 packages/sol-trace-based-tools-common/src/trace_collector.ts delete mode 100644 packages/sol-trace-based-tools-common/src/trace_info_subprovider.ts delete mode 100644 packages/sol-trace-based-tools-common/src/types.ts delete mode 100644 packages/sol-trace-based-tools-common/src/utils.ts delete mode 100644 packages/sol-trace-based-tools-common/test/collect_coverage_entries_test.ts delete mode 100644 packages/sol-trace-based-tools-common/test/fixtures/contracts/AllSolidityFeatures.sol delete mode 100644 packages/sol-trace-based-tools-common/test/fixtures/contracts/SimpleStorage.sol delete mode 100644 packages/sol-trace-based-tools-common/test/fixtures/contracts/Simplest.sol delete mode 100644 packages/sol-trace-based-tools-common/test/fixtures/contracts/SolcovIgnore.sol delete mode 100644 packages/sol-trace-based-tools-common/test/instructions_test.ts delete mode 100644 packages/sol-trace-based-tools-common/test/sol_compiler_artifact_adapter_test.ts delete mode 100644 packages/sol-trace-based-tools-common/test/source_maps_test.ts delete mode 100644 packages/sol-trace-based-tools-common/test/trace_test.ts delete mode 100644 packages/sol-trace-based-tools-common/test/utils_test.ts delete mode 100644 packages/sol-trace-based-tools-common/tsconfig.json delete mode 100644 packages/sol-trace-based-tools-common/tslint.json create mode 100644 packages/sol-tracing-utils/.npmignore create mode 100644 packages/sol-tracing-utils/CHANGELOG.json create mode 100644 packages/sol-tracing-utils/CHANGELOG.md create mode 100644 packages/sol-tracing-utils/README.md create mode 100644 packages/sol-tracing-utils/compiler.json create mode 100644 packages/sol-tracing-utils/package.json create mode 100644 packages/sol-tracing-utils/src/artifact_adapters/abstract_artifact_adapter.ts create mode 100644 packages/sol-tracing-utils/src/artifact_adapters/sol_compiler_artifact_adapter.ts create mode 100644 packages/sol-tracing-utils/src/artifact_adapters/truffle_artifact_adapter.ts create mode 100644 packages/sol-tracing-utils/src/ast_visitor.ts create mode 100644 packages/sol-tracing-utils/src/collect_coverage_entries.ts create mode 100644 packages/sol-tracing-utils/src/constants.ts create mode 100644 packages/sol-tracing-utils/src/get_source_range_snippet.ts create mode 100644 packages/sol-tracing-utils/src/globals.d.ts create mode 100644 packages/sol-tracing-utils/src/index.ts create mode 100644 packages/sol-tracing-utils/src/instructions.ts create mode 100644 packages/sol-tracing-utils/src/revert_trace.ts create mode 100644 packages/sol-tracing-utils/src/source_maps.ts create mode 100644 packages/sol-tracing-utils/src/trace.ts create mode 100644 packages/sol-tracing-utils/src/trace_collection_subprovider.ts create mode 100644 packages/sol-tracing-utils/src/trace_collector.ts create mode 100644 packages/sol-tracing-utils/src/trace_info_subprovider.ts create mode 100644 packages/sol-tracing-utils/src/types.ts create mode 100644 packages/sol-tracing-utils/src/utils.ts create mode 100644 packages/sol-tracing-utils/test/collect_coverage_entries_test.ts create mode 100644 packages/sol-tracing-utils/test/fixtures/contracts/AllSolidityFeatures.sol create mode 100644 packages/sol-tracing-utils/test/fixtures/contracts/SimpleStorage.sol create mode 100644 packages/sol-tracing-utils/test/fixtures/contracts/Simplest.sol create mode 100644 packages/sol-tracing-utils/test/fixtures/contracts/SolcovIgnore.sol create mode 100644 packages/sol-tracing-utils/test/instructions_test.ts create mode 100644 packages/sol-tracing-utils/test/sol_compiler_artifact_adapter_test.ts create mode 100644 packages/sol-tracing-utils/test/source_maps_test.ts create mode 100644 packages/sol-tracing-utils/test/trace_test.ts create mode 100644 packages/sol-tracing-utils/test/utils_test.ts create mode 100644 packages/sol-tracing-utils/tsconfig.json create mode 100644 packages/sol-tracing-utils/tslint.json (limited to 'packages') diff --git a/packages/sol-coverage/CHANGELOG.json b/packages/sol-coverage/CHANGELOG.json index 938fa97a0..c650a4a4b 100644 --- a/packages/sol-coverage/CHANGELOG.json +++ b/packages/sol-coverage/CHANGELOG.json @@ -4,7 +4,7 @@ "changes": [ { "note": - "Initial release as a separate package. For historical entries see @0x/sol-trace-based-tools-common", + "Initial release as a separate package. For historical entries see @0x/sol-tracing-utils", "pr": 1492 } ] diff --git a/packages/sol-coverage/package.json b/packages/sol-coverage/package.json index b95fa5502..7dc764efd 100644 --- a/packages/sol-coverage/package.json +++ b/packages/sol-coverage/package.json @@ -30,7 +30,7 @@ "homepage": "https://github.com/0xProject/0x-monorepo/packages/sol-coverage/README.md", "dependencies": { "@0x/subproviders": "^2.1.8", - "@0x/sol-trace-based-tools-common": "^2.1.16", + "@0x/sol-tracing-utils": "^2.1.16", "@0x/typescript-typings": "^3.0.6", "ethereum-types": "^1.1.4", "lodash": "^4.17.5" diff --git a/packages/sol-coverage/src/coverage_subprovider.ts b/packages/sol-coverage/src/coverage_subprovider.ts index 255fa9481..e6b546c4a 100644 --- a/packages/sol-coverage/src/coverage_subprovider.ts +++ b/packages/sol-coverage/src/coverage_subprovider.ts @@ -15,7 +15,7 @@ import { TraceInfo, TraceInfoSubprovider, utils, -} from '@0x/sol-trace-based-tools-common'; +} from '@0x/sol-tracing-utils'; import * as _ from 'lodash'; /** diff --git a/packages/sol-coverage/src/index.ts b/packages/sol-coverage/src/index.ts index dcf97b72e..97b4ecee6 100644 --- a/packages/sol-coverage/src/index.ts +++ b/packages/sol-coverage/src/index.ts @@ -4,7 +4,7 @@ export { TruffleArtifactAdapter, AbstractArtifactAdapter, ContractData, -} from '@0x/sol-trace-based-tools-common'; +} from '@0x/sol-tracing-utils'; export { JSONRPCRequestPayload, diff --git a/packages/sol-profiler/CHANGELOG.json b/packages/sol-profiler/CHANGELOG.json index 938fa97a0..c650a4a4b 100644 --- a/packages/sol-profiler/CHANGELOG.json +++ b/packages/sol-profiler/CHANGELOG.json @@ -4,7 +4,7 @@ "changes": [ { "note": - "Initial release as a separate package. For historical entries see @0x/sol-trace-based-tools-common", + "Initial release as a separate package. For historical entries see @0x/sol-tracing-utils", "pr": 1492 } ] diff --git a/packages/sol-profiler/package.json b/packages/sol-profiler/package.json index 03b421b0e..b85c82ed2 100644 --- a/packages/sol-profiler/package.json +++ b/packages/sol-profiler/package.json @@ -31,7 +31,7 @@ "dependencies": { "@0x/subproviders": "^2.1.8", "@0x/typescript-typings": "^3.0.6", - "@0x/sol-trace-based-tools-common": "^2.1.16", + "@0x/sol-tracing-utils": "^2.1.16", "ethereum-types": "^1.1.4", "lodash": "^4.17.5" }, diff --git a/packages/sol-profiler/src/index.ts b/packages/sol-profiler/src/index.ts index b36ed5baa..5d4806be4 100644 --- a/packages/sol-profiler/src/index.ts +++ b/packages/sol-profiler/src/index.ts @@ -3,7 +3,7 @@ export { SolCompilerArtifactAdapter, TruffleArtifactAdapter, ContractData, -} from '@0x/sol-trace-based-tools-common'; +} from '@0x/sol-tracing-utils'; // HACK: ProfilerSubprovider is a hacky way to do profiling using coverage tools. Not production ready export { ProfilerSubprovider } from './profiler_subprovider'; diff --git a/packages/sol-profiler/src/profiler_subprovider.ts b/packages/sol-profiler/src/profiler_subprovider.ts index 61dc25b6b..c3ed13ea5 100644 --- a/packages/sol-profiler/src/profiler_subprovider.ts +++ b/packages/sol-profiler/src/profiler_subprovider.ts @@ -12,7 +12,7 @@ import { TraceInfo, TraceInfoSubprovider, utils, -} from '@0x/sol-trace-based-tools-common'; +} from '@0x/sol-tracing-utils'; /** * This class implements the [web3-provider-engine](https://github.com/MetaMask/provider-engine) subprovider interface. diff --git a/packages/sol-trace-based-tools-common/.npmignore b/packages/sol-trace-based-tools-common/.npmignore deleted file mode 100644 index 037786e46..000000000 --- a/packages/sol-trace-based-tools-common/.npmignore +++ /dev/null @@ -1,6 +0,0 @@ -.* -yarn-error.log -/src/ -/scripts/ -tsconfig.json -/lib/src/monorepo_scripts/ diff --git a/packages/sol-trace-based-tools-common/CHANGELOG.json b/packages/sol-trace-based-tools-common/CHANGELOG.json deleted file mode 100644 index caccb3fff..000000000 --- a/packages/sol-trace-based-tools-common/CHANGELOG.json +++ /dev/null @@ -1,398 +0,0 @@ -[ - { - "version": "3.0.0", - "changes": [ - { - "note": "Move out specific tools and leave just the shared parts of the codebase", - "pr": 1492 - } - ] - }, - { - "version": "2.1.16", - "changes": [ - { - "note": "Dependencies updated" - } - ], - "timestamp": 1544739608 - }, - { - "version": "2.1.15", - "changes": [ - { - "note": "Dependencies updated" - } - ], - "timestamp": 1544570656 - }, - { - "timestamp": 1543401373, - "version": "2.1.14", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1542821676, - "version": "2.1.13", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1542208198, - "version": "2.1.12", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1542134075, - "version": "2.1.11", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1542028948, - "version": "2.1.10", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "version": "2.1.9", - "changes": [ - { - "note": "Dependencies updated" - } - ], - "timestamp": 1541740904 - }, - { - "version": "2.1.8", - "changes": [ - { - "note": "Make @types/solidity-parser-antlr a 'dependency' so it's available to users of the library", - "pr": 1105 - } - ], - "timestamp": 1539871071 - }, - { - "version": "2.1.7", - "changes": [ - { - "note": "Dependencies updated" - } - ], - "timestamp": 1538693146 - }, - { - "timestamp": 1538157789, - "version": "2.1.6", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1537907159, - "version": "2.1.5", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1537875740, - "version": "2.1.4", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1537541580, - "version": "2.1.3", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1536142250, - "version": "2.1.2", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1535377027, - "version": "2.1.1", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "version": "2.1.0", - "changes": [ - { - "note": - "Export types: `JSONRPCRequestPayload`, `Provider`, `JSONRPCErrorCallback`, `JSONRPCResponsePayload`, `JSONRPCRequestPayloadWithMethod`, `NextCallback`, `ErrorCallback`, `OnNextCompleted` and `Callback`", - "pr": 924 - } - ], - "timestamp": 1535133899 - }, - { - "version": "2.0.0", - "changes": [ - { - "note": - "Fix a bug when eth_call coverage was not computed because of silent schema validation failures", - "pr": 938 - }, - { - "note": "Make `TruffleArtifactAdapter` read the `truffle.js` config for `solc` settings", - "pr": 938 - }, - { - "note": - "Change the first param of `TruffleArtifactAdapter` to be the `projectRoot` instead of `sourcesDir`", - "pr": 938 - }, - { - "note": - "Throw a helpful error message if truffle artifacts were generated with a different solc version than the one passed in", - "pr": 938 - } - ], - "timestamp": 1534210131 - }, - { - "version": "1.0.3", - "changes": [ - { - "note": "Dependencies updated" - } - ], - "timestamp": 1532619515 - }, - { - "timestamp": 1532605697, - "version": "1.0.2", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1532357734, - "version": "1.0.1", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1532043000, - "version": "1.0.0", - "changes": [ - { - "note": - "Add artifact adapter as a parameter for `CoverageSubprovider`. Export `AbstractArtifactAdapter`", - "pr": 589 - }, - { - "note": "Implement `SolCompilerArtifactAdapter` and `TruffleArtifactAdapter`", - "pr": 589 - }, - { - "note": "Properly parse multi-level traces", - "pr": 589 - }, - { - "note": "Add support for solidity libraries", - "pr": 589 - }, - { - "note": "Fixed a bug causing `RegExp` to crash if contract code is longer that 32767 characters", - "pr": 675 - }, - { - "note": "Fixed a bug caused by Geth debug trace depth being 1indexed", - "pr": 675 - }, - { - "note": "Fixed a bug when the tool crashed on empty traces", - "pr": 675 - }, - { - "note": "Use `BlockchainLifecycle` to support reverts on Geth", - "pr": 675 - }, - { - "note": "Add `ProfilerSubprovider` as a hacky way to profile code using coverage tools", - "pr": 675 - }, - { - "note": "Collect traces from `estimate_gas` calls", - "pr": 675 - }, - { - "note": "Fix a race condition caused by not awaiting the transaction before getting a trace", - "pr": 675 - }, - { - "note": "Add `start`/`stop` functionality to `CoverageSubprovider` and `ProfilerSubprovider`", - "pr": 675 - }, - { - "note": "Skip interface artifacts with a warning instead of failing", - "pr": 675 - }, - { - "note": "Fix `solcVersion` regex in parameter validation", - "pr": 690 - }, - { - "note": - "Fix a bug when in `TruffleArtifactsAdapter` causing it to throw if `compiler.json` is not there", - "pr": 690 - }, - { - "note": "HUGE perf improvements", - "pr": 690 - }, - { - "note": "Create `RevertTraceSubprovider` which prints a stack trace when a `REVERT` is detected", - "pr": 705 - }, - { - "note": "Add source code snippets to stack traces printed by `RevertTraceSubprovider`", - "pr": 725 - } - ] - }, - { - "timestamp": 1531919263, - "version": "0.1.3", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1531149657, - "version": "0.1.2", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1529397769, - "version": "0.1.1", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "version": "0.1.0", - "changes": [ - { - "note": "Incorrect publish that was unpublished" - } - ], - "timestamp": 1527810075 - }, - { - "timestamp": 1527009134, - "version": "0.0.11", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1525477860, - "version": "0.0.10", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1525428773, - "version": "0.0.9", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1524044013, - "version": "0.0.8", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1523462196, - "version": "0.0.7", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1522673609, - "version": "0.0.6", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1522658513, - "version": "0.0.5", - "changes": [ - { - "note": "Dependencies updated" - } - ] - } -] diff --git a/packages/sol-trace-based-tools-common/CHANGELOG.md b/packages/sol-trace-based-tools-common/CHANGELOG.md deleted file mode 100644 index 879ef9c95..000000000 --- a/packages/sol-trace-based-tools-common/CHANGELOG.md +++ /dev/null @@ -1,158 +0,0 @@ - - -CHANGELOG - -## v2.1.16 - _December 13, 2018_ - - * Dependencies updated - -## v2.1.15 - _December 11, 2018_ - - * Dependencies updated - -## v2.1.14 - _November 28, 2018_ - - * Dependencies updated - -## v2.1.13 - _November 21, 2018_ - - * Dependencies updated - -## v2.1.12 - _November 14, 2018_ - - * Dependencies updated - -## v2.1.11 - _November 13, 2018_ - - * Dependencies updated - -## v2.1.10 - _November 12, 2018_ - - * Dependencies updated - -## v2.1.9 - _November 9, 2018_ - - * Dependencies updated - -## v2.1.8 - _October 18, 2018_ - - * Make @types/solidity-parser-antlr a 'dependency' so it's available to users of the library (#1105) - -## v2.1.7 - _October 4, 2018_ - - * Dependencies updated - -## v2.1.6 - _September 28, 2018_ - - * Dependencies updated - -## v2.1.5 - _September 25, 2018_ - - * Dependencies updated - -## v2.1.4 - _September 25, 2018_ - - * Dependencies updated - -## v2.1.3 - _September 21, 2018_ - - * Dependencies updated - -## v2.1.2 - _September 5, 2018_ - - * Dependencies updated - -## v2.1.1 - _August 27, 2018_ - - * Dependencies updated - -## v2.1.0 - _August 24, 2018_ - - * Export types: `JSONRPCRequestPayload`, `Provider`, `JSONRPCErrorCallback`, `JSONRPCResponsePayload`, `JSONRPCRequestPayloadWithMethod`, `NextCallback`, `ErrorCallback`, `OnNextCompleted` and `Callback` (#924) - -## v2.0.0 - _August 14, 2018_ - - * Fix a bug when eth_call coverage was not computed because of silent schema validation failures (#938) - * Make `TruffleArtifactAdapter` read the `truffle.js` config for `solc` settings (#938) - * Change the first param of `TruffleArtifactAdapter` to be the `projectRoot` instead of `sourcesDir` (#938) - * Throw a helpful error message if truffle artifacts were generated with a different solc version than the one passed in (#938) - -## v1.0.3 - _July 26, 2018_ - - * Dependencies updated - -## v1.0.2 - _July 26, 2018_ - - * Dependencies updated - -## v1.0.1 - _July 23, 2018_ - - * Dependencies updated - -## v1.0.0 - _July 19, 2018_ - - * Add artifact adapter as a parameter for `CoverageSubprovider`. Export `AbstractArtifactAdapter` (#589) - * Implement `SolCompilerArtifactAdapter` and `TruffleArtifactAdapter` (#589) - * Properly parse multi-level traces (#589) - * Add support for solidity libraries (#589) - * Fixed a bug causing `RegExp` to crash if contract code is longer that 32767 characters (#675) - * Fixed a bug caused by Geth debug trace depth being 1indexed (#675) - * Fixed a bug when the tool crashed on empty traces (#675) - * Use `BlockchainLifecycle` to support reverts on Geth (#675) - * Add `ProfilerSubprovider` as a hacky way to profile code using coverage tools (#675) - * Collect traces from `estimate_gas` calls (#675) - * Fix a race condition caused by not awaiting the transaction before getting a trace (#675) - * Add `start`/`stop` functionality to `CoverageSubprovider` and `ProfilerSubprovider` (#675) - * Skip interface artifacts with a warning instead of failing (#675) - * Fix `solcVersion` regex in parameter validation (#690) - * Fix a bug when in `TruffleArtifactsAdapter` causing it to throw if `compiler.json` is not there (#690) - * HUGE perf improvements (#690) - * Create `RevertTraceSubprovider` which prints a stack trace when a `REVERT` is detected (#705) - * Add source code snippets to stack traces printed by `RevertTraceSubprovider` (#725) - -## v0.1.3 - _July 18, 2018_ - - * Dependencies updated - -## v0.1.2 - _July 9, 2018_ - - * Dependencies updated - -## v0.1.1 - _June 19, 2018_ - - * Dependencies updated - -## v0.1.0 - _May 31, 2018_ - - * Incorrect publish that was unpublished - -## v0.0.11 - _May 22, 2018_ - - * Dependencies updated - -## v0.0.10 - _May 4, 2018_ - - * Dependencies updated - -## v0.0.9 - _May 4, 2018_ - - * Dependencies updated - -## v0.0.8 - _April 18, 2018_ - - * Dependencies updated - -## v0.0.7 - _April 11, 2018_ - - * Dependencies updated - -## v0.0.6 - _April 2, 2018_ - - * Dependencies updated - -## v0.0.5 - _April 2, 2018_ - - * Dependencies updated diff --git a/packages/sol-trace-based-tools-common/README.md b/packages/sol-trace-based-tools-common/README.md deleted file mode 100644 index b6d75dd59..000000000 --- a/packages/sol-trace-based-tools-common/README.md +++ /dev/null @@ -1,61 +0,0 @@ -## @0x/sol-trace-based-tools-common - -Common code for all solidity trace-based tools (sol-coverage, sol-profiler, sol-trace). - -## Installation - -```bash -yarn add @0x/sol-sol-trace-based-tools-common -``` - -## Contributing - -We welcome improvements and fixes from the wider community! 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 -``` - -### Build - -To build this package and all other monorepo packages that it depends on, run the following from the monorepo root directory: - -```bash -PKG=@0x/sol-trace-based-tools-common yarn build -``` - -Or continuously rebuild on change: - -```bash -PKG=@0x/sol-trace-based-tools-common yarn watch -``` - -### Clean - -```bash -yarn clean -``` - -### Lint - -```bash -yarn lint -``` - -### Run Tests - -```bash -yarn test -``` diff --git a/packages/sol-trace-based-tools-common/compiler.json b/packages/sol-trace-based-tools-common/compiler.json deleted file mode 100644 index a6a0c6d3a..000000000 --- a/packages/sol-trace-based-tools-common/compiler.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "contracts": ["SimpleStorage"], - "contractsDir": "test/fixtures/contracts", - "artifactsDir": "test/fixtures/artifacts", - "compilerSettings": { - "outputSelection": { - "*": { - "*": [ - "abi", - "evm.bytecode.object", - "evm.bytecode.sourceMap", - "evm.deployedBytecode.object", - "evm.deployedBytecode.sourceMap" - ] - } - } - } -} diff --git a/packages/sol-trace-based-tools-common/coverage/.gitkeep b/packages/sol-trace-based-tools-common/coverage/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/sol-trace-based-tools-common/package.json b/packages/sol-trace-based-tools-common/package.json deleted file mode 100644 index 6cfabe6d9..000000000 --- a/packages/sol-trace-based-tools-common/package.json +++ /dev/null @@ -1,87 +0,0 @@ -{ - "name": "@0x/sol-trace-based-tools-common", - "version": "2.1.16", - "engines": { - "node": ">=6.12" - }, - "description": "Common part of trace based solidity tools (sol-coverage, sol-trace, sol-profiler)", - "main": "lib/src/index.js", - "types": "lib/src/index.d.ts", - "scripts": { - "build": "yarn pre_build && tsc -b", - "build:ci": "yarn build", - "pre_build": "run-s copy_test_fixtures", - "lint": "tslint --format stylish --project .", - "test": "run-s compile_test run_mocha", - "rebuild_and_test": "run-s clean build test", - "test:coverage": "nyc npm run test --all && yarn coverage:report:lcov", - "coverage:report:lcov": "nyc report --reporter=text-lcov > coverage/lcov.info", - "test:circleci": "yarn test:coverage", - "run_mocha": "mocha --require source-map-support/register --require make-promises-safe lib/test/**/*_test.js --exit", - "clean": "shx rm -rf lib test/fixtures/artifacts src/artifacts generated_docs", - "copy_test_fixtures": "copyfiles 'test/fixtures/**/*' ./lib", - "compile_test": "sol-compiler compile", - "docs:json": "typedoc --excludePrivate --excludeExternals --target ES5 --tsconfig typedoc-tsconfig.json --json $JSON_FILE_PATH $PROJECT_FILES" - }, - "config": { - "postpublish": { - "assets": [], - "docOmitExports": [ - "ProfilerSubprovider", - "RevertTraceSubprovider" - ] - } - }, - "repository": { - "type": "git", - "url": "https://github.com/0xProject/0x-monorepo.git" - }, - "license": "Apache-2.0", - "bugs": { - "url": "https://github.com/0xProject/0x-monorepo/issues" - }, - "homepage": "https://github.com/0xProject/0x-monorepo/packages/sol-trace-based-tools-common/README.md", - "dependencies": { - "@0x/dev-utils": "^1.0.21", - "@0x/sol-compiler": "^1.1.16", - "@0x/subproviders": "^2.1.8", - "@0x/typescript-typings": "^3.0.6", - "@0x/utils": "^2.0.8", - "@0x/web3-wrapper": "^3.2.1", - "@types/solidity-parser-antlr": "^0.2.0", - "ethereum-types": "^1.1.4", - "ethereumjs-util": "^5.1.1", - "glob": "^7.1.2", - "istanbul": "^0.4.5", - "lodash": "^4.17.5", - "loglevel": "^1.6.1", - "mkdirp": "^0.5.1", - "rimraf": "^2.6.2", - "semaphore-async-await": "^1.5.1", - "solidity-parser-antlr": "^0.2.12" - }, - "devDependencies": { - "@0x/tslint-config": "^2.0.0", - "@types/istanbul": "^0.4.30", - "@types/loglevel": "^1.5.3", - "@types/mkdirp": "^0.5.1", - "@types/mocha": "^2.2.42", - "@types/node": "*", - "@types/rimraf": "^2.0.2", - "chai": "^4.0.1", - "copyfiles": "^2.0.0", - "dirty-chai": "^2.0.1", - "make-promises-safe": "^1.1.0", - "mocha": "^4.1.0", - "npm-run-all": "^4.1.2", - "nyc": "^11.0.1", - "shx": "^0.2.2", - "sinon": "^4.0.0", - "tslint": "5.11.0", - "typedoc": "0.13.0", - "typescript": "3.0.1" - }, - "publishConfig": { - "access": "public" - } -} diff --git a/packages/sol-trace-based-tools-common/src/artifact_adapters/abstract_artifact_adapter.ts b/packages/sol-trace-based-tools-common/src/artifact_adapters/abstract_artifact_adapter.ts deleted file mode 100644 index fcc6562ad..000000000 --- a/packages/sol-trace-based-tools-common/src/artifact_adapters/abstract_artifact_adapter.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { ContractData } from '../types'; - -export abstract class AbstractArtifactAdapter { - public abstract async collectContractsDataAsync(): Promise; -} diff --git a/packages/sol-trace-based-tools-common/src/artifact_adapters/sol_compiler_artifact_adapter.ts b/packages/sol-trace-based-tools-common/src/artifact_adapters/sol_compiler_artifact_adapter.ts deleted file mode 100644 index 57391abbe..000000000 --- a/packages/sol-trace-based-tools-common/src/artifact_adapters/sol_compiler_artifact_adapter.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { logUtils } from '@0x/utils'; -import { CompilerOptions, ContractArtifact } from 'ethereum-types'; -import * as fs from 'fs'; -import * as glob from 'glob'; -import * as _ from 'lodash'; -import * as path from 'path'; - -import { ContractData } from '../types'; - -import { AbstractArtifactAdapter } from './abstract_artifact_adapter'; - -const CONFIG_FILE = 'compiler.json'; - -export class SolCompilerArtifactAdapter extends AbstractArtifactAdapter { - private readonly _artifactsPath: string; - private readonly _sourcesPath: string; - /** - * Instantiates a SolCompilerArtifactAdapter - * @param artifactsPath Path to your artifacts directory - * @param sourcesPath Path to your contract sources directory - */ - constructor(artifactsPath?: string, sourcesPath?: string) { - super(); - const config: CompilerOptions = fs.existsSync(CONFIG_FILE) - ? JSON.parse(fs.readFileSync(CONFIG_FILE).toString()) - : {}; - if (_.isUndefined(artifactsPath) && _.isUndefined(config.artifactsDir)) { - throw new Error(`artifactsDir not found in ${CONFIG_FILE}`); - } - this._artifactsPath = (artifactsPath || config.artifactsDir) as string; - if (_.isUndefined(sourcesPath) && _.isUndefined(config.contractsDir)) { - throw new Error(`contractsDir not found in ${CONFIG_FILE}`); - } - this._sourcesPath = (sourcesPath || config.contractsDir) as string; - } - public async collectContractsDataAsync(): Promise { - const artifactsGlob = `${this._artifactsPath}/**/*.json`; - const artifactFileNames = glob.sync(artifactsGlob, { absolute: true }); - const contractsData: ContractData[] = []; - for (const artifactFileName of artifactFileNames) { - const artifact: ContractArtifact = JSON.parse(fs.readFileSync(artifactFileName).toString()); - if (_.isUndefined(artifact.compilerOutput.evm)) { - logUtils.warn(`${artifactFileName} doesn't contain bytecode. Skipping...`); - continue; - } - let sources = _.keys(artifact.sources); - sources = _.map(sources, relativeFilePath => path.resolve(this._sourcesPath, relativeFilePath)); - const sourceCodes = _.map(sources, (source: string) => fs.readFileSync(source).toString()); - const contractData = { - sourceCodes, - sources, - bytecode: artifact.compilerOutput.evm.bytecode.object, - sourceMap: artifact.compilerOutput.evm.bytecode.sourceMap, - runtimeBytecode: artifact.compilerOutput.evm.deployedBytecode.object, - sourceMapRuntime: artifact.compilerOutput.evm.deployedBytecode.sourceMap, - }; - contractsData.push(contractData); - } - return contractsData; - } -} diff --git a/packages/sol-trace-based-tools-common/src/artifact_adapters/truffle_artifact_adapter.ts b/packages/sol-trace-based-tools-common/src/artifact_adapters/truffle_artifact_adapter.ts deleted file mode 100644 index bb2b15153..000000000 --- a/packages/sol-trace-based-tools-common/src/artifact_adapters/truffle_artifact_adapter.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { Compiler, CompilerOptions } from '@0x/sol-compiler'; -import * as fs from 'fs'; -import * as glob from 'glob'; -import * as path from 'path'; - -import { ContractData } from '../types'; - -import { AbstractArtifactAdapter } from './abstract_artifact_adapter'; -import { SolCompilerArtifactAdapter } from './sol_compiler_artifact_adapter'; - -const DEFAULT_TRUFFLE_ARTIFACTS_DIR = './build/contracts'; - -interface TruffleConfig { - solc?: any; - contracts_build_directory?: string; -} - -export class TruffleArtifactAdapter extends AbstractArtifactAdapter { - private readonly _solcVersion: string; - private readonly _projectRoot: string; - /** - * Instantiates a TruffleArtifactAdapter - * @param projectRoot Path to the truffle project's root directory - * @param solcVersion Solidity version with which to compile all the contracts - */ - constructor(projectRoot: string, solcVersion: string) { - super(); - this._solcVersion = solcVersion; - this._projectRoot = projectRoot; - } - public async collectContractsDataAsync(): Promise { - const artifactsDir = '.0x-artifacts'; - const contractsDir = path.join(this._projectRoot, 'contracts'); - const truffleConfig = this._getTruffleConfig(); - const solcConfig = truffleConfig.solc || {}; - const truffleArtifactsDirectory = truffleConfig.contracts_build_directory || DEFAULT_TRUFFLE_ARTIFACTS_DIR; - this._assertSolidityVersionIsCorrect(truffleArtifactsDirectory); - const compilerOptions: CompilerOptions = { - contractsDir, - artifactsDir, - compilerSettings: { - ...solcConfig, - outputSelection: { - ['*']: { - ['*']: ['abi', 'evm.bytecode.object', 'evm.deployedBytecode.object'], - }, - }, - }, - contracts: '*', - solcVersion: this._solcVersion, - }; - const compiler = new Compiler(compilerOptions); - await compiler.compileAsync(); - const solCompilerArtifactAdapter = new SolCompilerArtifactAdapter(artifactsDir, contractsDir); - const contractsDataFrom0xArtifacts = await solCompilerArtifactAdapter.collectContractsDataAsync(); - return contractsDataFrom0xArtifacts; - } - private _getTruffleConfig(): TruffleConfig { - const truffleConfigFileShort = path.resolve(path.join(this._projectRoot, 'truffle.js')); - const truffleConfigFileLong = path.resolve(path.join(this._projectRoot, 'truffle-config.js')); - if (fs.existsSync(truffleConfigFileShort)) { - const truffleConfig = require(truffleConfigFileShort); - return truffleConfig; - } else if (fs.existsSync(truffleConfigFileLong)) { - const truffleConfig = require(truffleConfigFileLong); - return truffleConfig; - } else { - throw new Error( - `Neither ${truffleConfigFileShort} nor ${truffleConfigFileLong} exists. Make sure the project root is correct`, - ); - } - } - private _assertSolidityVersionIsCorrect(truffleArtifactsDirectory: string): void { - const artifactsGlob = `${truffleArtifactsDirectory}/**/*.json`; - const artifactFileNames = glob.sync(artifactsGlob, { absolute: true }); - for (const artifactFileName of artifactFileNames) { - const artifact = JSON.parse(fs.readFileSync(artifactFileName).toString()); - const compilerVersion = artifact.compiler.version; - if (!compilerVersion.startsWith(this._solcVersion)) { - throw new Error( - `${artifact.contractName} was compiled with solidity ${compilerVersion} but specified version is ${ - this._solcVersion - } making it impossible to process traces`, - ); - } - } - } -} diff --git a/packages/sol-trace-based-tools-common/src/ast_visitor.ts b/packages/sol-trace-based-tools-common/src/ast_visitor.ts deleted file mode 100644 index e55cdf6ec..000000000 --- a/packages/sol-trace-based-tools-common/src/ast_visitor.ts +++ /dev/null @@ -1,168 +0,0 @@ -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 readonly _fnMap: FnMap = {}; - private readonly _branchMap: BranchMap = {}; - private readonly _modifiersStatementIds: number[] = []; - private readonly _statementMap: StatementMap = {}; - private readonly _locationByOffset: LocationByOffset; - private readonly _ignoreRangesBeginningAt: number[]; - // keep track of contract/function ranges that are to be ignored - // so we can also ignore any children nodes within the contract/function - private readonly _ignoreRangesWithin: Array<[number, number]> = []; - constructor(locationByOffset: LocationByOffset, ignoreRangesBeginningAt: number[] = []) { - this._locationByOffset = locationByOffset; - this._ignoreRangesBeginningAt = ignoreRangesBeginningAt; - } - 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 ContractDefinition(ast: Parser.ContractDefinition): void { - if (this._shouldIgnoreExpression(ast)) { - this._ignoreRangesWithin.push(ast.range as [number, number]); - } - } - 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 EmitStatement(ast: any /* TODO: Parser.EmitStatement */): 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)) { - if (this._shouldIgnoreExpression(ast)) { - return; - } - this._modifiersStatementIds.push(this._entryId); - this._visitStatement(ast); - } - } - private _visitBinaryBranch( - ast: Parser.ASTNode, - left: Parser.ASTNode, - right: Parser.ASTNode, - type: BranchType, - ): void { - if (this._shouldIgnoreExpression(ast)) { - return; - } - this._branchMap[this._entryId++] = { - line: this._getExpressionRange(ast).start.line, - type, - locations: [this._getExpressionRange(left), this._getExpressionRange(right)], - }; - } - private _visitStatement(ast: Parser.ASTNode): void { - if (this._shouldIgnoreExpression(ast)) { - return; - } - this._statementMap[this._entryId++] = this._getExpressionRange(ast); - } - private _getExpressionRange(ast: Parser.ASTNode): SingleFileSourceRange { - const astRange = ast.range as [number, number]; - const start = this._locationByOffset[astRange[0]]; - const end = this._locationByOffset[astRange[1] + 1]; - const range = { - start, - end, - }; - return range; - } - private _shouldIgnoreExpression(ast: Parser.ASTNode): boolean { - const [astStart, astEnd] = ast.range as [number, number]; - const isRangeIgnored = _.some( - this._ignoreRangesWithin, - ([rangeStart, rangeEnd]: [number, number]) => astStart >= rangeStart && astEnd <= rangeEnd, - ); - return this._ignoreRangesBeginningAt.includes(astStart) || isRangeIgnored; - } - private _visitFunctionLikeDefinition(ast: Parser.ModifierDefinition | Parser.FunctionDefinition): void { - if (this._shouldIgnoreExpression(ast)) { - this._ignoreRangesWithin.push(ast.range as [number, number]); - return; - } - const loc = this._getExpressionRange(ast); - this._fnMap[this._entryId++] = { - name: ast.name, - line: loc.start.line, - loc, - }; - this._visitStatement(ast); - } -} diff --git a/packages/sol-trace-based-tools-common/src/collect_coverage_entries.ts b/packages/sol-trace-based-tools-common/src/collect_coverage_entries.ts deleted file mode 100644 index bdbcd613e..000000000 --- a/packages/sol-trace-based-tools-common/src/collect_coverage_entries.ts +++ /dev/null @@ -1,41 +0,0 @@ -import * as ethUtil from 'ethereumjs-util'; -import * as _ from 'lodash'; -import * as parser from 'solidity-parser-antlr'; - -import { ASTVisitor, CoverageEntriesDescription } from './ast_visitor'; -import { getLocationByOffset } from './source_maps'; - -const IGNORE_RE = /\/\*\s*solcov\s+ignore\s+next\s*\*\/\s*/gm; - -// Parsing source code for each transaction/code is slow and therefore we cache it -const coverageEntriesBySourceHash: { [sourceHash: string]: CoverageEntriesDescription } = {}; - -export const collectCoverageEntries = (contractSource: string) => { - const sourceHash = ethUtil.sha3(contractSource).toString('hex'); - if (_.isUndefined(coverageEntriesBySourceHash[sourceHash]) && !_.isUndefined(contractSource)) { - const ast = parser.parse(contractSource, { range: true }); - const locationByOffset = getLocationByOffset(contractSource); - const ignoreRangesBegingingAt = gatherRangesToIgnore(contractSource); - const visitor = new ASTVisitor(locationByOffset, ignoreRangesBegingingAt); - parser.visit(ast, visitor); - coverageEntriesBySourceHash[sourceHash] = visitor.getCollectedCoverageEntries(); - } - const coverageEntriesDescription = coverageEntriesBySourceHash[sourceHash]; - return coverageEntriesDescription; -}; - -// Gather the start index of all code blocks preceeded by "/* solcov ignore next */" -function gatherRangesToIgnore(contractSource: string): number[] { - const ignoreRangesStart = []; - - let match; - do { - match = IGNORE_RE.exec(contractSource); - if (match) { - const matchLen = match[0].length; - ignoreRangesStart.push(match.index + matchLen); - } - } while (match); - - return ignoreRangesStart; -} diff --git a/packages/sol-trace-based-tools-common/src/constants.ts b/packages/sol-trace-based-tools-common/src/constants.ts deleted file mode 100644 index 34d62b537..000000000 --- a/packages/sol-trace-based-tools-common/src/constants.ts +++ /dev/null @@ -1,8 +0,0 @@ -// tslint:disable:number-literal-format -export const constants = { - NEW_CONTRACT: 'NEW_CONTRACT' as 'NEW_CONTRACT', - PUSH1: 0x60, - PUSH2: 0x61, - PUSH32: 0x7f, - TIMESTAMP: 0x42, -}; diff --git a/packages/sol-trace-based-tools-common/src/get_source_range_snippet.ts b/packages/sol-trace-based-tools-common/src/get_source_range_snippet.ts deleted file mode 100644 index f578675d3..000000000 --- a/packages/sol-trace-based-tools-common/src/get_source_range_snippet.ts +++ /dev/null @@ -1,185 +0,0 @@ -import * as ethUtil from 'ethereumjs-util'; -import * as _ from 'lodash'; -import * as Parser from 'solidity-parser-antlr'; - -import { SingleFileSourceRange, SourceRange, SourceSnippet } from './types'; -import { utils } from './utils'; - -interface ASTInfo { - type: string; - node: Parser.ASTNode; - name: string | null; - range?: SingleFileSourceRange; -} - -// Parsing source code for each transaction/code is slow and therefore we cache it -const parsedSourceByHash: { [sourceHash: string]: Parser.ASTNode } = {}; - -/** - * Gets the source range snippet by source range to be used by revert trace. - * @param sourceRange source range - * @param sourceCode source code - */ -export function getSourceRangeSnippet(sourceRange: SourceRange, sourceCode: string): SourceSnippet | null { - const sourceHash = ethUtil.sha3(sourceCode).toString('hex'); - if (_.isUndefined(parsedSourceByHash[sourceHash])) { - parsedSourceByHash[sourceHash] = Parser.parse(sourceCode, { loc: true }); - } - const astNode = parsedSourceByHash[sourceHash]; - const visitor = new ASTInfoVisitor(); - Parser.visit(astNode, visitor); - const astInfo = visitor.getASTInfoForRange(sourceRange); - if (astInfo === null) { - return null; - } - const sourceCodeInRange = utils.getRange(sourceCode, sourceRange.location); - return { - ...astInfo, - range: astInfo.range as SingleFileSourceRange, - source: sourceCodeInRange, - fileName: sourceRange.fileName, - }; -} - -// A visitor which collects ASTInfo for most nodes in the AST. -class ASTInfoVisitor { - private readonly _astInfos: ASTInfo[] = []; - public getASTInfoForRange(sourceRange: SourceRange): ASTInfo | null { - // HACK(albrow): Sometimes the source range doesn't exactly match that - // of astInfo. To work around that we try with a +/-1 offset on - // end.column. If nothing matches even with the offset, we return null. - const offset = { - start: { - line: 0, - column: 0, - }, - end: { - line: 0, - column: 0, - }, - }; - let astInfo = this._getASTInfoForRange(sourceRange, offset); - if (astInfo !== null) { - return astInfo; - } - offset.end.column += 1; - astInfo = this._getASTInfoForRange(sourceRange, offset); - if (astInfo !== null) { - return astInfo; - } - offset.end.column -= 2; - astInfo = this._getASTInfoForRange(sourceRange, offset); - if (astInfo !== null) { - return astInfo; - } - return null; - } - public ContractDefinition(ast: Parser.ContractDefinition): void { - this._visitContractDefinition(ast); - } - public IfStatement(ast: Parser.IfStatement): void { - this._visitStatement(ast); - } - 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 EmitStatement(ast: any /* TODO: Parser.EmitStatement */): 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 ModifierInvocation(ast: Parser.ModifierInvocation): void { - const BUILTIN_MODIFIERS = ['public', 'view', 'payable', 'external', 'internal', 'pure', 'constant']; - if (!_.includes(BUILTIN_MODIFIERS, ast.name)) { - this._visitStatement(ast); - } - } - private _visitStatement(ast: Parser.ASTNode): void { - this._astInfos.push({ - type: ast.type, - node: ast, - name: null, - range: ast.loc, - }); - } - private _visitFunctionLikeDefinition(ast: Parser.ModifierDefinition | Parser.FunctionDefinition): void { - this._astInfos.push({ - type: ast.type, - node: ast, - name: ast.name, - range: ast.loc, - }); - } - private _visitContractDefinition(ast: Parser.ContractDefinition): void { - this._astInfos.push({ - type: ast.type, - node: ast, - name: ast.name, - range: ast.loc, - }); - } - private _getASTInfoForRange(sourceRange: SourceRange, offset: SingleFileSourceRange): ASTInfo | null { - const offsetSourceRange = { - ...sourceRange, - location: { - start: { - line: sourceRange.location.start.line + offset.start.line, - column: sourceRange.location.start.column + offset.start.column, - }, - end: { - line: sourceRange.location.end.line + offset.end.line, - column: sourceRange.location.end.column + offset.end.column, - }, - }, - }; - for (const astInfo of this._astInfos) { - const astInfoRange = astInfo.range as SingleFileSourceRange; - if ( - astInfoRange.start.column === offsetSourceRange.location.start.column && - astInfoRange.start.line === offsetSourceRange.location.start.line && - astInfoRange.end.column === offsetSourceRange.location.end.column && - astInfoRange.end.line === offsetSourceRange.location.end.line - ) { - return astInfo; - } - } - return null; - } -} diff --git a/packages/sol-trace-based-tools-common/src/globals.d.ts b/packages/sol-trace-based-tools-common/src/globals.d.ts deleted file mode 100644 index e799b3529..000000000 --- a/packages/sol-trace-based-tools-common/src/globals.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -// tslint:disable:completed-docs -declare module '*.json' { - const json: any; - /* tslint:disable */ - export default json; - /* tslint:enable */ -} diff --git a/packages/sol-trace-based-tools-common/src/index.ts b/packages/sol-trace-based-tools-common/src/index.ts deleted file mode 100644 index 413e5305e..000000000 --- a/packages/sol-trace-based-tools-common/src/index.ts +++ /dev/null @@ -1,39 +0,0 @@ -export { SolCompilerArtifactAdapter } from './artifact_adapters/sol_compiler_artifact_adapter'; -export { TruffleArtifactAdapter } from './artifact_adapters/truffle_artifact_adapter'; -export { AbstractArtifactAdapter } from './artifact_adapters/abstract_artifact_adapter'; - -export { - ContractData, - EvmCallStack, - SourceRange, - SourceSnippet, - StatementCoverage, - StatementDescription, - BranchCoverage, - BranchDescription, - Subtrace, - TraceInfo, - Coverage, - LineColumn, - LineCoverage, - FunctionCoverage, - FunctionDescription, - SingleFileSourceRange, - BranchMap, - EvmCallStackEntry, - FnMap, - LocationByOffset, - StatementMap, - TraceInfoBase, - TraceInfoExistingContract, - TraceInfoNewContract, -} from './types'; -export { collectCoverageEntries } from './collect_coverage_entries'; -export { TraceCollector, SingleFileSubtraceHandler } from './trace_collector'; -export { TraceInfoSubprovider } from './trace_info_subprovider'; -export { utils } from './utils'; -export { constants } from './constants'; -export { parseSourceMap } from './source_maps'; -export { getSourceRangeSnippet } from './get_source_range_snippet'; -export { getRevertTrace } from './revert_trace'; -export { TraceCollectionSubprovider } from './trace_collection_subprovider'; diff --git a/packages/sol-trace-based-tools-common/src/instructions.ts b/packages/sol-trace-based-tools-common/src/instructions.ts deleted file mode 100644 index 40987dbe5..000000000 --- a/packages/sol-trace-based-tools-common/src/instructions.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { constants } from './constants'; - -const isPush = (inst: number) => inst >= constants.PUSH1 && inst <= constants.PUSH32; - -const pushDataLength = (inst: number) => inst - constants.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; -}; diff --git a/packages/sol-trace-based-tools-common/src/revert_trace.ts b/packages/sol-trace-based-tools-common/src/revert_trace.ts deleted file mode 100644 index 4d474120c..000000000 --- a/packages/sol-trace-based-tools-common/src/revert_trace.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { logUtils } from '@0x/utils'; -import { OpCode, StructLog } from 'ethereum-types'; - -import * as _ from 'lodash'; - -import { EvmCallStack } from './types'; -import { utils } from './utils'; - -/** - * Converts linear trace to a call stack by following calls and returns - * @param structLogs Linear trace - * @param startAddress The address of initial context - */ -export function getRevertTrace(structLogs: StructLog[], startAddress: string): EvmCallStack { - const evmCallStack: EvmCallStack = []; - const addressStack = [startAddress]; - if (_.isEmpty(structLogs)) { - return []; - } - const normalizedStructLogs = utils.normalizeStructLogs(structLogs); - // tslint:disable-next-line:prefer-for-of - for (let i = 0; i < normalizedStructLogs.length; i++) { - const structLog = normalizedStructLogs[i]; - if (structLog.depth !== addressStack.length - 1) { - throw new Error("Malformed trace. Trace depth doesn't match call stack depth"); - } - // After that check we have a guarantee that call stack is never empty - // If it would: callStack.length - 1 === structLog.depth === -1 - // That means that we can always safely pop from it - - if (utils.isCallLike(structLog.op)) { - const currentAddress = _.last(addressStack) as string; - const jumpAddressOffset = 1; - const newAddress = utils.getAddressFromStackEntry( - structLog.stack[structLog.stack.length - jumpAddressOffset - 1], - ); - - // Sometimes calls don't change the execution context (current address). When we do a transfer to an - // externally owned account - it does the call and immediately returns because there is no fallback - // function. We manually check if the call depth had changed to handle that case. - const nextStructLog = normalizedStructLogs[i + 1]; - if (nextStructLog.depth !== structLog.depth) { - addressStack.push(newAddress); - evmCallStack.push({ - address: currentAddress, - structLog, - }); - } - } else if (utils.isEndOpcode(structLog.op) && structLog.op !== OpCode.Revert) { - // Just like with calls, sometimes returns/stops don't change the execution context (current address). - const nextStructLog = normalizedStructLogs[i + 1]; - if (_.isUndefined(nextStructLog) || nextStructLog.depth !== structLog.depth) { - evmCallStack.pop(); - addressStack.pop(); - } - if (structLog.op === OpCode.SelfDestruct) { - // After contract execution, we look at all sub-calls to external contracts, and for each one, fetch - // the bytecode and compute the coverage for the call. If the contract is destroyed with a call - // to `selfdestruct`, we are unable to fetch it's bytecode and compute coverage. - // TODO: Refactor this logic to fetch the sub-called contract bytecode before the selfdestruct is called - // in order to handle this edge-case. - logUtils.warn( - "Detected a selfdestruct. We currently do not support that scenario. We'll just skip the trace part for a destructed contract", - ); - } - } else if (structLog.op === OpCode.Revert) { - evmCallStack.push({ - address: _.last(addressStack) as string, - structLog, - }); - return evmCallStack; - } else if (structLog.op === OpCode.Create) { - // TODO: Extract the new contract address from the stack and handle that scenario - logUtils.warn( - "Detected a contract created from within another contract. We currently do not support that scenario. We'll just skip that trace", - ); - return []; - } else { - if (structLog !== _.last(normalizedStructLogs)) { - const nextStructLog = normalizedStructLogs[i + 1]; - if (nextStructLog.depth === structLog.depth) { - continue; - } else if (nextStructLog.depth === structLog.depth - 1) { - addressStack.pop(); - } else { - throw new Error('Malformed trace. Unexpected call depth change'); - } - } - } - } - if (evmCallStack.length !== 0) { - logUtils.warn('Malformed trace. Call stack non empty at the end. (probably out of gas)'); - } - return []; -} diff --git a/packages/sol-trace-based-tools-common/src/source_maps.ts b/packages/sol-trace-based-tools-common/src/source_maps.ts deleted file mode 100644 index af0fb4035..000000000 --- a/packages/sol-trace-based-tools-common/src/source_maps.ts +++ /dev/null @@ -1,91 +0,0 @@ -import * as _ from 'lodash'; - -import { getPcToInstructionIndexMapping } from './instructions'; -import { LocationByOffset, SourceRange } from './types'; - -const RADIX = 10; - -export interface SourceLocation { - offset: number; - length: number; - fileIndex: number; -} - -/** - * Receives a string with newlines and returns a map of byte offset to LineColumn - * @param str A string to process - */ -export function getLocationByOffset(str: string): LocationByOffset { - const locationByOffset: LocationByOffset = { 0: { line: 1, column: 0 } }; - let currentOffset = 0; - for (const char of str.split('')) { - const location = locationByOffset[currentOffset]; - const isNewline = char === '\n'; - locationByOffset[currentOffset + 1] = { - 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 - * @param sourceCodes sources contents - * @param srcMap source map string - * @param bytecodeHex contract bytecode - * @param sources sources file names - */ -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, s => (_.isUndefined(s) ? {} : getLocationByOffset(s))); - const entries = srcMap.split(';'); - let lastParsedEntry: SourceLocation = {} as any; - const instructionIndexToSourceRange: { [instructionIndex: number]: SourceRange } = {}; - _.each(entries, (entry: string, i: number) => { - // tslint:disable-next-line:no-unused-variable - 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 && !_.isUndefined(locationByOffsetByFileIndex[parsedEntry.fileIndex])) { - const sourceRange = { - location: { - start: locationByOffsetByFileIndex[parsedEntry.fileIndex][parsedEntry.offset], - end: locationByOffsetByFileIndex[parsedEntry.fileIndex][parsedEntry.offset + parsedEntry.length], - }, - 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; -} diff --git a/packages/sol-trace-based-tools-common/src/trace.ts b/packages/sol-trace-based-tools-common/src/trace.ts deleted file mode 100644 index 770080af3..000000000 --- a/packages/sol-trace-based-tools-common/src/trace.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { logUtils } from '@0x/utils'; -import { OpCode, StructLog } from 'ethereum-types'; -import * as _ from 'lodash'; - -import { utils } from './utils'; - -export interface TraceByContractAddress { - [contractAddress: string]: StructLog[]; -} - -/** - * Converts linear stack trace to `TraceByContractAddress`. - * @param structLogs stack trace - * @param startAddress initial context address - */ -export function getTracesByContractAddress(structLogs: StructLog[], startAddress: string): TraceByContractAddress { - const traceByContractAddress: TraceByContractAddress = {}; - let currentTraceSegment = []; - const addressStack = [startAddress]; - if (_.isEmpty(structLogs)) { - return traceByContractAddress; - } - const normalizedStructLogs = utils.normalizeStructLogs(structLogs); - // tslint:disable-next-line:prefer-for-of - for (let i = 0; i < normalizedStructLogs.length; i++) { - const structLog = normalizedStructLogs[i]; - if (structLog.depth !== addressStack.length - 1) { - throw new Error("Malformed trace. Trace depth doesn't match call stack depth"); - } - // After that check we have a guarantee that call stack is never empty - // If it would: callStack.length - 1 === structLog.depth === -1 - // That means that we can always safely pop from it - currentTraceSegment.push(structLog); - - if (utils.isCallLike(structLog.op)) { - const currentAddress = _.last(addressStack) as string; - const jumpAddressOffset = 1; - const newAddress = utils.getAddressFromStackEntry( - structLog.stack[structLog.stack.length - jumpAddressOffset - 1], - ); - - // Sometimes calls don't change the execution context (current address). When we do a transfer to an - // externally owned account - it does the call and immediately returns because there is no fallback - // function. We manually check if the call depth had changed to handle that case. - const nextStructLog = normalizedStructLogs[i + 1]; - if (nextStructLog.depth !== structLog.depth) { - addressStack.push(newAddress); - traceByContractAddress[currentAddress] = (traceByContractAddress[currentAddress] || []).concat( - currentTraceSegment, - ); - currentTraceSegment = []; - } - } else if (utils.isEndOpcode(structLog.op)) { - const currentAddress = addressStack.pop() as string; - traceByContractAddress[currentAddress] = (traceByContractAddress[currentAddress] || []).concat( - currentTraceSegment, - ); - currentTraceSegment = []; - if (structLog.op === OpCode.SelfDestruct) { - // After contract execution, we look at all sub-calls to external contracts, and for each one, fetch - // the bytecode and compute the coverage for the call. If the contract is destroyed with a call - // to `selfdestruct`, we are unable to fetch it's bytecode and compute coverage. - // TODO: Refactor this logic to fetch the sub-called contract bytecode before the selfdestruct is called - // in order to handle this edge-case. - logUtils.warn( - "Detected a selfdestruct. We currently do not support that scenario. We'll just skip the trace part for a destructed contract", - ); - } - } else if (structLog.op === OpCode.Create) { - // TODO: Extract the new contract address from the stack and handle that scenario - logUtils.warn( - "Detected a contract created from within another contract. We currently do not support that scenario. We'll just skip that trace", - ); - return traceByContractAddress; - } else { - if (structLog !== _.last(normalizedStructLogs)) { - const nextStructLog = normalizedStructLogs[i + 1]; - if (nextStructLog.depth === structLog.depth) { - continue; - } else if (nextStructLog.depth === structLog.depth - 1) { - const currentAddress = addressStack.pop() as string; - traceByContractAddress[currentAddress] = (traceByContractAddress[currentAddress] || []).concat( - currentTraceSegment, - ); - currentTraceSegment = []; - } else { - throw new Error('Malformed trace. Unexpected call depth change'); - } - } - } - } - if (addressStack.length !== 0) { - logUtils.warn('Malformed trace. Call stack non empty at the end'); - } - if (currentTraceSegment.length !== 0) { - const currentAddress = addressStack.pop() as string; - traceByContractAddress[currentAddress] = (traceByContractAddress[currentAddress] || []).concat( - currentTraceSegment, - ); - currentTraceSegment = []; - logUtils.warn('Malformed trace. Current trace segment non empty at the end'); - } - return traceByContractAddress; -} diff --git a/packages/sol-trace-based-tools-common/src/trace_collection_subprovider.ts b/packages/sol-trace-based-tools-common/src/trace_collection_subprovider.ts deleted file mode 100644 index 25e38768d..000000000 --- a/packages/sol-trace-based-tools-common/src/trace_collection_subprovider.ts +++ /dev/null @@ -1,188 +0,0 @@ -import { BlockchainLifecycle } from '@0x/dev-utils'; -import { Callback, ErrorCallback, NextCallback, Subprovider } from '@0x/subproviders'; -import { CallDataRPC, marshaller, Web3Wrapper } from '@0x/web3-wrapper'; -import { JSONRPCRequestPayload, Provider, TxData } from 'ethereum-types'; -import * as _ from 'lodash'; -import { Lock } from 'semaphore-async-await'; - -import { constants } from './constants'; -import { BlockParamLiteral } from './types'; - -interface MaybeFakeTxData extends TxData { - isFakeTransaction?: boolean; -} - -const BLOCK_GAS_LIMIT = 6000000; - -export interface TraceCollectionSubproviderConfig { - shouldCollectTransactionTraces: boolean; - shouldCollectCallTraces: boolean; - shouldCollectGasEstimateTraces: boolean; -} - -// Because there is no notion of a call trace in the Ethereum rpc - we collect them in a 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 avoid influencing test behaviour. - -/** - * This class implements the [web3-provider-engine](https://github.com/MetaMask/provider-engine) subprovider interface. - * It collects traces of all transactions that were sent and all calls that were executed through JSON RPC. It must - * be extended by implementing the _recordTxTraceAsync method which is called for every transaction. - */ -export abstract class TraceCollectionSubprovider extends Subprovider { - protected _web3Wrapper!: Web3Wrapper; - // Lock is used to not accept normal transactions while doing call/snapshot magic because they'll be reverted later otherwise - private readonly _lock = new Lock(); - private readonly _defaultFromAddress: string; - private _isEnabled = true; - private readonly _config: TraceCollectionSubproviderConfig; - /** - * Instantiates a TraceCollectionSubprovider instance - * @param defaultFromAddress default from address to use when sending transactions - */ - constructor(defaultFromAddress: string, config: TraceCollectionSubproviderConfig) { - super(); - this._defaultFromAddress = defaultFromAddress; - this._config = config; - } - /** - * Starts trace collection - */ - public start(): void { - this._isEnabled = true; - } - /** - * Stops trace collection - */ - public stop(): void { - this._isEnabled = false; - } - /** - * This method conforms to the web3-provider-engine interface. - * It is called internally by the ProviderEngine when it is this subproviders - * turn to handle a JSON RPC request. - * @param payload JSON RPC payload - * @param next Callback to call if this subprovider decides not to handle the request - * @param _end Callback to call if subprovider handled the request and wants to pass back the request. - */ - // tslint:disable-next-line:prefer-function-over-method async-suffix - public async handleRequest(payload: JSONRPCRequestPayload, next: NextCallback, _end: ErrorCallback): Promise { - if (this._isEnabled) { - switch (payload.method) { - case 'eth_sendTransaction': - if (!this._config.shouldCollectTransactionTraces) { - next(); - } else { - const txData = payload.params[0]; - next(this._onTransactionSentAsync.bind(this, txData)); - } - return; - - case 'eth_call': - if (!this._config.shouldCollectCallTraces) { - next(); - } else { - const callData = payload.params[0]; - next(this._onCallOrGasEstimateExecutedAsync.bind(this, callData)); - } - return; - - case 'eth_estimateGas': - if (!this._config.shouldCollectGasEstimateTraces) { - next(); - } else { - const estimateGasData = payload.params[0]; - next(this._onCallOrGasEstimateExecutedAsync.bind(this, estimateGasData)); - } - return; - - default: - next(); - return; - } - } else { - next(); - return; - } - } - /** - * Set's the subprovider's engine to the ProviderEngine it is added to. - * This is only called within the ProviderEngine source code, do not call - * directly. - * @param engine The ProviderEngine this subprovider is added to - */ - public setEngine(engine: Provider): void { - super.setEngine(engine); - this._web3Wrapper = new Web3Wrapper(engine); - } - protected abstract async _recordTxTraceAsync( - address: string, - data: string | undefined, - txHash: string, - ): Promise; - private async _onTransactionSentAsync( - txData: MaybeFakeTxData, - err: Error | null, - txHash: string | undefined, - cb: Callback, - ): Promise { - if (!txData.isFakeTransaction) { - // This transaction is a usual transaction. Not a call executed as one. - // And we don't want it to be executed within a snapshotting period - await this._lock.acquire(); - } - const NULL_ADDRESS = '0x0'; - if (_.isNull(err)) { - const toAddress = - _.isUndefined(txData.to) || txData.to === NULL_ADDRESS ? constants.NEW_CONTRACT : txData.to; - await this._recordTxTraceAsync(toAddress, txData.data, txHash as string); - } else { - const latestBlock = await this._web3Wrapper.getBlockWithTransactionDataAsync(BlockParamLiteral.Latest); - const transactions = latestBlock.transactions; - for (const transaction of transactions) { - const toAddress = - _.isUndefined(txData.to) || txData.to === NULL_ADDRESS ? constants.NEW_CONTRACT : txData.to; - await this._recordTxTraceAsync(toAddress, transaction.input, transaction.hash); - } - } - if (!txData.isFakeTransaction) { - // This transaction is a usual transaction. 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 _onCallOrGasEstimateExecutedAsync( - callData: Partial, - _err: Error | null, - _callResult: string, - cb: Callback, - ): Promise { - await this._recordCallOrGasEstimateTraceAsync(callData); - cb(); - } - private async _recordCallOrGasEstimateTraceAsync(callData: Partial): Promise { - // 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 blockchainLifecycle = new BlockchainLifecycle(this._web3Wrapper); - await blockchainLifecycle.startAsync(); - const fakeTxData = { - gas: BLOCK_GAS_LIMIT.toString(16), // tslint:disable-line:custom-no-magic-numbers - isFakeTransaction: true, // This transaction (and only it) is allowed to come through when the lock is locked - ...callData, - from: callData.from || this._defaultFromAddress, - }; - try { - const txData = marshaller.unmarshalTxData(fakeTxData); - const txHash = await this._web3Wrapper.sendTransactionAsync(txData); - await this._web3Wrapper.awaitTransactionMinedAsync(txHash, 0); - } catch (err) { - // TODO(logvinov) Check that transaction failed and not some other exception - // Even if this transaction failed - we've already recorded it's trace. - _.noop(); - } - await blockchainLifecycle.revertAsync(); - this._lock.release(); - } -} diff --git a/packages/sol-trace-based-tools-common/src/trace_collector.ts b/packages/sol-trace-based-tools-common/src/trace_collector.ts deleted file mode 100644 index 8ba71f4df..000000000 --- a/packages/sol-trace-based-tools-common/src/trace_collector.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { promisify } from '@0x/utils'; -import { stripHexPrefix } from 'ethereumjs-util'; -import * as fs from 'fs'; -import { Collector } from 'istanbul'; -import * as _ from 'lodash'; -import { getLogger, levels, Logger } from 'loglevel'; -import * as mkdirp from 'mkdirp'; - -import { AbstractArtifactAdapter } from './artifact_adapters/abstract_artifact_adapter'; -import { constants } from './constants'; -import { parseSourceMap } from './source_maps'; -import { - ContractData, - Coverage, - SourceRange, - Subtrace, - TraceInfo, - TraceInfoExistingContract, - TraceInfoNewContract, -} from './types'; -import { utils } from './utils'; - -const mkdirpAsync = promisify(mkdirp); - -export type SingleFileSubtraceHandler = ( - contractData: ContractData, - subtrace: Subtrace, - pcToSourceRange: { [programCounter: number]: SourceRange }, - fileIndex: number, -) => Coverage; - -/** - * TraceCollector is used by CoverageSubprovider to compute code coverage based on collected trace data. - */ -export class TraceCollector { - private readonly _artifactAdapter: AbstractArtifactAdapter; - private readonly _logger: Logger; - private _contractsData!: ContractData[]; - private readonly _collector = new Collector(); - private readonly _singleFileSubtraceHandler: SingleFileSubtraceHandler; - - /** - * Instantiates a TraceCollector instance - * @param artifactAdapter Adapter for used artifacts format (0x, truffle, giveth, etc.) - * @param isVerbose If true, we will log any unknown transactions. Otherwise we will ignore them - * @param singleFileSubtraceHandler A handler function for computing partial coverage for a single file & subtrace - */ - constructor( - artifactAdapter: AbstractArtifactAdapter, - isVerbose: boolean, - singleFileSubtraceHandler: SingleFileSubtraceHandler, - ) { - this._artifactAdapter = artifactAdapter; - this._logger = getLogger('sol-trace-based-tools-common'); - this._logger.setLevel(isVerbose ? levels.TRACE : levels.ERROR); - this._singleFileSubtraceHandler = singleFileSubtraceHandler; - } - public async writeOutputAsync(): Promise { - const finalCoverage = this._collector.getFinalCoverage(); - const stringifiedCoverage = JSON.stringify(finalCoverage, null, '\t'); - await mkdirpAsync('coverage'); - fs.writeFileSync('coverage/coverage.json', stringifiedCoverage); - } - public async computeSingleTraceCoverageAsync(traceInfo: TraceInfo): Promise { - if (_.isUndefined(this._contractsData)) { - this._contractsData = await this._artifactAdapter.collectContractsDataAsync(); - } - const isContractCreation = traceInfo.address === constants.NEW_CONTRACT; - const bytecode = isContractCreation - ? (traceInfo as TraceInfoNewContract).bytecode - : (traceInfo as TraceInfoExistingContract).runtimeBytecode; - const contractData = utils.getContractDataIfExists(this._contractsData, bytecode); - if (_.isUndefined(contractData)) { - const errMsg = isContractCreation - ? `Unknown contract creation transaction` - : `Transaction to an unknown address: ${traceInfo.address}`; - this._logger.warn(errMsg); - return; - } - const bytecodeHex = stripHexPrefix(bytecode); - const sourceMap = isContractCreation ? contractData.sourceMap : contractData.sourceMapRuntime; - const pcToSourceRange = parseSourceMap(contractData.sourceCodes, sourceMap, bytecodeHex, contractData.sources); - for (let fileIndex = 0; fileIndex < contractData.sources.length; fileIndex++) { - const singleFileCoverageForTrace = this._singleFileSubtraceHandler( - contractData, - traceInfo.subtrace, - pcToSourceRange, - fileIndex, - ); - this._collector.add(singleFileCoverageForTrace); - } - } -} diff --git a/packages/sol-trace-based-tools-common/src/trace_info_subprovider.ts b/packages/sol-trace-based-tools-common/src/trace_info_subprovider.ts deleted file mode 100644 index 635a68f58..000000000 --- a/packages/sol-trace-based-tools-common/src/trace_info_subprovider.ts +++ /dev/null @@ -1,59 +0,0 @@ -import * as _ from 'lodash'; - -import { constants } from './constants'; -import { getTracesByContractAddress } from './trace'; -import { TraceCollectionSubprovider } from './trace_collection_subprovider'; -import { TraceInfo, TraceInfoExistingContract, TraceInfoNewContract } from './types'; - -// TraceInfoSubprovider is extended by subproviders which need to work with one -// TraceInfo at a time. It has one abstract method: _handleTraceInfoAsync, which -// is called for each TraceInfo. -export abstract class TraceInfoSubprovider extends TraceCollectionSubprovider { - protected abstract _handleTraceInfoAsync(traceInfo: TraceInfo): Promise; - protected async _recordTxTraceAsync(address: string, data: string | undefined, txHash: string): Promise { - await this._web3Wrapper.awaitTransactionMinedAsync(txHash, 0); - const trace = await this._web3Wrapper.getTransactionTraceAsync(txHash, { - disableMemory: true, - disableStack: false, - disableStorage: true, - }); - const tracesByContractAddress = getTracesByContractAddress(trace.structLogs, address); - const subcallAddresses = _.keys(tracesByContractAddress); - if (address === constants.NEW_CONTRACT) { - for (const subcallAddress of subcallAddresses) { - let traceInfo: TraceInfoNewContract | TraceInfoExistingContract; - if (subcallAddress === 'NEW_CONTRACT') { - const traceForThatSubcall = tracesByContractAddress[subcallAddress]; - traceInfo = { - subtrace: traceForThatSubcall, - txHash, - address: subcallAddress, - bytecode: data as string, - }; - } else { - const runtimeBytecode = await this._web3Wrapper.getContractCodeAsync(subcallAddress); - const traceForThatSubcall = tracesByContractAddress[subcallAddress]; - traceInfo = { - subtrace: traceForThatSubcall, - txHash, - address: subcallAddress, - runtimeBytecode, - }; - } - await this._handleTraceInfoAsync(traceInfo); - } - } else { - for (const subcallAddress of subcallAddresses) { - const runtimeBytecode = await this._web3Wrapper.getContractCodeAsync(subcallAddress); - const traceForThatSubcall = tracesByContractAddress[subcallAddress]; - const traceInfo: TraceInfoExistingContract = { - subtrace: traceForThatSubcall, - txHash, - address: subcallAddress, - runtimeBytecode, - }; - await this._handleTraceInfoAsync(traceInfo); - } - } - } -} diff --git a/packages/sol-trace-based-tools-common/src/types.ts b/packages/sol-trace-based-tools-common/src/types.ts deleted file mode 100644 index 54ade0400..000000000 --- a/packages/sol-trace-based-tools-common/src/types.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { StructLog } from 'ethereum-types'; -import * as Parser from 'solidity-parser-antlr'; - -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]: number; -} - -export interface FunctionCoverage { - [functionId: string]: number; -} - -export interface StatementCoverage { - [statementId: string]: number; -} - -export interface BranchCoverage { - [branchId: string]: number[]; -} - -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[]; - sources: string[]; -} - -// Part of the trace executed within the same context -export type Subtrace = StructLog[]; - -export interface TraceInfoBase { - subtrace: Subtrace; - 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; - -export enum BlockParamLiteral { - Latest = 'latest', -} - -export interface EvmCallStackEntry { - structLog: StructLog; - address: string; -} - -export type EvmCallStack = EvmCallStackEntry[]; - -export interface SourceSnippet { - source: string; - fileName: string; - type: string; - node: Parser.ASTNode; - name: string | null; - range: SingleFileSourceRange; -} diff --git a/packages/sol-trace-based-tools-common/src/utils.ts b/packages/sol-trace-based-tools-common/src/utils.ts deleted file mode 100644 index d8bc65e73..000000000 --- a/packages/sol-trace-based-tools-common/src/utils.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { addressUtils, BigNumber } from '@0x/utils'; -import { OpCode, StructLog } from 'ethereum-types'; -import { addHexPrefix } from 'ethereumjs-util'; -import * as _ from 'lodash'; - -import { ContractData, LineColumn, SingleFileSourceRange } from './types'; - -// This is the minimum length of valid contract bytecode. The Solidity compiler -// metadata is 86 bytes. If you add the '0x' prefix, we get 88. -const MIN_CONTRACT_BYTECODE_LENGTH = 88; - -export const utils = { - compareLineColumn(lhs: LineColumn, rhs: LineColumn): number { - return lhs.line !== rhs.line ? lhs.line - rhs.line : lhs.column - rhs.column; - }, - removeHexPrefix(hex: string): string { - const hexPrefix = '0x'; - return hex.startsWith(hexPrefix) ? hex.slice(hexPrefix.length) : hex; - }, - isRangeInside(childRange: SingleFileSourceRange, parentRange: SingleFileSourceRange): boolean { - return ( - utils.compareLineColumn(parentRange.start, childRange.start) <= 0 && - utils.compareLineColumn(childRange.end, parentRange.end) <= 0 - ); - }, - bytecodeToBytecodeRegex(bytecode: string): string { - const bytecodeRegex = bytecode - // Library linking placeholder: __ConvertLib____________________________ - .replace(/_.*_/, '.*') - // Last 86 characters is solidity compiler metadata that's different between compilations - .replace(/.{86}$/, '') - // Libraries contain their own address at the beginning of the code and it's impossible to know it in advance - .replace(/^0x730000000000000000000000000000000000000000/, '0x73........................................'); - // HACK: Node regexes can't be longer that 32767 characters. Contracts bytecode can. We just truncate the regexes. It's safe in practice. - const MAX_REGEX_LENGTH = 32767; - const truncatedBytecodeRegex = bytecodeRegex.slice(0, MAX_REGEX_LENGTH); - return truncatedBytecodeRegex; - }, - getContractDataIfExists(contractsData: ContractData[], bytecode: string): ContractData | undefined { - if (!bytecode.startsWith('0x')) { - throw new Error(`0x hex prefix missing: ${bytecode}`); - } - const contractData = _.find(contractsData, contractDataCandidate => { - const bytecodeRegex = utils.bytecodeToBytecodeRegex(contractDataCandidate.bytecode); - // If the bytecode is less than the minimum length, we are probably - // dealing with an interface. This isn't what we're looking for. - if (bytecodeRegex.length < MIN_CONTRACT_BYTECODE_LENGTH) { - return false; - } - const runtimeBytecodeRegex = utils.bytecodeToBytecodeRegex(contractDataCandidate.runtimeBytecode); - if (runtimeBytecodeRegex.length < MIN_CONTRACT_BYTECODE_LENGTH) { - return false; - } - // We use that function to find by bytecode or runtimeBytecode. Those are quasi-random strings so - // collisions are practically impossible and it allows us to reuse that code - return !_.isNull(bytecode.match(bytecodeRegex)) || !_.isNull(bytecode.match(runtimeBytecodeRegex)); - }); - return contractData; - }, - isCallLike(op: OpCode): boolean { - return _.includes([OpCode.CallCode, OpCode.StaticCall, OpCode.Call, OpCode.DelegateCall], op); - }, - isEndOpcode(op: OpCode): boolean { - return _.includes([OpCode.Return, OpCode.Stop, OpCode.Revert, OpCode.Invalid, OpCode.SelfDestruct], op); - }, - getAddressFromStackEntry(stackEntry: string): string { - const hexBase = 16; - return addressUtils.padZeros(new BigNumber(addHexPrefix(stackEntry)).toString(hexBase)); - }, - normalizeStructLogs(structLogs: StructLog[]): StructLog[] { - if (structLogs[0].depth === 1) { - // Geth uses 1-indexed depth counter whilst ganache starts from 0 - const newStructLogs = _.map(structLogs, structLog => ({ - ...structLog, - depth: structLog.depth - 1, - })); - return newStructLogs; - } - return structLogs; - }, - getRange(sourceCode: string, range: SingleFileSourceRange): string { - const lines = sourceCode.split('\n').slice(range.start.line - 1, range.end.line); - lines[lines.length - 1] = lines[lines.length - 1].slice(0, range.end.column); - lines[0] = lines[0].slice(range.start.column); - return lines.join('\n'); - }, -}; diff --git a/packages/sol-trace-based-tools-common/test/collect_coverage_entries_test.ts b/packages/sol-trace-based-tools-common/test/collect_coverage_entries_test.ts deleted file mode 100644 index 7832ec316..000000000 --- a/packages/sol-trace-based-tools-common/test/collect_coverage_entries_test.ts +++ /dev/null @@ -1,155 +0,0 @@ -import * as chai from 'chai'; -import * as fs from 'fs'; -import * as _ from 'lodash'; -import 'mocha'; -import * as path from 'path'; - -import { collectCoverageEntries } from '../src/collect_coverage_entries'; -import { utils } from '../src/utils'; - -const expect = chai.expect; - -describe('Collect coverage entries', () => { - describe('#collectCoverageEntries', () => { - it('correctly collects coverage entries for Simplest contract', () => { - const simplestContractBaseName = 'Simplest.sol'; - const simplestContractFileName = path.resolve(__dirname, 'fixtures/contracts', simplestContractBaseName); - const simplestContract = fs.readFileSync(simplestContractFileName).toString(); - const coverageEntries = collectCoverageEntries(simplestContract); - expect(coverageEntries.fnMap).to.be.deep.equal({}); - expect(coverageEntries.branchMap).to.be.deep.equal({}); - expect(coverageEntries.statementMap).to.be.deep.equal({}); - expect(coverageEntries.modifiersStatementIds).to.be.deep.equal([]); - }); - it('correctly collects coverage entries for SimpleStorage contract', () => { - const simpleStorageContractBaseName = 'SimpleStorage.sol'; - const simpleStorageContractFileName = path.resolve( - __dirname, - 'fixtures/contracts', - simpleStorageContractBaseName, - ); - const simpleStorageContract = fs.readFileSync(simpleStorageContractFileName).toString(); - const coverageEntries = collectCoverageEntries(simpleStorageContract); - const fnIds = _.keys(coverageEntries.fnMap); - expect(coverageEntries.fnMap[fnIds[0]].name).to.be.equal('set'); - // tslint:disable-next-line:custom-no-magic-numbers - expect(coverageEntries.fnMap[fnIds[0]].line).to.be.equal(5); - const setFunction = `function set(uint x) { - storedData = x; - }`; - expect(utils.getRange(simpleStorageContract, coverageEntries.fnMap[fnIds[0]].loc)).to.be.equal(setFunction); - expect(coverageEntries.fnMap[fnIds[1]].name).to.be.equal('get'); - // tslint:disable-next-line:custom-no-magic-numbers - expect(coverageEntries.fnMap[fnIds[1]].line).to.be.equal(8); - const getFunction = `function get() constant returns (uint retVal) { - return storedData; - }`; - expect(utils.getRange(simpleStorageContract, coverageEntries.fnMap[fnIds[1]].loc)).to.be.equal(getFunction); - expect(coverageEntries.branchMap).to.be.deep.equal({}); - const statementIds = _.keys(coverageEntries.statementMap); - expect(utils.getRange(simpleStorageContract, coverageEntries.statementMap[statementIds[1]])).to.be.equal( - 'storedData = x', - ); - expect(utils.getRange(simpleStorageContract, coverageEntries.statementMap[statementIds[3]])).to.be.equal( - 'return storedData;', - ); - expect(coverageEntries.modifiersStatementIds).to.be.deep.equal([]); - }); - it('correctly collects coverage entries for AllSolidityFeatures contract', () => { - const simpleStorageContractBaseName = 'AllSolidityFeatures.sol'; - const simpleStorageContractFileName = path.resolve( - __dirname, - 'fixtures/contracts', - simpleStorageContractBaseName, - ); - const simpleStorageContract = fs.readFileSync(simpleStorageContractFileName).toString(); - const coverageEntries = collectCoverageEntries(simpleStorageContract); - const fnDescriptions = _.values(coverageEntries.fnMap); - const fnNames = _.map(fnDescriptions, fnDescription => fnDescription.name); - const expectedFnNames = [ - 'f', - 'c', - 'test', - 'getChoice', - 'Base', - 'Derived', - 'f', - 'f', - '', - 'g', - 'setData', - 'getData', - 'sendHalf', - 'insert', - 'remove', - 'contains', - 'iterate_start', - 'iterate_valid', - 'iterate_advance', - 'iterate_get', - 'insert', - 'sum', - 'restricted', - 'DualIndex', - 'set', - 'transfer_ownership', - 'lookup', - '', - '', - 'sum', - 'someFunction', - 'fun', - 'at', - 'test', - 'get', - 'returnNumber', - 'alloc', - 'ham', - 'getMyTuple', - 'ham', - 'abstain', - 'foobar', - 'foobar', - 'a', - ]; - expect(fnNames).to.be.deep.equal(expectedFnNames); - - const branchDescriptions = _.values(coverageEntries.branchMap); - const branchLines = _.map(branchDescriptions, branchDescription => branchDescription.line); - // tslint:disable-next-line:custom-no-magic-numbers - expect(branchLines).to.be.deep.equal([94, 115, 119, 130, 151, 187]); - const branchTypes = _.map(branchDescriptions, branchDescription => branchDescription.type); - expect(branchTypes).to.be.deep.equal(['if', 'if', 'if', 'if', 'binary-expr', 'if']); - }); - - it('correctly ignores all coverage entries for Ignore contract', () => { - const solcovIgnoreContractBaseName = 'SolcovIgnore.sol'; - const solcovIgnoreContractFileName = path.resolve( - __dirname, - 'fixtures/contracts', - solcovIgnoreContractBaseName, - ); - const solcovIgnoreContract = fs.readFileSync(solcovIgnoreContractFileName).toString(); - const coverageEntries = collectCoverageEntries(solcovIgnoreContract); - const fnIds = _.keys(coverageEntries.fnMap); - - expect(fnIds.length).to.be.equal(1); - expect(coverageEntries.fnMap[fnIds[0]].name).to.be.equal('set'); - // tslint:disable-next-line:custom-no-magic-numbers - expect(coverageEntries.fnMap[fnIds[0]].line).to.be.equal(6); - const setFunction = `function set(uint x) public { - /* solcov ignore next */ - storedData = x; - }`; - expect(utils.getRange(solcovIgnoreContract, coverageEntries.fnMap[fnIds[0]].loc)).to.be.equal(setFunction); - - expect(coverageEntries.branchMap).to.be.deep.equal({}); - const statementIds = _.keys(coverageEntries.statementMap); - expect(utils.getRange(solcovIgnoreContract, coverageEntries.statementMap[statementIds[0]])).to.be.equal( - setFunction, - ); - expect(statementIds.length).to.be.equal(1); - expect(coverageEntries.modifiersStatementIds.length).to.be.equal(0); - }); - }); -}); diff --git a/packages/sol-trace-based-tools-common/test/fixtures/contracts/AllSolidityFeatures.sol b/packages/sol-trace-based-tools-common/test/fixtures/contracts/AllSolidityFeatures.sol deleted file mode 100644 index 21137347e..000000000 --- a/packages/sol-trace-based-tools-common/test/fixtures/contracts/AllSolidityFeatures.sol +++ /dev/null @@ -1,413 +0,0 @@ -// Examples taken from the Solidity documentation online. - -// for pragma version numbers, see https://docs.npmjs.com/misc/semver#versions -pragma solidity 0.4.0; -pragma solidity ^0.4.0; - -import "SomeFile.sol"; -import "SomeFile.sol" as SomeOtherFile; -import * as SomeSymbol from "AnotherFile.sol"; -import {symbol1 as alias, symbol2} from "File.sol"; - -interface i { - function f(); -} - -contract c { - function c() - { - val1 = 1 wei; // 1 - val2 = 1 szabo; // 1 * 10 ** 12 - val3 = 1 finney; // 1 * 10 ** 15 - val4 = 1 ether; // 1 * 10 ** 18 - } - uint256 val1; - uint256 val2; - uint256 val3; - uint256 val4; -} - -contract test { - enum ActionChoices { GoLeft, GoRight, GoStraight, SitStill } - - function test() - { - choices = ActionChoices.GoStraight; - } - function getChoice() returns (uint d) - { - d = uint256(choices); - } - ActionChoices choices; -} - -contract Base { - function Base(uint i) - { - m_i = i; - } - uint public m_i; -} -contract Derived is Base(0) { - function Derived(uint i) Base(i) {} -} - -contract C { - uint248 x; // 31 bytes: slot 0, offset 0 - uint16 y; // 2 bytes: slot 1, offset 0 (does not fit in slot 0) - uint240 z; // 30 bytes: slot 1, offset 2 bytes - uint8 a; // 1 byte: slot 2, offset 0 bytes - struct S { - uint8 a; // 1 byte, slot +0, offset 0 bytes - uint256 b; // 32 bytes, slot +1, offset 0 bytes (does not fit) - } - S structData; // 2 slots, slot 3, offset 0 bytes (does not really apply) - uint8 alpha; // 1 byte, slot 4 (start new slot after struct) - uint16[3] beta; // 3*16 bytes, slots 5+6 (start new slot for array) - uint8 gamma; // 1 byte, slot 7 (start new slot after array) -} - -contract test { - function f(uint x, uint y) returns (uint z) { - var c = x + 3; - var b = 7 + (c * (8 - 7)) - x; - return -(-b | 0); - } -} - -contract test { - function f(uint x, uint y) returns (uint z) { - return 10; - } -} - -contract c { - function () returns (uint) { return g(8); } - function g(uint pos) internal returns (uint) { setData(pos, 8); return getData(pos); } - function setData(uint pos, uint value) internal { data[pos] = value; } - function getData(uint pos) internal { return data[pos]; } - mapping(uint => uint) data; -} - -contract Sharer { - function sendHalf(address addr) returns (uint balance) { - if (!addr.send(msg.value/2)) - throw; // also reverts the transfer to Sharer - return address(this).balance; - } -} - -/// @dev Models a modifiable and iterable set of uint values. -library IntegerSet -{ - struct data - { - /// Mapping item => index (or zero if not present) - mapping(uint => uint) index; - /// Items by index (index 0 is invalid), items with index[item] == 0 are invalid. - uint[] items; - /// Number of stored items. - uint size; - } - function insert(data storage self, uint value) returns (bool alreadyPresent) - { - uint index = self.index[value]; - if (index > 0) - return true; - else - { - if (self.items.length == 0) self.items.length = 1; - index = self.items.length++; - self.items[index] = value; - self.index[value] = index; - self.size++; - return false; - } - } - function remove(data storage self, uint value) returns (bool success) - { - uint index = self.index[value]; - if (index == 0) - return false; - delete self.index[value]; - delete self.items[index]; - self.size --; - } - function contains(data storage self, uint value) returns (bool) - { - return self.index[value] > 0; - } - function iterate_start(data storage self) returns (uint index) - { - return iterate_advance(self, 0); - } - function iterate_valid(data storage self, uint index) returns (bool) - { - return index < self.items.length; - } - function iterate_advance(data storage self, uint index) returns (uint r_index) - { - index++; - while (iterate_valid(self, index) && self.index[self.items[index]] == index) - index++; - return index; - } - function iterate_get(data storage self, uint index) returns (uint value) - { - return self.items[index]; - } -} - -/// How to use it: -contract User -{ - /// Just a struct holding our data. - IntegerSet.data data; - /// Insert something - function insert(uint v) returns (uint size) - { - /// Sends `data` via reference, so IntegerSet can modify it. - IntegerSet.insert(data, v); - /// We can access members of the struct - but we should take care not to mess with them. - return data.size; - } - /// Computes the sum of all stored data. - function sum() returns (uint s) - { - for (var i = IntegerSet.iterate_start(data); IntegerSet.iterate_valid(data, i); i = IntegerSet.iterate_advance(data, i)) - s += IntegerSet.iterate_get(data, i); - } -} - -// This broke it at one point (namely the modifiers). -contract DualIndex { - mapping(uint => mapping(uint => uint)) data; - address public admin; - - modifier restricted { if (msg.sender == admin) _; } - - function DualIndex() { - admin = msg.sender; - } - - function set(uint key1, uint key2, uint value) restricted { - uint[2][4] memory defaults; // "memory" broke things at one time. - data[key1][key2] = value; - } - - function transfer_ownership(address _admin) restricted { - admin = _admin; - } - - function lookup(uint key1, uint key2) returns(uint) { - return data[key1][key2]; - } -} - -contract A { - -} - -contract B { - -} - -contract C is A, B { - -} - -contract TestPrivate -{ - uint private value; -} - -contract TestInternal -{ - uint internal value; -} - -contract FromSolparse is A, B, TestPrivate, TestInternal { - function() { - uint a = 6 ** 9; - var (x) = 100; - uint y = 2 days; - } -} - -contract CommentedOutFunction { - // FYI: This empty function, as well as the commented - // out function below (bad code) is important to this test. - function() { - - } - - // function something() - // uint x = 10; - // } -} - -library VarHasBrackets { - string constant specialRight = "}"; - //string storage specialLeft = "{"; -} - -library UsingExampleLibrary { - function sum(uint[] storage self) returns (uint s) { - for (uint i = 0; i < self.length; i++) - s += self[i]; - } -} - -contract UsingExampleContract { - using UsingExampleLibrary for uint[]; -} - -contract NewStuff { - uint[] b; - - function someFunction() payable { - string storage a = hex"ab1248fe"; - b[2+2]; - } -} - -// modifier with expression -contract MyContract { - function fun() mymodifier(foo.bar()) {} -} - -library GetCode { - function at(address _addr) returns (bytes o_code) { - assembly { - // retrieve the size of the code, this needs assembly - let size := extcodesize(_addr) - // allocate output byte array - this could also be done without assembly - // by using o_code = new bytes(size) - o_code := mload(0x40) - // new "memory end" including padding - mstore(0x40, add(o_code, and(add(add(size, 0x20), 0x1f), not(0x1f)))) - // store length in memory - mstore(o_code, size) - // actually retrieve the code, this needs assembly - extcodecopy(_addr, add(o_code, 0x20), 0, size) - } - } -} - -contract assemblyLocalBinding { - function test(){ - assembly { - let v := 1 - let x := 0x00 - let y := x - let z := "hello" - } - } -} - -contract assemblyReturn { - uint a = 10; - - function get() constant returns(uint) { - assembly { - mstore(0x40, sload(0)) - byte(0) - address(0) - return(0x40,32) - } - } -} - -contract usesConst { - uint const = 0; -} - -contract memoryArrays { - uint seven = 7; - - function returnNumber(uint number) returns (uint){ - return number; - } - - function alloc() { - uint[] memory a = new uint[](7); - uint[] memory b = new uint[](returnNumber(seven)); - } -} - -contract DeclarativeExpressions { - uint a; - uint b = 7; - uint b2=0; - uint public c; - uint constant public d; - uint public constant e; - uint private constant f = 7; - struct S { uint q;} - - function ham(S storage s1, uint[] storage arr) internal { - uint x; - uint y = 7; - S storage s2 = s1; - uint[] memory stor; - uint[] storage stor2 = arr; - } -} - -contract VariableDeclarationTuple { - function getMyTuple() returns (bool, bool){ - return (true, false); - } - - function ham (){ - var (x, y) = (10, 20); - var (a, b) = getMyTuple(); - var (,c) = (10, 20); - var (d,,) = (10, 20, 30); - var (,e,,f,) = (10, 20, 30, 40, 50); - - var ( - num1, num2, - num3, ,num5 - ) = (10, 20, 30, 40, 50); - } -} - -contract TypeIndexSpacing { - uint [ 7 ] x; - uint [] y; -} - -contract Ballot { - - struct Voter { - uint weight; - bool voted; - } - - function abstain() returns (bool) { - return false; - } - - function foobar() payable owner (myPrice) returns (uint[], address myAdd, string[] names) {} - function foobar() payable owner (myPrice) returns (uint[], address myAdd, string[] names); - - Voter you = Voter(1, true); - - Voter me = Voter({ - weight: 2, - voted: abstain() - }); - - Voter airbnb = Voter({ - weight: 2, - voted: true, - }); -} - -contract multilineReturn { - function a() returns (uint x) { - return - 5; - } -} diff --git a/packages/sol-trace-based-tools-common/test/fixtures/contracts/SimpleStorage.sol b/packages/sol-trace-based-tools-common/test/fixtures/contracts/SimpleStorage.sol deleted file mode 100644 index e4b4ac246..000000000 --- a/packages/sol-trace-based-tools-common/test/fixtures/contracts/SimpleStorage.sol +++ /dev/null @@ -1,11 +0,0 @@ -pragma solidity ^0.4.21; - -contract SimpleStorage { - uint public storedData; - function set(uint x) { - storedData = x; - } - function get() constant returns (uint retVal) { - return storedData; - } -} diff --git a/packages/sol-trace-based-tools-common/test/fixtures/contracts/Simplest.sol b/packages/sol-trace-based-tools-common/test/fixtures/contracts/Simplest.sol deleted file mode 100644 index d71016e07..000000000 --- a/packages/sol-trace-based-tools-common/test/fixtures/contracts/Simplest.sol +++ /dev/null @@ -1,2 +0,0 @@ -contract Simplest { -} diff --git a/packages/sol-trace-based-tools-common/test/fixtures/contracts/SolcovIgnore.sol b/packages/sol-trace-based-tools-common/test/fixtures/contracts/SolcovIgnore.sol deleted file mode 100644 index a7977ffb4..000000000 --- a/packages/sol-trace-based-tools-common/test/fixtures/contracts/SolcovIgnore.sol +++ /dev/null @@ -1,22 +0,0 @@ -pragma solidity ^0.4.21; - -contract SolcovIgnore { - uint public storedData; - - function set(uint x) public { - /* solcov ignore next */ - storedData = x; - } - - /* solcov ignore next */ - function get() constant public returns (uint retVal) { - return storedData; - } -} - -/* solcov ignore next */ -contract Ignore { - function ignored() public returns (bool) { - return false; - } -} diff --git a/packages/sol-trace-based-tools-common/test/instructions_test.ts b/packages/sol-trace-based-tools-common/test/instructions_test.ts deleted file mode 100644 index 058053cf9..000000000 --- a/packages/sol-trace-based-tools-common/test/instructions_test.ts +++ /dev/null @@ -1,19 +0,0 @@ -import * as chai from 'chai'; -import 'mocha'; - -import { constants } from '../src/constants'; -import { getPcToInstructionIndexMapping } from '../src/instructions'; - -const expect = chai.expect; - -describe('instructions', () => { - describe('#getPcToInstructionIndexMapping', () => { - it('correctly maps pcs to instruction indexed', () => { - // tslint:disable-next-line:custom-no-magic-numbers - const bytecode = new Uint8Array([constants.PUSH1, 42, constants.PUSH2, 1, 2, constants.TIMESTAMP]); - const pcToInstruction = getPcToInstructionIndexMapping(bytecode); - const expectedPcToInstruction = { '0': 0, '2': 1, '5': 2 }; - expect(pcToInstruction).to.be.deep.equal(expectedPcToInstruction); - }); - }); -}); diff --git a/packages/sol-trace-based-tools-common/test/sol_compiler_artifact_adapter_test.ts b/packages/sol-trace-based-tools-common/test/sol_compiler_artifact_adapter_test.ts deleted file mode 100644 index 9c58d2cef..000000000 --- a/packages/sol-trace-based-tools-common/test/sol_compiler_artifact_adapter_test.ts +++ /dev/null @@ -1,29 +0,0 @@ -import * as chai from 'chai'; -import * as _ from 'lodash'; -import 'mocha'; -import * as path from 'path'; - -import { SolCompilerArtifactAdapter } from '../src/artifact_adapters/sol_compiler_artifact_adapter'; - -const expect = chai.expect; - -describe('SolCompilerArtifactAdapter', () => { - describe('#collectContractsData', () => { - it('correctly collects contracts data', async () => { - const artifactsPath = path.resolve(__dirname, 'fixtures/artifacts'); - const sourcesPath = path.resolve(__dirname, 'fixtures/contracts'); - const zeroExArtifactsAdapter = new SolCompilerArtifactAdapter(artifactsPath, sourcesPath); - const contractsData = await zeroExArtifactsAdapter.collectContractsDataAsync(); - _.forEach(contractsData, contractData => { - expect(contractData).to.have.keys([ - 'sourceCodes', - 'sources', - 'sourceMap', - 'sourceMapRuntime', - 'bytecode', - 'runtimeBytecode', - ]); - }); - }); - }); -}); diff --git a/packages/sol-trace-based-tools-common/test/source_maps_test.ts b/packages/sol-trace-based-tools-common/test/source_maps_test.ts deleted file mode 100644 index 5820bedd7..000000000 --- a/packages/sol-trace-based-tools-common/test/source_maps_test.ts +++ /dev/null @@ -1,71 +0,0 @@ -import * as chai from 'chai'; -import * as fs from 'fs'; -import * as _ from 'lodash'; -import 'mocha'; -import * as path from 'path'; - -import { getLocationByOffset, parseSourceMap } from '../src/source_maps'; - -const expect = chai.expect; - -const simplestContractBaseName = 'Simplest.sol'; -const simplestContractFileName = path.resolve(__dirname, 'fixtures/contracts', simplestContractBaseName); -const simplestContract = fs.readFileSync(simplestContractFileName).toString(); - -describe('source maps', () => { - describe('#getLocationByOffset', () => { - it('correctly computes location by offset', () => { - const locationByOffset = getLocationByOffset(simplestContract); - const expectedLocationByOffset = { - '0': { line: 1, column: 0 }, - '1': { line: 1, column: 1 }, - '2': { line: 1, column: 2 }, - '3': { line: 1, column: 3 }, - '4': { line: 1, column: 4 }, - '5': { line: 1, column: 5 }, - '6': { line: 1, column: 6 }, - '7': { line: 1, column: 7 }, - '8': { line: 1, column: 8 }, - '9': { line: 1, column: 9 }, - '10': { line: 1, column: 10 }, - '11': { line: 1, column: 11 }, - '12': { line: 1, column: 12 }, - '13': { line: 1, column: 13 }, - '14': { line: 1, column: 14 }, - '15': { line: 1, column: 15 }, - '16': { line: 1, column: 16 }, - '17': { line: 1, column: 17 }, - '18': { line: 1, column: 18 }, - '19': { line: 1, column: 19 }, - '20': { line: 2, column: 0 }, - '21': { line: 2, column: 1 }, - '22': { line: 3, column: 0 }, - }; - expect(locationByOffset).to.be.deep.equal(expectedLocationByOffset); - }); - }); - describe('#parseSourceMap', () => { - it('correctly parses the source map', () => { - // This is the source map and bytecode for an empty contract like Example.sol - const srcMap = '0:21:0:-;;;;;;;;;;;;;;;;;'; - const bytecodeHex = - '60606040523415600e57600080fd5b603580601b6000396000f3006060604052600080fd00a165627a7a72305820377cdef690e46589f40efeef14d8ef73504af059fb3fd46f1da3cd2fc52ef7890029'; - const sources = [simplestContractBaseName]; - const pcToSourceRange = parseSourceMap([simplestContract], srcMap, bytecodeHex, sources); - const expectedSourceRange = { - location: { - start: { line: 1, column: 0 }, - end: { line: 2, column: 1 }, - }, - fileName: simplestContractBaseName, - }; - _.forEach(pcToSourceRange, sourceRange => { - // Solidity source maps are too short and we map some instructions to undefined - // Source: https://github.com/ethereum/solidity/issues/3741 - if (!_.isUndefined(sourceRange)) { - expect(sourceRange).to.be.deep.equal(expectedSourceRange); - } - }); - }); - }); -}); diff --git a/packages/sol-trace-based-tools-common/test/trace_test.ts b/packages/sol-trace-based-tools-common/test/trace_test.ts deleted file mode 100644 index 7a034362c..000000000 --- a/packages/sol-trace-based-tools-common/test/trace_test.ts +++ /dev/null @@ -1,55 +0,0 @@ -import * as chai from 'chai'; -import { OpCode, StructLog } from 'ethereum-types'; -import * as _ from 'lodash'; -import 'mocha'; - -import { getTracesByContractAddress } from '../src/trace'; - -const expect = chai.expect; - -const DEFAULT_STRUCT_LOG: StructLog = { - depth: 0, - error: '', - gas: 0, - gasCost: 0, - memory: [], - op: OpCode.Invalid, - pc: 0, - stack: [], - storage: {}, -}; - -function addDefaultStructLogFields(compactStructLog: Partial & { op: OpCode; depth: number }): StructLog { - return { ...DEFAULT_STRUCT_LOG, ...compactStructLog }; -} - -describe('Trace', () => { - describe('#getTracesByContractAddress', () => { - it('correctly splits trace by contract address', () => { - const delegateCallAddress = '0x0000000000000000000000000000000000000002'; - const trace = [ - { - op: OpCode.DelegateCall, - stack: [delegateCallAddress, '0x'], - depth: 0, - }, - { - op: OpCode.Return, - depth: 1, - }, - { - op: OpCode.Return, - depth: 0, - }, - ]; - const fullTrace = _.map(trace, compactStructLog => addDefaultStructLogFields(compactStructLog)); - const startAddress = '0x0000000000000000000000000000000000000001'; - const traceByContractAddress = getTracesByContractAddress(fullTrace, startAddress); - const expectedTraceByContractAddress = { - [startAddress]: [fullTrace[0], fullTrace[2]], - [delegateCallAddress]: [fullTrace[1]], - }; - expect(traceByContractAddress).to.be.deep.equal(expectedTraceByContractAddress); - }); - }); -}); diff --git a/packages/sol-trace-based-tools-common/test/utils_test.ts b/packages/sol-trace-based-tools-common/test/utils_test.ts deleted file mode 100644 index 6fc8fcfe1..000000000 --- a/packages/sol-trace-based-tools-common/test/utils_test.ts +++ /dev/null @@ -1,53 +0,0 @@ -import * as chai from 'chai'; -import * as dirtyChai from 'dirty-chai'; -import 'mocha'; - -import { utils } from '../src/utils'; - -chai.use(dirtyChai); -const expect = chai.expect; - -describe('utils', () => { - describe('#compareLineColumn', () => { - it('correctly compares LineColumns', () => { - expect(utils.compareLineColumn({ line: 1, column: 3 }, { line: 1, column: 4 })).to.be.lessThan(0); - expect(utils.compareLineColumn({ line: 1, column: 4 }, { line: 1, column: 3 })).to.be.greaterThan(0); - expect(utils.compareLineColumn({ line: 1, column: 3 }, { line: 1, column: 3 })).to.be.equal(0); - expect(utils.compareLineColumn({ line: 0, column: 2 }, { line: 1, column: 0 })).to.be.lessThan(0); - expect(utils.compareLineColumn({ line: 1, column: 0 }, { line: 0, column: 2 })).to.be.greaterThan(0); - }); - }); - - describe('#isRangeInside', () => { - it('returns true if inside', () => { - expect( - utils.isRangeInside( - { start: { line: 1, column: 3 }, end: { line: 1, column: 4 } }, - { start: { line: 1, column: 2 }, end: { line: 1, column: 5 } }, - ), - ).to.be.true(); - }); - it('returns true if the same', () => { - expect( - utils.isRangeInside( - { start: { line: 1, column: 3 }, end: { line: 1, column: 4 } }, - { start: { line: 1, column: 3 }, end: { line: 1, column: 4 } }, - ), - ).to.be.true(); - }); - it('returns false if not inside', () => { - expect( - utils.isRangeInside( - { start: { line: 1, column: 3 }, end: { line: 1, column: 4 } }, - { start: { line: 1, column: 4 }, end: { line: 1, column: 4 } }, - ), - ).to.be.false(); - expect( - utils.isRangeInside( - { start: { line: 1, column: 3 }, end: { line: 1, column: 4 } }, - { start: { line: 1, column: 4 }, end: { line: 1, column: 5 } }, - ), - ).to.be.false(); - }); - }); -}); diff --git a/packages/sol-trace-based-tools-common/tsconfig.json b/packages/sol-trace-based-tools-common/tsconfig.json deleted file mode 100644 index 2ee711adc..000000000 --- a/packages/sol-trace-based-tools-common/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../tsconfig", - "compilerOptions": { - "outDir": "lib", - "rootDir": "." - }, - "include": ["./src/**/*", "./test/**/*"] -} diff --git a/packages/sol-trace-based-tools-common/tslint.json b/packages/sol-trace-based-tools-common/tslint.json deleted file mode 100644 index dd9053357..000000000 --- a/packages/sol-trace-based-tools-common/tslint.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": ["@0x/tslint-config"] -} diff --git a/packages/sol-trace/CHANGELOG.json b/packages/sol-trace/CHANGELOG.json index 938fa97a0..c650a4a4b 100644 --- a/packages/sol-trace/CHANGELOG.json +++ b/packages/sol-trace/CHANGELOG.json @@ -4,7 +4,7 @@ "changes": [ { "note": - "Initial release as a separate package. For historical entries see @0x/sol-trace-based-tools-common", + "Initial release as a separate package. For historical entries see @0x/sol-tracing-utils", "pr": 1492 } ] diff --git a/packages/sol-trace/package.json b/packages/sol-trace/package.json index fd80c541e..3013be6f9 100644 --- a/packages/sol-trace/package.json +++ b/packages/sol-trace/package.json @@ -31,7 +31,7 @@ "dependencies": { "@0x/subproviders": "^2.1.8", "@0x/typescript-typings": "^3.0.6", - "@0x/sol-trace-based-tools-common": "^2.1.16", + "@0x/sol-tracing-utils": "^2.1.16", "ethereum-types": "^1.1.4", "ethereumjs-util": "^5.1.1", "lodash": "^4.17.5", diff --git a/packages/sol-trace/src/index.ts b/packages/sol-trace/src/index.ts index 1f3ca3a99..120c0d0a9 100644 --- a/packages/sol-trace/src/index.ts +++ b/packages/sol-trace/src/index.ts @@ -3,7 +3,7 @@ export { TruffleArtifactAdapter, SolCompilerArtifactAdapter, ContractData, -} from '@0x/sol-trace-based-tools-common'; +} from '@0x/sol-tracing-utils'; export { RevertTraceSubprovider } from './revert_trace_subprovider'; diff --git a/packages/sol-trace/src/revert_trace_subprovider.ts b/packages/sol-trace/src/revert_trace_subprovider.ts index 37e62b3f8..31067a402 100644 --- a/packages/sol-trace/src/revert_trace_subprovider.ts +++ b/packages/sol-trace/src/revert_trace_subprovider.ts @@ -10,7 +10,7 @@ import { SourceSnippet, TraceCollectionSubprovider, utils, -} from '@0x/sol-trace-based-tools-common'; +} from '@0x/sol-tracing-utils'; import { stripHexPrefix } from 'ethereumjs-util'; import * as _ from 'lodash'; import { getLogger, levels, Logger } from 'loglevel'; diff --git a/packages/sol-tracing-utils/.npmignore b/packages/sol-tracing-utils/.npmignore new file mode 100644 index 000000000..037786e46 --- /dev/null +++ b/packages/sol-tracing-utils/.npmignore @@ -0,0 +1,6 @@ +.* +yarn-error.log +/src/ +/scripts/ +tsconfig.json +/lib/src/monorepo_scripts/ diff --git a/packages/sol-tracing-utils/CHANGELOG.json b/packages/sol-tracing-utils/CHANGELOG.json new file mode 100644 index 000000000..caccb3fff --- /dev/null +++ b/packages/sol-tracing-utils/CHANGELOG.json @@ -0,0 +1,398 @@ +[ + { + "version": "3.0.0", + "changes": [ + { + "note": "Move out specific tools and leave just the shared parts of the codebase", + "pr": 1492 + } + ] + }, + { + "version": "2.1.16", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1544739608 + }, + { + "version": "2.1.15", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1544570656 + }, + { + "timestamp": 1543401373, + "version": "2.1.14", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1542821676, + "version": "2.1.13", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1542208198, + "version": "2.1.12", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1542134075, + "version": "2.1.11", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1542028948, + "version": "2.1.10", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "version": "2.1.9", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1541740904 + }, + { + "version": "2.1.8", + "changes": [ + { + "note": "Make @types/solidity-parser-antlr a 'dependency' so it's available to users of the library", + "pr": 1105 + } + ], + "timestamp": 1539871071 + }, + { + "version": "2.1.7", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1538693146 + }, + { + "timestamp": 1538157789, + "version": "2.1.6", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1537907159, + "version": "2.1.5", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1537875740, + "version": "2.1.4", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1537541580, + "version": "2.1.3", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1536142250, + "version": "2.1.2", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1535377027, + "version": "2.1.1", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "version": "2.1.0", + "changes": [ + { + "note": + "Export types: `JSONRPCRequestPayload`, `Provider`, `JSONRPCErrorCallback`, `JSONRPCResponsePayload`, `JSONRPCRequestPayloadWithMethod`, `NextCallback`, `ErrorCallback`, `OnNextCompleted` and `Callback`", + "pr": 924 + } + ], + "timestamp": 1535133899 + }, + { + "version": "2.0.0", + "changes": [ + { + "note": + "Fix a bug when eth_call coverage was not computed because of silent schema validation failures", + "pr": 938 + }, + { + "note": "Make `TruffleArtifactAdapter` read the `truffle.js` config for `solc` settings", + "pr": 938 + }, + { + "note": + "Change the first param of `TruffleArtifactAdapter` to be the `projectRoot` instead of `sourcesDir`", + "pr": 938 + }, + { + "note": + "Throw a helpful error message if truffle artifacts were generated with a different solc version than the one passed in", + "pr": 938 + } + ], + "timestamp": 1534210131 + }, + { + "version": "1.0.3", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1532619515 + }, + { + "timestamp": 1532605697, + "version": "1.0.2", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1532357734, + "version": "1.0.1", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1532043000, + "version": "1.0.0", + "changes": [ + { + "note": + "Add artifact adapter as a parameter for `CoverageSubprovider`. Export `AbstractArtifactAdapter`", + "pr": 589 + }, + { + "note": "Implement `SolCompilerArtifactAdapter` and `TruffleArtifactAdapter`", + "pr": 589 + }, + { + "note": "Properly parse multi-level traces", + "pr": 589 + }, + { + "note": "Add support for solidity libraries", + "pr": 589 + }, + { + "note": "Fixed a bug causing `RegExp` to crash if contract code is longer that 32767 characters", + "pr": 675 + }, + { + "note": "Fixed a bug caused by Geth debug trace depth being 1indexed", + "pr": 675 + }, + { + "note": "Fixed a bug when the tool crashed on empty traces", + "pr": 675 + }, + { + "note": "Use `BlockchainLifecycle` to support reverts on Geth", + "pr": 675 + }, + { + "note": "Add `ProfilerSubprovider` as a hacky way to profile code using coverage tools", + "pr": 675 + }, + { + "note": "Collect traces from `estimate_gas` calls", + "pr": 675 + }, + { + "note": "Fix a race condition caused by not awaiting the transaction before getting a trace", + "pr": 675 + }, + { + "note": "Add `start`/`stop` functionality to `CoverageSubprovider` and `ProfilerSubprovider`", + "pr": 675 + }, + { + "note": "Skip interface artifacts with a warning instead of failing", + "pr": 675 + }, + { + "note": "Fix `solcVersion` regex in parameter validation", + "pr": 690 + }, + { + "note": + "Fix a bug when in `TruffleArtifactsAdapter` causing it to throw if `compiler.json` is not there", + "pr": 690 + }, + { + "note": "HUGE perf improvements", + "pr": 690 + }, + { + "note": "Create `RevertTraceSubprovider` which prints a stack trace when a `REVERT` is detected", + "pr": 705 + }, + { + "note": "Add source code snippets to stack traces printed by `RevertTraceSubprovider`", + "pr": 725 + } + ] + }, + { + "timestamp": 1531919263, + "version": "0.1.3", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1531149657, + "version": "0.1.2", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1529397769, + "version": "0.1.1", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "version": "0.1.0", + "changes": [ + { + "note": "Incorrect publish that was unpublished" + } + ], + "timestamp": 1527810075 + }, + { + "timestamp": 1527009134, + "version": "0.0.11", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1525477860, + "version": "0.0.10", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1525428773, + "version": "0.0.9", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1524044013, + "version": "0.0.8", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1523462196, + "version": "0.0.7", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1522673609, + "version": "0.0.6", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1522658513, + "version": "0.0.5", + "changes": [ + { + "note": "Dependencies updated" + } + ] + } +] diff --git a/packages/sol-tracing-utils/CHANGELOG.md b/packages/sol-tracing-utils/CHANGELOG.md new file mode 100644 index 000000000..879ef9c95 --- /dev/null +++ b/packages/sol-tracing-utils/CHANGELOG.md @@ -0,0 +1,158 @@ + + +CHANGELOG + +## v2.1.16 - _December 13, 2018_ + + * Dependencies updated + +## v2.1.15 - _December 11, 2018_ + + * Dependencies updated + +## v2.1.14 - _November 28, 2018_ + + * Dependencies updated + +## v2.1.13 - _November 21, 2018_ + + * Dependencies updated + +## v2.1.12 - _November 14, 2018_ + + * Dependencies updated + +## v2.1.11 - _November 13, 2018_ + + * Dependencies updated + +## v2.1.10 - _November 12, 2018_ + + * Dependencies updated + +## v2.1.9 - _November 9, 2018_ + + * Dependencies updated + +## v2.1.8 - _October 18, 2018_ + + * Make @types/solidity-parser-antlr a 'dependency' so it's available to users of the library (#1105) + +## v2.1.7 - _October 4, 2018_ + + * Dependencies updated + +## v2.1.6 - _September 28, 2018_ + + * Dependencies updated + +## v2.1.5 - _September 25, 2018_ + + * Dependencies updated + +## v2.1.4 - _September 25, 2018_ + + * Dependencies updated + +## v2.1.3 - _September 21, 2018_ + + * Dependencies updated + +## v2.1.2 - _September 5, 2018_ + + * Dependencies updated + +## v2.1.1 - _August 27, 2018_ + + * Dependencies updated + +## v2.1.0 - _August 24, 2018_ + + * Export types: `JSONRPCRequestPayload`, `Provider`, `JSONRPCErrorCallback`, `JSONRPCResponsePayload`, `JSONRPCRequestPayloadWithMethod`, `NextCallback`, `ErrorCallback`, `OnNextCompleted` and `Callback` (#924) + +## v2.0.0 - _August 14, 2018_ + + * Fix a bug when eth_call coverage was not computed because of silent schema validation failures (#938) + * Make `TruffleArtifactAdapter` read the `truffle.js` config for `solc` settings (#938) + * Change the first param of `TruffleArtifactAdapter` to be the `projectRoot` instead of `sourcesDir` (#938) + * Throw a helpful error message if truffle artifacts were generated with a different solc version than the one passed in (#938) + +## v1.0.3 - _July 26, 2018_ + + * Dependencies updated + +## v1.0.2 - _July 26, 2018_ + + * Dependencies updated + +## v1.0.1 - _July 23, 2018_ + + * Dependencies updated + +## v1.0.0 - _July 19, 2018_ + + * Add artifact adapter as a parameter for `CoverageSubprovider`. Export `AbstractArtifactAdapter` (#589) + * Implement `SolCompilerArtifactAdapter` and `TruffleArtifactAdapter` (#589) + * Properly parse multi-level traces (#589) + * Add support for solidity libraries (#589) + * Fixed a bug causing `RegExp` to crash if contract code is longer that 32767 characters (#675) + * Fixed a bug caused by Geth debug trace depth being 1indexed (#675) + * Fixed a bug when the tool crashed on empty traces (#675) + * Use `BlockchainLifecycle` to support reverts on Geth (#675) + * Add `ProfilerSubprovider` as a hacky way to profile code using coverage tools (#675) + * Collect traces from `estimate_gas` calls (#675) + * Fix a race condition caused by not awaiting the transaction before getting a trace (#675) + * Add `start`/`stop` functionality to `CoverageSubprovider` and `ProfilerSubprovider` (#675) + * Skip interface artifacts with a warning instead of failing (#675) + * Fix `solcVersion` regex in parameter validation (#690) + * Fix a bug when in `TruffleArtifactsAdapter` causing it to throw if `compiler.json` is not there (#690) + * HUGE perf improvements (#690) + * Create `RevertTraceSubprovider` which prints a stack trace when a `REVERT` is detected (#705) + * Add source code snippets to stack traces printed by `RevertTraceSubprovider` (#725) + +## v0.1.3 - _July 18, 2018_ + + * Dependencies updated + +## v0.1.2 - _July 9, 2018_ + + * Dependencies updated + +## v0.1.1 - _June 19, 2018_ + + * Dependencies updated + +## v0.1.0 - _May 31, 2018_ + + * Incorrect publish that was unpublished + +## v0.0.11 - _May 22, 2018_ + + * Dependencies updated + +## v0.0.10 - _May 4, 2018_ + + * Dependencies updated + +## v0.0.9 - _May 4, 2018_ + + * Dependencies updated + +## v0.0.8 - _April 18, 2018_ + + * Dependencies updated + +## v0.0.7 - _April 11, 2018_ + + * Dependencies updated + +## v0.0.6 - _April 2, 2018_ + + * Dependencies updated + +## v0.0.5 - _April 2, 2018_ + + * Dependencies updated diff --git a/packages/sol-tracing-utils/README.md b/packages/sol-tracing-utils/README.md new file mode 100644 index 000000000..0a4749b8e --- /dev/null +++ b/packages/sol-tracing-utils/README.md @@ -0,0 +1,61 @@ +## @0x/sol-tracing-utils + +Common code for all solidity trace-based tools (sol-coverage, sol-profiler, sol-trace). + +## Installation + +```bash +yarn add @0x/sol-sol-tracing-utils +``` + +## Contributing + +We welcome improvements and fixes from the wider community! 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 +``` + +### Build + +To build this package and all other monorepo packages that it depends on, run the following from the monorepo root directory: + +```bash +PKG=@0x/sol-tracing-utils yarn build +``` + +Or continuously rebuild on change: + +```bash +PKG=@0x/sol-tracing-utils yarn watch +``` + +### Clean + +```bash +yarn clean +``` + +### Lint + +```bash +yarn lint +``` + +### Run Tests + +```bash +yarn test +``` diff --git a/packages/sol-tracing-utils/compiler.json b/packages/sol-tracing-utils/compiler.json new file mode 100644 index 000000000..a6a0c6d3a --- /dev/null +++ b/packages/sol-tracing-utils/compiler.json @@ -0,0 +1,18 @@ +{ + "contracts": ["SimpleStorage"], + "contractsDir": "test/fixtures/contracts", + "artifactsDir": "test/fixtures/artifacts", + "compilerSettings": { + "outputSelection": { + "*": { + "*": [ + "abi", + "evm.bytecode.object", + "evm.bytecode.sourceMap", + "evm.deployedBytecode.object", + "evm.deployedBytecode.sourceMap" + ] + } + } + } +} diff --git a/packages/sol-tracing-utils/package.json b/packages/sol-tracing-utils/package.json new file mode 100644 index 000000000..f351ccc08 --- /dev/null +++ b/packages/sol-tracing-utils/package.json @@ -0,0 +1,87 @@ +{ + "name": "@0x/sol-tracing-utils", + "version": "2.1.16", + "engines": { + "node": ">=6.12" + }, + "description": "Common part of trace based solidity tools (sol-coverage, sol-trace, sol-profiler)", + "main": "lib/src/index.js", + "types": "lib/src/index.d.ts", + "scripts": { + "build": "yarn pre_build && tsc -b", + "build:ci": "yarn build", + "pre_build": "run-s copy_test_fixtures", + "lint": "tslint --format stylish --project .", + "test": "run-s compile_test run_mocha", + "rebuild_and_test": "run-s clean build test", + "test:coverage": "nyc npm run test --all && yarn coverage:report:lcov", + "coverage:report:lcov": "nyc report --reporter=text-lcov > coverage/lcov.info", + "test:circleci": "yarn test:coverage", + "run_mocha": "mocha --require source-map-support/register --require make-promises-safe lib/test/**/*_test.js --exit", + "clean": "shx rm -rf lib test/fixtures/artifacts src/artifacts generated_docs", + "copy_test_fixtures": "copyfiles 'test/fixtures/**/*' ./lib", + "compile_test": "sol-compiler compile", + "docs:json": "typedoc --excludePrivate --excludeExternals --target ES5 --tsconfig typedoc-tsconfig.json --json $JSON_FILE_PATH $PROJECT_FILES" + }, + "config": { + "postpublish": { + "assets": [], + "docOmitExports": [ + "ProfilerSubprovider", + "RevertTraceSubprovider" + ] + } + }, + "repository": { + "type": "git", + "url": "https://github.com/0xProject/0x-monorepo.git" + }, + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/0xProject/0x-monorepo/issues" + }, + "homepage": "https://github.com/0xProject/0x-monorepo/packages/sol-tracing-utils/README.md", + "dependencies": { + "@0x/dev-utils": "^1.0.21", + "@0x/sol-compiler": "^1.1.16", + "@0x/subproviders": "^2.1.8", + "@0x/typescript-typings": "^3.0.6", + "@0x/utils": "^2.0.8", + "@0x/web3-wrapper": "^3.2.1", + "@types/solidity-parser-antlr": "^0.2.0", + "ethereum-types": "^1.1.4", + "ethereumjs-util": "^5.1.1", + "glob": "^7.1.2", + "istanbul": "^0.4.5", + "lodash": "^4.17.5", + "loglevel": "^1.6.1", + "mkdirp": "^0.5.1", + "rimraf": "^2.6.2", + "semaphore-async-await": "^1.5.1", + "solidity-parser-antlr": "^0.2.12" + }, + "devDependencies": { + "@0x/tslint-config": "^2.0.0", + "@types/istanbul": "^0.4.30", + "@types/loglevel": "^1.5.3", + "@types/mkdirp": "^0.5.1", + "@types/mocha": "^2.2.42", + "@types/node": "*", + "@types/rimraf": "^2.0.2", + "chai": "^4.0.1", + "copyfiles": "^2.0.0", + "dirty-chai": "^2.0.1", + "make-promises-safe": "^1.1.0", + "mocha": "^4.1.0", + "npm-run-all": "^4.1.2", + "nyc": "^11.0.1", + "shx": "^0.2.2", + "sinon": "^4.0.0", + "tslint": "5.11.0", + "typedoc": "0.13.0", + "typescript": "3.0.1" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/sol-tracing-utils/src/artifact_adapters/abstract_artifact_adapter.ts b/packages/sol-tracing-utils/src/artifact_adapters/abstract_artifact_adapter.ts new file mode 100644 index 000000000..fcc6562ad --- /dev/null +++ b/packages/sol-tracing-utils/src/artifact_adapters/abstract_artifact_adapter.ts @@ -0,0 +1,5 @@ +import { ContractData } from '../types'; + +export abstract class AbstractArtifactAdapter { + public abstract async collectContractsDataAsync(): Promise; +} diff --git a/packages/sol-tracing-utils/src/artifact_adapters/sol_compiler_artifact_adapter.ts b/packages/sol-tracing-utils/src/artifact_adapters/sol_compiler_artifact_adapter.ts new file mode 100644 index 000000000..57391abbe --- /dev/null +++ b/packages/sol-tracing-utils/src/artifact_adapters/sol_compiler_artifact_adapter.ts @@ -0,0 +1,61 @@ +import { logUtils } from '@0x/utils'; +import { CompilerOptions, ContractArtifact } from 'ethereum-types'; +import * as fs from 'fs'; +import * as glob from 'glob'; +import * as _ from 'lodash'; +import * as path from 'path'; + +import { ContractData } from '../types'; + +import { AbstractArtifactAdapter } from './abstract_artifact_adapter'; + +const CONFIG_FILE = 'compiler.json'; + +export class SolCompilerArtifactAdapter extends AbstractArtifactAdapter { + private readonly _artifactsPath: string; + private readonly _sourcesPath: string; + /** + * Instantiates a SolCompilerArtifactAdapter + * @param artifactsPath Path to your artifacts directory + * @param sourcesPath Path to your contract sources directory + */ + constructor(artifactsPath?: string, sourcesPath?: string) { + super(); + const config: CompilerOptions = fs.existsSync(CONFIG_FILE) + ? JSON.parse(fs.readFileSync(CONFIG_FILE).toString()) + : {}; + if (_.isUndefined(artifactsPath) && _.isUndefined(config.artifactsDir)) { + throw new Error(`artifactsDir not found in ${CONFIG_FILE}`); + } + this._artifactsPath = (artifactsPath || config.artifactsDir) as string; + if (_.isUndefined(sourcesPath) && _.isUndefined(config.contractsDir)) { + throw new Error(`contractsDir not found in ${CONFIG_FILE}`); + } + this._sourcesPath = (sourcesPath || config.contractsDir) as string; + } + public async collectContractsDataAsync(): Promise { + const artifactsGlob = `${this._artifactsPath}/**/*.json`; + const artifactFileNames = glob.sync(artifactsGlob, { absolute: true }); + const contractsData: ContractData[] = []; + for (const artifactFileName of artifactFileNames) { + const artifact: ContractArtifact = JSON.parse(fs.readFileSync(artifactFileName).toString()); + if (_.isUndefined(artifact.compilerOutput.evm)) { + logUtils.warn(`${artifactFileName} doesn't contain bytecode. Skipping...`); + continue; + } + let sources = _.keys(artifact.sources); + sources = _.map(sources, relativeFilePath => path.resolve(this._sourcesPath, relativeFilePath)); + const sourceCodes = _.map(sources, (source: string) => fs.readFileSync(source).toString()); + const contractData = { + sourceCodes, + sources, + bytecode: artifact.compilerOutput.evm.bytecode.object, + sourceMap: artifact.compilerOutput.evm.bytecode.sourceMap, + runtimeBytecode: artifact.compilerOutput.evm.deployedBytecode.object, + sourceMapRuntime: artifact.compilerOutput.evm.deployedBytecode.sourceMap, + }; + contractsData.push(contractData); + } + return contractsData; + } +} diff --git a/packages/sol-tracing-utils/src/artifact_adapters/truffle_artifact_adapter.ts b/packages/sol-tracing-utils/src/artifact_adapters/truffle_artifact_adapter.ts new file mode 100644 index 000000000..bb2b15153 --- /dev/null +++ b/packages/sol-tracing-utils/src/artifact_adapters/truffle_artifact_adapter.ts @@ -0,0 +1,88 @@ +import { Compiler, CompilerOptions } from '@0x/sol-compiler'; +import * as fs from 'fs'; +import * as glob from 'glob'; +import * as path from 'path'; + +import { ContractData } from '../types'; + +import { AbstractArtifactAdapter } from './abstract_artifact_adapter'; +import { SolCompilerArtifactAdapter } from './sol_compiler_artifact_adapter'; + +const DEFAULT_TRUFFLE_ARTIFACTS_DIR = './build/contracts'; + +interface TruffleConfig { + solc?: any; + contracts_build_directory?: string; +} + +export class TruffleArtifactAdapter extends AbstractArtifactAdapter { + private readonly _solcVersion: string; + private readonly _projectRoot: string; + /** + * Instantiates a TruffleArtifactAdapter + * @param projectRoot Path to the truffle project's root directory + * @param solcVersion Solidity version with which to compile all the contracts + */ + constructor(projectRoot: string, solcVersion: string) { + super(); + this._solcVersion = solcVersion; + this._projectRoot = projectRoot; + } + public async collectContractsDataAsync(): Promise { + const artifactsDir = '.0x-artifacts'; + const contractsDir = path.join(this._projectRoot, 'contracts'); + const truffleConfig = this._getTruffleConfig(); + const solcConfig = truffleConfig.solc || {}; + const truffleArtifactsDirectory = truffleConfig.contracts_build_directory || DEFAULT_TRUFFLE_ARTIFACTS_DIR; + this._assertSolidityVersionIsCorrect(truffleArtifactsDirectory); + const compilerOptions: CompilerOptions = { + contractsDir, + artifactsDir, + compilerSettings: { + ...solcConfig, + outputSelection: { + ['*']: { + ['*']: ['abi', 'evm.bytecode.object', 'evm.deployedBytecode.object'], + }, + }, + }, + contracts: '*', + solcVersion: this._solcVersion, + }; + const compiler = new Compiler(compilerOptions); + await compiler.compileAsync(); + const solCompilerArtifactAdapter = new SolCompilerArtifactAdapter(artifactsDir, contractsDir); + const contractsDataFrom0xArtifacts = await solCompilerArtifactAdapter.collectContractsDataAsync(); + return contractsDataFrom0xArtifacts; + } + private _getTruffleConfig(): TruffleConfig { + const truffleConfigFileShort = path.resolve(path.join(this._projectRoot, 'truffle.js')); + const truffleConfigFileLong = path.resolve(path.join(this._projectRoot, 'truffle-config.js')); + if (fs.existsSync(truffleConfigFileShort)) { + const truffleConfig = require(truffleConfigFileShort); + return truffleConfig; + } else if (fs.existsSync(truffleConfigFileLong)) { + const truffleConfig = require(truffleConfigFileLong); + return truffleConfig; + } else { + throw new Error( + `Neither ${truffleConfigFileShort} nor ${truffleConfigFileLong} exists. Make sure the project root is correct`, + ); + } + } + private _assertSolidityVersionIsCorrect(truffleArtifactsDirectory: string): void { + const artifactsGlob = `${truffleArtifactsDirectory}/**/*.json`; + const artifactFileNames = glob.sync(artifactsGlob, { absolute: true }); + for (const artifactFileName of artifactFileNames) { + const artifact = JSON.parse(fs.readFileSync(artifactFileName).toString()); + const compilerVersion = artifact.compiler.version; + if (!compilerVersion.startsWith(this._solcVersion)) { + throw new Error( + `${artifact.contractName} was compiled with solidity ${compilerVersion} but specified version is ${ + this._solcVersion + } making it impossible to process traces`, + ); + } + } + } +} diff --git a/packages/sol-tracing-utils/src/ast_visitor.ts b/packages/sol-tracing-utils/src/ast_visitor.ts new file mode 100644 index 000000000..e55cdf6ec --- /dev/null +++ b/packages/sol-tracing-utils/src/ast_visitor.ts @@ -0,0 +1,168 @@ +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 readonly _fnMap: FnMap = {}; + private readonly _branchMap: BranchMap = {}; + private readonly _modifiersStatementIds: number[] = []; + private readonly _statementMap: StatementMap = {}; + private readonly _locationByOffset: LocationByOffset; + private readonly _ignoreRangesBeginningAt: number[]; + // keep track of contract/function ranges that are to be ignored + // so we can also ignore any children nodes within the contract/function + private readonly _ignoreRangesWithin: Array<[number, number]> = []; + constructor(locationByOffset: LocationByOffset, ignoreRangesBeginningAt: number[] = []) { + this._locationByOffset = locationByOffset; + this._ignoreRangesBeginningAt = ignoreRangesBeginningAt; + } + 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 ContractDefinition(ast: Parser.ContractDefinition): void { + if (this._shouldIgnoreExpression(ast)) { + this._ignoreRangesWithin.push(ast.range as [number, number]); + } + } + 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 EmitStatement(ast: any /* TODO: Parser.EmitStatement */): 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)) { + if (this._shouldIgnoreExpression(ast)) { + return; + } + this._modifiersStatementIds.push(this._entryId); + this._visitStatement(ast); + } + } + private _visitBinaryBranch( + ast: Parser.ASTNode, + left: Parser.ASTNode, + right: Parser.ASTNode, + type: BranchType, + ): void { + if (this._shouldIgnoreExpression(ast)) { + return; + } + this._branchMap[this._entryId++] = { + line: this._getExpressionRange(ast).start.line, + type, + locations: [this._getExpressionRange(left), this._getExpressionRange(right)], + }; + } + private _visitStatement(ast: Parser.ASTNode): void { + if (this._shouldIgnoreExpression(ast)) { + return; + } + this._statementMap[this._entryId++] = this._getExpressionRange(ast); + } + private _getExpressionRange(ast: Parser.ASTNode): SingleFileSourceRange { + const astRange = ast.range as [number, number]; + const start = this._locationByOffset[astRange[0]]; + const end = this._locationByOffset[astRange[1] + 1]; + const range = { + start, + end, + }; + return range; + } + private _shouldIgnoreExpression(ast: Parser.ASTNode): boolean { + const [astStart, astEnd] = ast.range as [number, number]; + const isRangeIgnored = _.some( + this._ignoreRangesWithin, + ([rangeStart, rangeEnd]: [number, number]) => astStart >= rangeStart && astEnd <= rangeEnd, + ); + return this._ignoreRangesBeginningAt.includes(astStart) || isRangeIgnored; + } + private _visitFunctionLikeDefinition(ast: Parser.ModifierDefinition | Parser.FunctionDefinition): void { + if (this._shouldIgnoreExpression(ast)) { + this._ignoreRangesWithin.push(ast.range as [number, number]); + return; + } + const loc = this._getExpressionRange(ast); + this._fnMap[this._entryId++] = { + name: ast.name, + line: loc.start.line, + loc, + }; + this._visitStatement(ast); + } +} diff --git a/packages/sol-tracing-utils/src/collect_coverage_entries.ts b/packages/sol-tracing-utils/src/collect_coverage_entries.ts new file mode 100644 index 000000000..bdbcd613e --- /dev/null +++ b/packages/sol-tracing-utils/src/collect_coverage_entries.ts @@ -0,0 +1,41 @@ +import * as ethUtil from 'ethereumjs-util'; +import * as _ from 'lodash'; +import * as parser from 'solidity-parser-antlr'; + +import { ASTVisitor, CoverageEntriesDescription } from './ast_visitor'; +import { getLocationByOffset } from './source_maps'; + +const IGNORE_RE = /\/\*\s*solcov\s+ignore\s+next\s*\*\/\s*/gm; + +// Parsing source code for each transaction/code is slow and therefore we cache it +const coverageEntriesBySourceHash: { [sourceHash: string]: CoverageEntriesDescription } = {}; + +export const collectCoverageEntries = (contractSource: string) => { + const sourceHash = ethUtil.sha3(contractSource).toString('hex'); + if (_.isUndefined(coverageEntriesBySourceHash[sourceHash]) && !_.isUndefined(contractSource)) { + const ast = parser.parse(contractSource, { range: true }); + const locationByOffset = getLocationByOffset(contractSource); + const ignoreRangesBegingingAt = gatherRangesToIgnore(contractSource); + const visitor = new ASTVisitor(locationByOffset, ignoreRangesBegingingAt); + parser.visit(ast, visitor); + coverageEntriesBySourceHash[sourceHash] = visitor.getCollectedCoverageEntries(); + } + const coverageEntriesDescription = coverageEntriesBySourceHash[sourceHash]; + return coverageEntriesDescription; +}; + +// Gather the start index of all code blocks preceeded by "/* solcov ignore next */" +function gatherRangesToIgnore(contractSource: string): number[] { + const ignoreRangesStart = []; + + let match; + do { + match = IGNORE_RE.exec(contractSource); + if (match) { + const matchLen = match[0].length; + ignoreRangesStart.push(match.index + matchLen); + } + } while (match); + + return ignoreRangesStart; +} diff --git a/packages/sol-tracing-utils/src/constants.ts b/packages/sol-tracing-utils/src/constants.ts new file mode 100644 index 000000000..34d62b537 --- /dev/null +++ b/packages/sol-tracing-utils/src/constants.ts @@ -0,0 +1,8 @@ +// tslint:disable:number-literal-format +export const constants = { + NEW_CONTRACT: 'NEW_CONTRACT' as 'NEW_CONTRACT', + PUSH1: 0x60, + PUSH2: 0x61, + PUSH32: 0x7f, + TIMESTAMP: 0x42, +}; diff --git a/packages/sol-tracing-utils/src/get_source_range_snippet.ts b/packages/sol-tracing-utils/src/get_source_range_snippet.ts new file mode 100644 index 000000000..f578675d3 --- /dev/null +++ b/packages/sol-tracing-utils/src/get_source_range_snippet.ts @@ -0,0 +1,185 @@ +import * as ethUtil from 'ethereumjs-util'; +import * as _ from 'lodash'; +import * as Parser from 'solidity-parser-antlr'; + +import { SingleFileSourceRange, SourceRange, SourceSnippet } from './types'; +import { utils } from './utils'; + +interface ASTInfo { + type: string; + node: Parser.ASTNode; + name: string | null; + range?: SingleFileSourceRange; +} + +// Parsing source code for each transaction/code is slow and therefore we cache it +const parsedSourceByHash: { [sourceHash: string]: Parser.ASTNode } = {}; + +/** + * Gets the source range snippet by source range to be used by revert trace. + * @param sourceRange source range + * @param sourceCode source code + */ +export function getSourceRangeSnippet(sourceRange: SourceRange, sourceCode: string): SourceSnippet | null { + const sourceHash = ethUtil.sha3(sourceCode).toString('hex'); + if (_.isUndefined(parsedSourceByHash[sourceHash])) { + parsedSourceByHash[sourceHash] = Parser.parse(sourceCode, { loc: true }); + } + const astNode = parsedSourceByHash[sourceHash]; + const visitor = new ASTInfoVisitor(); + Parser.visit(astNode, visitor); + const astInfo = visitor.getASTInfoForRange(sourceRange); + if (astInfo === null) { + return null; + } + const sourceCodeInRange = utils.getRange(sourceCode, sourceRange.location); + return { + ...astInfo, + range: astInfo.range as SingleFileSourceRange, + source: sourceCodeInRange, + fileName: sourceRange.fileName, + }; +} + +// A visitor which collects ASTInfo for most nodes in the AST. +class ASTInfoVisitor { + private readonly _astInfos: ASTInfo[] = []; + public getASTInfoForRange(sourceRange: SourceRange): ASTInfo | null { + // HACK(albrow): Sometimes the source range doesn't exactly match that + // of astInfo. To work around that we try with a +/-1 offset on + // end.column. If nothing matches even with the offset, we return null. + const offset = { + start: { + line: 0, + column: 0, + }, + end: { + line: 0, + column: 0, + }, + }; + let astInfo = this._getASTInfoForRange(sourceRange, offset); + if (astInfo !== null) { + return astInfo; + } + offset.end.column += 1; + astInfo = this._getASTInfoForRange(sourceRange, offset); + if (astInfo !== null) { + return astInfo; + } + offset.end.column -= 2; + astInfo = this._getASTInfoForRange(sourceRange, offset); + if (astInfo !== null) { + return astInfo; + } + return null; + } + public ContractDefinition(ast: Parser.ContractDefinition): void { + this._visitContractDefinition(ast); + } + public IfStatement(ast: Parser.IfStatement): void { + this._visitStatement(ast); + } + 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 EmitStatement(ast: any /* TODO: Parser.EmitStatement */): 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 ModifierInvocation(ast: Parser.ModifierInvocation): void { + const BUILTIN_MODIFIERS = ['public', 'view', 'payable', 'external', 'internal', 'pure', 'constant']; + if (!_.includes(BUILTIN_MODIFIERS, ast.name)) { + this._visitStatement(ast); + } + } + private _visitStatement(ast: Parser.ASTNode): void { + this._astInfos.push({ + type: ast.type, + node: ast, + name: null, + range: ast.loc, + }); + } + private _visitFunctionLikeDefinition(ast: Parser.ModifierDefinition | Parser.FunctionDefinition): void { + this._astInfos.push({ + type: ast.type, + node: ast, + name: ast.name, + range: ast.loc, + }); + } + private _visitContractDefinition(ast: Parser.ContractDefinition): void { + this._astInfos.push({ + type: ast.type, + node: ast, + name: ast.name, + range: ast.loc, + }); + } + private _getASTInfoForRange(sourceRange: SourceRange, offset: SingleFileSourceRange): ASTInfo | null { + const offsetSourceRange = { + ...sourceRange, + location: { + start: { + line: sourceRange.location.start.line + offset.start.line, + column: sourceRange.location.start.column + offset.start.column, + }, + end: { + line: sourceRange.location.end.line + offset.end.line, + column: sourceRange.location.end.column + offset.end.column, + }, + }, + }; + for (const astInfo of this._astInfos) { + const astInfoRange = astInfo.range as SingleFileSourceRange; + if ( + astInfoRange.start.column === offsetSourceRange.location.start.column && + astInfoRange.start.line === offsetSourceRange.location.start.line && + astInfoRange.end.column === offsetSourceRange.location.end.column && + astInfoRange.end.line === offsetSourceRange.location.end.line + ) { + return astInfo; + } + } + return null; + } +} diff --git a/packages/sol-tracing-utils/src/globals.d.ts b/packages/sol-tracing-utils/src/globals.d.ts new file mode 100644 index 000000000..e799b3529 --- /dev/null +++ b/packages/sol-tracing-utils/src/globals.d.ts @@ -0,0 +1,7 @@ +// tslint:disable:completed-docs +declare module '*.json' { + const json: any; + /* tslint:disable */ + export default json; + /* tslint:enable */ +} diff --git a/packages/sol-tracing-utils/src/index.ts b/packages/sol-tracing-utils/src/index.ts new file mode 100644 index 000000000..413e5305e --- /dev/null +++ b/packages/sol-tracing-utils/src/index.ts @@ -0,0 +1,39 @@ +export { SolCompilerArtifactAdapter } from './artifact_adapters/sol_compiler_artifact_adapter'; +export { TruffleArtifactAdapter } from './artifact_adapters/truffle_artifact_adapter'; +export { AbstractArtifactAdapter } from './artifact_adapters/abstract_artifact_adapter'; + +export { + ContractData, + EvmCallStack, + SourceRange, + SourceSnippet, + StatementCoverage, + StatementDescription, + BranchCoverage, + BranchDescription, + Subtrace, + TraceInfo, + Coverage, + LineColumn, + LineCoverage, + FunctionCoverage, + FunctionDescription, + SingleFileSourceRange, + BranchMap, + EvmCallStackEntry, + FnMap, + LocationByOffset, + StatementMap, + TraceInfoBase, + TraceInfoExistingContract, + TraceInfoNewContract, +} from './types'; +export { collectCoverageEntries } from './collect_coverage_entries'; +export { TraceCollector, SingleFileSubtraceHandler } from './trace_collector'; +export { TraceInfoSubprovider } from './trace_info_subprovider'; +export { utils } from './utils'; +export { constants } from './constants'; +export { parseSourceMap } from './source_maps'; +export { getSourceRangeSnippet } from './get_source_range_snippet'; +export { getRevertTrace } from './revert_trace'; +export { TraceCollectionSubprovider } from './trace_collection_subprovider'; diff --git a/packages/sol-tracing-utils/src/instructions.ts b/packages/sol-tracing-utils/src/instructions.ts new file mode 100644 index 000000000..40987dbe5 --- /dev/null +++ b/packages/sol-tracing-utils/src/instructions.ts @@ -0,0 +1,23 @@ +import { constants } from './constants'; + +const isPush = (inst: number) => inst >= constants.PUSH1 && inst <= constants.PUSH32; + +const pushDataLength = (inst: number) => inst - constants.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; +}; diff --git a/packages/sol-tracing-utils/src/revert_trace.ts b/packages/sol-tracing-utils/src/revert_trace.ts new file mode 100644 index 000000000..4d474120c --- /dev/null +++ b/packages/sol-tracing-utils/src/revert_trace.ts @@ -0,0 +1,95 @@ +import { logUtils } from '@0x/utils'; +import { OpCode, StructLog } from 'ethereum-types'; + +import * as _ from 'lodash'; + +import { EvmCallStack } from './types'; +import { utils } from './utils'; + +/** + * Converts linear trace to a call stack by following calls and returns + * @param structLogs Linear trace + * @param startAddress The address of initial context + */ +export function getRevertTrace(structLogs: StructLog[], startAddress: string): EvmCallStack { + const evmCallStack: EvmCallStack = []; + const addressStack = [startAddress]; + if (_.isEmpty(structLogs)) { + return []; + } + const normalizedStructLogs = utils.normalizeStructLogs(structLogs); + // tslint:disable-next-line:prefer-for-of + for (let i = 0; i < normalizedStructLogs.length; i++) { + const structLog = normalizedStructLogs[i]; + if (structLog.depth !== addressStack.length - 1) { + throw new Error("Malformed trace. Trace depth doesn't match call stack depth"); + } + // After that check we have a guarantee that call stack is never empty + // If it would: callStack.length - 1 === structLog.depth === -1 + // That means that we can always safely pop from it + + if (utils.isCallLike(structLog.op)) { + const currentAddress = _.last(addressStack) as string; + const jumpAddressOffset = 1; + const newAddress = utils.getAddressFromStackEntry( + structLog.stack[structLog.stack.length - jumpAddressOffset - 1], + ); + + // Sometimes calls don't change the execution context (current address). When we do a transfer to an + // externally owned account - it does the call and immediately returns because there is no fallback + // function. We manually check if the call depth had changed to handle that case. + const nextStructLog = normalizedStructLogs[i + 1]; + if (nextStructLog.depth !== structLog.depth) { + addressStack.push(newAddress); + evmCallStack.push({ + address: currentAddress, + structLog, + }); + } + } else if (utils.isEndOpcode(structLog.op) && structLog.op !== OpCode.Revert) { + // Just like with calls, sometimes returns/stops don't change the execution context (current address). + const nextStructLog = normalizedStructLogs[i + 1]; + if (_.isUndefined(nextStructLog) || nextStructLog.depth !== structLog.depth) { + evmCallStack.pop(); + addressStack.pop(); + } + if (structLog.op === OpCode.SelfDestruct) { + // After contract execution, we look at all sub-calls to external contracts, and for each one, fetch + // the bytecode and compute the coverage for the call. If the contract is destroyed with a call + // to `selfdestruct`, we are unable to fetch it's bytecode and compute coverage. + // TODO: Refactor this logic to fetch the sub-called contract bytecode before the selfdestruct is called + // in order to handle this edge-case. + logUtils.warn( + "Detected a selfdestruct. We currently do not support that scenario. We'll just skip the trace part for a destructed contract", + ); + } + } else if (structLog.op === OpCode.Revert) { + evmCallStack.push({ + address: _.last(addressStack) as string, + structLog, + }); + return evmCallStack; + } else if (structLog.op === OpCode.Create) { + // TODO: Extract the new contract address from the stack and handle that scenario + logUtils.warn( + "Detected a contract created from within another contract. We currently do not support that scenario. We'll just skip that trace", + ); + return []; + } else { + if (structLog !== _.last(normalizedStructLogs)) { + const nextStructLog = normalizedStructLogs[i + 1]; + if (nextStructLog.depth === structLog.depth) { + continue; + } else if (nextStructLog.depth === structLog.depth - 1) { + addressStack.pop(); + } else { + throw new Error('Malformed trace. Unexpected call depth change'); + } + } + } + } + if (evmCallStack.length !== 0) { + logUtils.warn('Malformed trace. Call stack non empty at the end. (probably out of gas)'); + } + return []; +} diff --git a/packages/sol-tracing-utils/src/source_maps.ts b/packages/sol-tracing-utils/src/source_maps.ts new file mode 100644 index 000000000..af0fb4035 --- /dev/null +++ b/packages/sol-tracing-utils/src/source_maps.ts @@ -0,0 +1,91 @@ +import * as _ from 'lodash'; + +import { getPcToInstructionIndexMapping } from './instructions'; +import { LocationByOffset, SourceRange } from './types'; + +const RADIX = 10; + +export interface SourceLocation { + offset: number; + length: number; + fileIndex: number; +} + +/** + * Receives a string with newlines and returns a map of byte offset to LineColumn + * @param str A string to process + */ +export function getLocationByOffset(str: string): LocationByOffset { + const locationByOffset: LocationByOffset = { 0: { line: 1, column: 0 } }; + let currentOffset = 0; + for (const char of str.split('')) { + const location = locationByOffset[currentOffset]; + const isNewline = char === '\n'; + locationByOffset[currentOffset + 1] = { + 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 + * @param sourceCodes sources contents + * @param srcMap source map string + * @param bytecodeHex contract bytecode + * @param sources sources file names + */ +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, s => (_.isUndefined(s) ? {} : getLocationByOffset(s))); + const entries = srcMap.split(';'); + let lastParsedEntry: SourceLocation = {} as any; + const instructionIndexToSourceRange: { [instructionIndex: number]: SourceRange } = {}; + _.each(entries, (entry: string, i: number) => { + // tslint:disable-next-line:no-unused-variable + 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 && !_.isUndefined(locationByOffsetByFileIndex[parsedEntry.fileIndex])) { + const sourceRange = { + location: { + start: locationByOffsetByFileIndex[parsedEntry.fileIndex][parsedEntry.offset], + end: locationByOffsetByFileIndex[parsedEntry.fileIndex][parsedEntry.offset + parsedEntry.length], + }, + 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; +} diff --git a/packages/sol-tracing-utils/src/trace.ts b/packages/sol-tracing-utils/src/trace.ts new file mode 100644 index 000000000..770080af3 --- /dev/null +++ b/packages/sol-tracing-utils/src/trace.ts @@ -0,0 +1,104 @@ +import { logUtils } from '@0x/utils'; +import { OpCode, StructLog } from 'ethereum-types'; +import * as _ from 'lodash'; + +import { utils } from './utils'; + +export interface TraceByContractAddress { + [contractAddress: string]: StructLog[]; +} + +/** + * Converts linear stack trace to `TraceByContractAddress`. + * @param structLogs stack trace + * @param startAddress initial context address + */ +export function getTracesByContractAddress(structLogs: StructLog[], startAddress: string): TraceByContractAddress { + const traceByContractAddress: TraceByContractAddress = {}; + let currentTraceSegment = []; + const addressStack = [startAddress]; + if (_.isEmpty(structLogs)) { + return traceByContractAddress; + } + const normalizedStructLogs = utils.normalizeStructLogs(structLogs); + // tslint:disable-next-line:prefer-for-of + for (let i = 0; i < normalizedStructLogs.length; i++) { + const structLog = normalizedStructLogs[i]; + if (structLog.depth !== addressStack.length - 1) { + throw new Error("Malformed trace. Trace depth doesn't match call stack depth"); + } + // After that check we have a guarantee that call stack is never empty + // If it would: callStack.length - 1 === structLog.depth === -1 + // That means that we can always safely pop from it + currentTraceSegment.push(structLog); + + if (utils.isCallLike(structLog.op)) { + const currentAddress = _.last(addressStack) as string; + const jumpAddressOffset = 1; + const newAddress = utils.getAddressFromStackEntry( + structLog.stack[structLog.stack.length - jumpAddressOffset - 1], + ); + + // Sometimes calls don't change the execution context (current address). When we do a transfer to an + // externally owned account - it does the call and immediately returns because there is no fallback + // function. We manually check if the call depth had changed to handle that case. + const nextStructLog = normalizedStructLogs[i + 1]; + if (nextStructLog.depth !== structLog.depth) { + addressStack.push(newAddress); + traceByContractAddress[currentAddress] = (traceByContractAddress[currentAddress] || []).concat( + currentTraceSegment, + ); + currentTraceSegment = []; + } + } else if (utils.isEndOpcode(structLog.op)) { + const currentAddress = addressStack.pop() as string; + traceByContractAddress[currentAddress] = (traceByContractAddress[currentAddress] || []).concat( + currentTraceSegment, + ); + currentTraceSegment = []; + if (structLog.op === OpCode.SelfDestruct) { + // After contract execution, we look at all sub-calls to external contracts, and for each one, fetch + // the bytecode and compute the coverage for the call. If the contract is destroyed with a call + // to `selfdestruct`, we are unable to fetch it's bytecode and compute coverage. + // TODO: Refactor this logic to fetch the sub-called contract bytecode before the selfdestruct is called + // in order to handle this edge-case. + logUtils.warn( + "Detected a selfdestruct. We currently do not support that scenario. We'll just skip the trace part for a destructed contract", + ); + } + } else if (structLog.op === OpCode.Create) { + // TODO: Extract the new contract address from the stack and handle that scenario + logUtils.warn( + "Detected a contract created from within another contract. We currently do not support that scenario. We'll just skip that trace", + ); + return traceByContractAddress; + } else { + if (structLog !== _.last(normalizedStructLogs)) { + const nextStructLog = normalizedStructLogs[i + 1]; + if (nextStructLog.depth === structLog.depth) { + continue; + } else if (nextStructLog.depth === structLog.depth - 1) { + const currentAddress = addressStack.pop() as string; + traceByContractAddress[currentAddress] = (traceByContractAddress[currentAddress] || []).concat( + currentTraceSegment, + ); + currentTraceSegment = []; + } else { + throw new Error('Malformed trace. Unexpected call depth change'); + } + } + } + } + if (addressStack.length !== 0) { + logUtils.warn('Malformed trace. Call stack non empty at the end'); + } + if (currentTraceSegment.length !== 0) { + const currentAddress = addressStack.pop() as string; + traceByContractAddress[currentAddress] = (traceByContractAddress[currentAddress] || []).concat( + currentTraceSegment, + ); + currentTraceSegment = []; + logUtils.warn('Malformed trace. Current trace segment non empty at the end'); + } + return traceByContractAddress; +} diff --git a/packages/sol-tracing-utils/src/trace_collection_subprovider.ts b/packages/sol-tracing-utils/src/trace_collection_subprovider.ts new file mode 100644 index 000000000..25e38768d --- /dev/null +++ b/packages/sol-tracing-utils/src/trace_collection_subprovider.ts @@ -0,0 +1,188 @@ +import { BlockchainLifecycle } from '@0x/dev-utils'; +import { Callback, ErrorCallback, NextCallback, Subprovider } from '@0x/subproviders'; +import { CallDataRPC, marshaller, Web3Wrapper } from '@0x/web3-wrapper'; +import { JSONRPCRequestPayload, Provider, TxData } from 'ethereum-types'; +import * as _ from 'lodash'; +import { Lock } from 'semaphore-async-await'; + +import { constants } from './constants'; +import { BlockParamLiteral } from './types'; + +interface MaybeFakeTxData extends TxData { + isFakeTransaction?: boolean; +} + +const BLOCK_GAS_LIMIT = 6000000; + +export interface TraceCollectionSubproviderConfig { + shouldCollectTransactionTraces: boolean; + shouldCollectCallTraces: boolean; + shouldCollectGasEstimateTraces: boolean; +} + +// Because there is no notion of a call trace in the Ethereum rpc - we collect them in a 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 avoid influencing test behaviour. + +/** + * This class implements the [web3-provider-engine](https://github.com/MetaMask/provider-engine) subprovider interface. + * It collects traces of all transactions that were sent and all calls that were executed through JSON RPC. It must + * be extended by implementing the _recordTxTraceAsync method which is called for every transaction. + */ +export abstract class TraceCollectionSubprovider extends Subprovider { + protected _web3Wrapper!: Web3Wrapper; + // Lock is used to not accept normal transactions while doing call/snapshot magic because they'll be reverted later otherwise + private readonly _lock = new Lock(); + private readonly _defaultFromAddress: string; + private _isEnabled = true; + private readonly _config: TraceCollectionSubproviderConfig; + /** + * Instantiates a TraceCollectionSubprovider instance + * @param defaultFromAddress default from address to use when sending transactions + */ + constructor(defaultFromAddress: string, config: TraceCollectionSubproviderConfig) { + super(); + this._defaultFromAddress = defaultFromAddress; + this._config = config; + } + /** + * Starts trace collection + */ + public start(): void { + this._isEnabled = true; + } + /** + * Stops trace collection + */ + public stop(): void { + this._isEnabled = false; + } + /** + * This method conforms to the web3-provider-engine interface. + * It is called internally by the ProviderEngine when it is this subproviders + * turn to handle a JSON RPC request. + * @param payload JSON RPC payload + * @param next Callback to call if this subprovider decides not to handle the request + * @param _end Callback to call if subprovider handled the request and wants to pass back the request. + */ + // tslint:disable-next-line:prefer-function-over-method async-suffix + public async handleRequest(payload: JSONRPCRequestPayload, next: NextCallback, _end: ErrorCallback): Promise { + if (this._isEnabled) { + switch (payload.method) { + case 'eth_sendTransaction': + if (!this._config.shouldCollectTransactionTraces) { + next(); + } else { + const txData = payload.params[0]; + next(this._onTransactionSentAsync.bind(this, txData)); + } + return; + + case 'eth_call': + if (!this._config.shouldCollectCallTraces) { + next(); + } else { + const callData = payload.params[0]; + next(this._onCallOrGasEstimateExecutedAsync.bind(this, callData)); + } + return; + + case 'eth_estimateGas': + if (!this._config.shouldCollectGasEstimateTraces) { + next(); + } else { + const estimateGasData = payload.params[0]; + next(this._onCallOrGasEstimateExecutedAsync.bind(this, estimateGasData)); + } + return; + + default: + next(); + return; + } + } else { + next(); + return; + } + } + /** + * Set's the subprovider's engine to the ProviderEngine it is added to. + * This is only called within the ProviderEngine source code, do not call + * directly. + * @param engine The ProviderEngine this subprovider is added to + */ + public setEngine(engine: Provider): void { + super.setEngine(engine); + this._web3Wrapper = new Web3Wrapper(engine); + } + protected abstract async _recordTxTraceAsync( + address: string, + data: string | undefined, + txHash: string, + ): Promise; + private async _onTransactionSentAsync( + txData: MaybeFakeTxData, + err: Error | null, + txHash: string | undefined, + cb: Callback, + ): Promise { + if (!txData.isFakeTransaction) { + // This transaction is a usual transaction. Not a call executed as one. + // And we don't want it to be executed within a snapshotting period + await this._lock.acquire(); + } + const NULL_ADDRESS = '0x0'; + if (_.isNull(err)) { + const toAddress = + _.isUndefined(txData.to) || txData.to === NULL_ADDRESS ? constants.NEW_CONTRACT : txData.to; + await this._recordTxTraceAsync(toAddress, txData.data, txHash as string); + } else { + const latestBlock = await this._web3Wrapper.getBlockWithTransactionDataAsync(BlockParamLiteral.Latest); + const transactions = latestBlock.transactions; + for (const transaction of transactions) { + const toAddress = + _.isUndefined(txData.to) || txData.to === NULL_ADDRESS ? constants.NEW_CONTRACT : txData.to; + await this._recordTxTraceAsync(toAddress, transaction.input, transaction.hash); + } + } + if (!txData.isFakeTransaction) { + // This transaction is a usual transaction. 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 _onCallOrGasEstimateExecutedAsync( + callData: Partial, + _err: Error | null, + _callResult: string, + cb: Callback, + ): Promise { + await this._recordCallOrGasEstimateTraceAsync(callData); + cb(); + } + private async _recordCallOrGasEstimateTraceAsync(callData: Partial): Promise { + // 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 blockchainLifecycle = new BlockchainLifecycle(this._web3Wrapper); + await blockchainLifecycle.startAsync(); + const fakeTxData = { + gas: BLOCK_GAS_LIMIT.toString(16), // tslint:disable-line:custom-no-magic-numbers + isFakeTransaction: true, // This transaction (and only it) is allowed to come through when the lock is locked + ...callData, + from: callData.from || this._defaultFromAddress, + }; + try { + const txData = marshaller.unmarshalTxData(fakeTxData); + const txHash = await this._web3Wrapper.sendTransactionAsync(txData); + await this._web3Wrapper.awaitTransactionMinedAsync(txHash, 0); + } catch (err) { + // TODO(logvinov) Check that transaction failed and not some other exception + // Even if this transaction failed - we've already recorded it's trace. + _.noop(); + } + await blockchainLifecycle.revertAsync(); + this._lock.release(); + } +} diff --git a/packages/sol-tracing-utils/src/trace_collector.ts b/packages/sol-tracing-utils/src/trace_collector.ts new file mode 100644 index 000000000..943e208cf --- /dev/null +++ b/packages/sol-tracing-utils/src/trace_collector.ts @@ -0,0 +1,93 @@ +import { promisify } from '@0x/utils'; +import { stripHexPrefix } from 'ethereumjs-util'; +import * as fs from 'fs'; +import { Collector } from 'istanbul'; +import * as _ from 'lodash'; +import { getLogger, levels, Logger } from 'loglevel'; +import * as mkdirp from 'mkdirp'; + +import { AbstractArtifactAdapter } from './artifact_adapters/abstract_artifact_adapter'; +import { constants } from './constants'; +import { parseSourceMap } from './source_maps'; +import { + ContractData, + Coverage, + SourceRange, + Subtrace, + TraceInfo, + TraceInfoExistingContract, + TraceInfoNewContract, +} from './types'; +import { utils } from './utils'; + +const mkdirpAsync = promisify(mkdirp); + +export type SingleFileSubtraceHandler = ( + contractData: ContractData, + subtrace: Subtrace, + pcToSourceRange: { [programCounter: number]: SourceRange }, + fileIndex: number, +) => Coverage; + +/** + * TraceCollector is used by CoverageSubprovider to compute code coverage based on collected trace data. + */ +export class TraceCollector { + private readonly _artifactAdapter: AbstractArtifactAdapter; + private readonly _logger: Logger; + private _contractsData!: ContractData[]; + private readonly _collector = new Collector(); + private readonly _singleFileSubtraceHandler: SingleFileSubtraceHandler; + + /** + * Instantiates a TraceCollector instance + * @param artifactAdapter Adapter for used artifacts format (0x, truffle, giveth, etc.) + * @param isVerbose If true, we will log any unknown transactions. Otherwise we will ignore them + * @param singleFileSubtraceHandler A handler function for computing partial coverage for a single file & subtrace + */ + constructor( + artifactAdapter: AbstractArtifactAdapter, + isVerbose: boolean, + singleFileSubtraceHandler: SingleFileSubtraceHandler, + ) { + this._artifactAdapter = artifactAdapter; + this._logger = getLogger('sol-tracing-utils'); + this._logger.setLevel(isVerbose ? levels.TRACE : levels.ERROR); + this._singleFileSubtraceHandler = singleFileSubtraceHandler; + } + public async writeOutputAsync(): Promise { + const finalCoverage = this._collector.getFinalCoverage(); + const stringifiedCoverage = JSON.stringify(finalCoverage, null, '\t'); + await mkdirpAsync('coverage'); + fs.writeFileSync('coverage/coverage.json', stringifiedCoverage); + } + public async computeSingleTraceCoverageAsync(traceInfo: TraceInfo): Promise { + if (_.isUndefined(this._contractsData)) { + this._contractsData = await this._artifactAdapter.collectContractsDataAsync(); + } + const isContractCreation = traceInfo.address === constants.NEW_CONTRACT; + const bytecode = isContractCreation + ? (traceInfo as TraceInfoNewContract).bytecode + : (traceInfo as TraceInfoExistingContract).runtimeBytecode; + const contractData = utils.getContractDataIfExists(this._contractsData, bytecode); + if (_.isUndefined(contractData)) { + const errMsg = isContractCreation + ? `Unknown contract creation transaction` + : `Transaction to an unknown address: ${traceInfo.address}`; + this._logger.warn(errMsg); + return; + } + const bytecodeHex = stripHexPrefix(bytecode); + const sourceMap = isContractCreation ? contractData.sourceMap : contractData.sourceMapRuntime; + const pcToSourceRange = parseSourceMap(contractData.sourceCodes, sourceMap, bytecodeHex, contractData.sources); + for (let fileIndex = 0; fileIndex < contractData.sources.length; fileIndex++) { + const singleFileCoverageForTrace = this._singleFileSubtraceHandler( + contractData, + traceInfo.subtrace, + pcToSourceRange, + fileIndex, + ); + this._collector.add(singleFileCoverageForTrace); + } + } +} diff --git a/packages/sol-tracing-utils/src/trace_info_subprovider.ts b/packages/sol-tracing-utils/src/trace_info_subprovider.ts new file mode 100644 index 000000000..635a68f58 --- /dev/null +++ b/packages/sol-tracing-utils/src/trace_info_subprovider.ts @@ -0,0 +1,59 @@ +import * as _ from 'lodash'; + +import { constants } from './constants'; +import { getTracesByContractAddress } from './trace'; +import { TraceCollectionSubprovider } from './trace_collection_subprovider'; +import { TraceInfo, TraceInfoExistingContract, TraceInfoNewContract } from './types'; + +// TraceInfoSubprovider is extended by subproviders which need to work with one +// TraceInfo at a time. It has one abstract method: _handleTraceInfoAsync, which +// is called for each TraceInfo. +export abstract class TraceInfoSubprovider extends TraceCollectionSubprovider { + protected abstract _handleTraceInfoAsync(traceInfo: TraceInfo): Promise; + protected async _recordTxTraceAsync(address: string, data: string | undefined, txHash: string): Promise { + await this._web3Wrapper.awaitTransactionMinedAsync(txHash, 0); + const trace = await this._web3Wrapper.getTransactionTraceAsync(txHash, { + disableMemory: true, + disableStack: false, + disableStorage: true, + }); + const tracesByContractAddress = getTracesByContractAddress(trace.structLogs, address); + const subcallAddresses = _.keys(tracesByContractAddress); + if (address === constants.NEW_CONTRACT) { + for (const subcallAddress of subcallAddresses) { + let traceInfo: TraceInfoNewContract | TraceInfoExistingContract; + if (subcallAddress === 'NEW_CONTRACT') { + const traceForThatSubcall = tracesByContractAddress[subcallAddress]; + traceInfo = { + subtrace: traceForThatSubcall, + txHash, + address: subcallAddress, + bytecode: data as string, + }; + } else { + const runtimeBytecode = await this._web3Wrapper.getContractCodeAsync(subcallAddress); + const traceForThatSubcall = tracesByContractAddress[subcallAddress]; + traceInfo = { + subtrace: traceForThatSubcall, + txHash, + address: subcallAddress, + runtimeBytecode, + }; + } + await this._handleTraceInfoAsync(traceInfo); + } + } else { + for (const subcallAddress of subcallAddresses) { + const runtimeBytecode = await this._web3Wrapper.getContractCodeAsync(subcallAddress); + const traceForThatSubcall = tracesByContractAddress[subcallAddress]; + const traceInfo: TraceInfoExistingContract = { + subtrace: traceForThatSubcall, + txHash, + address: subcallAddress, + runtimeBytecode, + }; + await this._handleTraceInfoAsync(traceInfo); + } + } + } +} diff --git a/packages/sol-tracing-utils/src/types.ts b/packages/sol-tracing-utils/src/types.ts new file mode 100644 index 000000000..54ade0400 --- /dev/null +++ b/packages/sol-tracing-utils/src/types.ts @@ -0,0 +1,126 @@ +import { StructLog } from 'ethereum-types'; +import * as Parser from 'solidity-parser-antlr'; + +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]: number; +} + +export interface FunctionCoverage { + [functionId: string]: number; +} + +export interface StatementCoverage { + [statementId: string]: number; +} + +export interface BranchCoverage { + [branchId: string]: number[]; +} + +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[]; + sources: string[]; +} + +// Part of the trace executed within the same context +export type Subtrace = StructLog[]; + +export interface TraceInfoBase { + subtrace: Subtrace; + 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; + +export enum BlockParamLiteral { + Latest = 'latest', +} + +export interface EvmCallStackEntry { + structLog: StructLog; + address: string; +} + +export type EvmCallStack = EvmCallStackEntry[]; + +export interface SourceSnippet { + source: string; + fileName: string; + type: string; + node: Parser.ASTNode; + name: string | null; + range: SingleFileSourceRange; +} diff --git a/packages/sol-tracing-utils/src/utils.ts b/packages/sol-tracing-utils/src/utils.ts new file mode 100644 index 000000000..d8bc65e73 --- /dev/null +++ b/packages/sol-tracing-utils/src/utils.ts @@ -0,0 +1,87 @@ +import { addressUtils, BigNumber } from '@0x/utils'; +import { OpCode, StructLog } from 'ethereum-types'; +import { addHexPrefix } from 'ethereumjs-util'; +import * as _ from 'lodash'; + +import { ContractData, LineColumn, SingleFileSourceRange } from './types'; + +// This is the minimum length of valid contract bytecode. The Solidity compiler +// metadata is 86 bytes. If you add the '0x' prefix, we get 88. +const MIN_CONTRACT_BYTECODE_LENGTH = 88; + +export const utils = { + compareLineColumn(lhs: LineColumn, rhs: LineColumn): number { + return lhs.line !== rhs.line ? lhs.line - rhs.line : lhs.column - rhs.column; + }, + removeHexPrefix(hex: string): string { + const hexPrefix = '0x'; + return hex.startsWith(hexPrefix) ? hex.slice(hexPrefix.length) : hex; + }, + isRangeInside(childRange: SingleFileSourceRange, parentRange: SingleFileSourceRange): boolean { + return ( + utils.compareLineColumn(parentRange.start, childRange.start) <= 0 && + utils.compareLineColumn(childRange.end, parentRange.end) <= 0 + ); + }, + bytecodeToBytecodeRegex(bytecode: string): string { + const bytecodeRegex = bytecode + // Library linking placeholder: __ConvertLib____________________________ + .replace(/_.*_/, '.*') + // Last 86 characters is solidity compiler metadata that's different between compilations + .replace(/.{86}$/, '') + // Libraries contain their own address at the beginning of the code and it's impossible to know it in advance + .replace(/^0x730000000000000000000000000000000000000000/, '0x73........................................'); + // HACK: Node regexes can't be longer that 32767 characters. Contracts bytecode can. We just truncate the regexes. It's safe in practice. + const MAX_REGEX_LENGTH = 32767; + const truncatedBytecodeRegex = bytecodeRegex.slice(0, MAX_REGEX_LENGTH); + return truncatedBytecodeRegex; + }, + getContractDataIfExists(contractsData: ContractData[], bytecode: string): ContractData | undefined { + if (!bytecode.startsWith('0x')) { + throw new Error(`0x hex prefix missing: ${bytecode}`); + } + const contractData = _.find(contractsData, contractDataCandidate => { + const bytecodeRegex = utils.bytecodeToBytecodeRegex(contractDataCandidate.bytecode); + // If the bytecode is less than the minimum length, we are probably + // dealing with an interface. This isn't what we're looking for. + if (bytecodeRegex.length < MIN_CONTRACT_BYTECODE_LENGTH) { + return false; + } + const runtimeBytecodeRegex = utils.bytecodeToBytecodeRegex(contractDataCandidate.runtimeBytecode); + if (runtimeBytecodeRegex.length < MIN_CONTRACT_BYTECODE_LENGTH) { + return false; + } + // We use that function to find by bytecode or runtimeBytecode. Those are quasi-random strings so + // collisions are practically impossible and it allows us to reuse that code + return !_.isNull(bytecode.match(bytecodeRegex)) || !_.isNull(bytecode.match(runtimeBytecodeRegex)); + }); + return contractData; + }, + isCallLike(op: OpCode): boolean { + return _.includes([OpCode.CallCode, OpCode.StaticCall, OpCode.Call, OpCode.DelegateCall], op); + }, + isEndOpcode(op: OpCode): boolean { + return _.includes([OpCode.Return, OpCode.Stop, OpCode.Revert, OpCode.Invalid, OpCode.SelfDestruct], op); + }, + getAddressFromStackEntry(stackEntry: string): string { + const hexBase = 16; + return addressUtils.padZeros(new BigNumber(addHexPrefix(stackEntry)).toString(hexBase)); + }, + normalizeStructLogs(structLogs: StructLog[]): StructLog[] { + if (structLogs[0].depth === 1) { + // Geth uses 1-indexed depth counter whilst ganache starts from 0 + const newStructLogs = _.map(structLogs, structLog => ({ + ...structLog, + depth: structLog.depth - 1, + })); + return newStructLogs; + } + return structLogs; + }, + getRange(sourceCode: string, range: SingleFileSourceRange): string { + const lines = sourceCode.split('\n').slice(range.start.line - 1, range.end.line); + lines[lines.length - 1] = lines[lines.length - 1].slice(0, range.end.column); + lines[0] = lines[0].slice(range.start.column); + return lines.join('\n'); + }, +}; diff --git a/packages/sol-tracing-utils/test/collect_coverage_entries_test.ts b/packages/sol-tracing-utils/test/collect_coverage_entries_test.ts new file mode 100644 index 000000000..7832ec316 --- /dev/null +++ b/packages/sol-tracing-utils/test/collect_coverage_entries_test.ts @@ -0,0 +1,155 @@ +import * as chai from 'chai'; +import * as fs from 'fs'; +import * as _ from 'lodash'; +import 'mocha'; +import * as path from 'path'; + +import { collectCoverageEntries } from '../src/collect_coverage_entries'; +import { utils } from '../src/utils'; + +const expect = chai.expect; + +describe('Collect coverage entries', () => { + describe('#collectCoverageEntries', () => { + it('correctly collects coverage entries for Simplest contract', () => { + const simplestContractBaseName = 'Simplest.sol'; + const simplestContractFileName = path.resolve(__dirname, 'fixtures/contracts', simplestContractBaseName); + const simplestContract = fs.readFileSync(simplestContractFileName).toString(); + const coverageEntries = collectCoverageEntries(simplestContract); + expect(coverageEntries.fnMap).to.be.deep.equal({}); + expect(coverageEntries.branchMap).to.be.deep.equal({}); + expect(coverageEntries.statementMap).to.be.deep.equal({}); + expect(coverageEntries.modifiersStatementIds).to.be.deep.equal([]); + }); + it('correctly collects coverage entries for SimpleStorage contract', () => { + const simpleStorageContractBaseName = 'SimpleStorage.sol'; + const simpleStorageContractFileName = path.resolve( + __dirname, + 'fixtures/contracts', + simpleStorageContractBaseName, + ); + const simpleStorageContract = fs.readFileSync(simpleStorageContractFileName).toString(); + const coverageEntries = collectCoverageEntries(simpleStorageContract); + const fnIds = _.keys(coverageEntries.fnMap); + expect(coverageEntries.fnMap[fnIds[0]].name).to.be.equal('set'); + // tslint:disable-next-line:custom-no-magic-numbers + expect(coverageEntries.fnMap[fnIds[0]].line).to.be.equal(5); + const setFunction = `function set(uint x) { + storedData = x; + }`; + expect(utils.getRange(simpleStorageContract, coverageEntries.fnMap[fnIds[0]].loc)).to.be.equal(setFunction); + expect(coverageEntries.fnMap[fnIds[1]].name).to.be.equal('get'); + // tslint:disable-next-line:custom-no-magic-numbers + expect(coverageEntries.fnMap[fnIds[1]].line).to.be.equal(8); + const getFunction = `function get() constant returns (uint retVal) { + return storedData; + }`; + expect(utils.getRange(simpleStorageContract, coverageEntries.fnMap[fnIds[1]].loc)).to.be.equal(getFunction); + expect(coverageEntries.branchMap).to.be.deep.equal({}); + const statementIds = _.keys(coverageEntries.statementMap); + expect(utils.getRange(simpleStorageContract, coverageEntries.statementMap[statementIds[1]])).to.be.equal( + 'storedData = x', + ); + expect(utils.getRange(simpleStorageContract, coverageEntries.statementMap[statementIds[3]])).to.be.equal( + 'return storedData;', + ); + expect(coverageEntries.modifiersStatementIds).to.be.deep.equal([]); + }); + it('correctly collects coverage entries for AllSolidityFeatures contract', () => { + const simpleStorageContractBaseName = 'AllSolidityFeatures.sol'; + const simpleStorageContractFileName = path.resolve( + __dirname, + 'fixtures/contracts', + simpleStorageContractBaseName, + ); + const simpleStorageContract = fs.readFileSync(simpleStorageContractFileName).toString(); + const coverageEntries = collectCoverageEntries(simpleStorageContract); + const fnDescriptions = _.values(coverageEntries.fnMap); + const fnNames = _.map(fnDescriptions, fnDescription => fnDescription.name); + const expectedFnNames = [ + 'f', + 'c', + 'test', + 'getChoice', + 'Base', + 'Derived', + 'f', + 'f', + '', + 'g', + 'setData', + 'getData', + 'sendHalf', + 'insert', + 'remove', + 'contains', + 'iterate_start', + 'iterate_valid', + 'iterate_advance', + 'iterate_get', + 'insert', + 'sum', + 'restricted', + 'DualIndex', + 'set', + 'transfer_ownership', + 'lookup', + '', + '', + 'sum', + 'someFunction', + 'fun', + 'at', + 'test', + 'get', + 'returnNumber', + 'alloc', + 'ham', + 'getMyTuple', + 'ham', + 'abstain', + 'foobar', + 'foobar', + 'a', + ]; + expect(fnNames).to.be.deep.equal(expectedFnNames); + + const branchDescriptions = _.values(coverageEntries.branchMap); + const branchLines = _.map(branchDescriptions, branchDescription => branchDescription.line); + // tslint:disable-next-line:custom-no-magic-numbers + expect(branchLines).to.be.deep.equal([94, 115, 119, 130, 151, 187]); + const branchTypes = _.map(branchDescriptions, branchDescription => branchDescription.type); + expect(branchTypes).to.be.deep.equal(['if', 'if', 'if', 'if', 'binary-expr', 'if']); + }); + + it('correctly ignores all coverage entries for Ignore contract', () => { + const solcovIgnoreContractBaseName = 'SolcovIgnore.sol'; + const solcovIgnoreContractFileName = path.resolve( + __dirname, + 'fixtures/contracts', + solcovIgnoreContractBaseName, + ); + const solcovIgnoreContract = fs.readFileSync(solcovIgnoreContractFileName).toString(); + const coverageEntries = collectCoverageEntries(solcovIgnoreContract); + const fnIds = _.keys(coverageEntries.fnMap); + + expect(fnIds.length).to.be.equal(1); + expect(coverageEntries.fnMap[fnIds[0]].name).to.be.equal('set'); + // tslint:disable-next-line:custom-no-magic-numbers + expect(coverageEntries.fnMap[fnIds[0]].line).to.be.equal(6); + const setFunction = `function set(uint x) public { + /* solcov ignore next */ + storedData = x; + }`; + expect(utils.getRange(solcovIgnoreContract, coverageEntries.fnMap[fnIds[0]].loc)).to.be.equal(setFunction); + + expect(coverageEntries.branchMap).to.be.deep.equal({}); + const statementIds = _.keys(coverageEntries.statementMap); + expect(utils.getRange(solcovIgnoreContract, coverageEntries.statementMap[statementIds[0]])).to.be.equal( + setFunction, + ); + expect(statementIds.length).to.be.equal(1); + expect(coverageEntries.modifiersStatementIds.length).to.be.equal(0); + }); + }); +}); diff --git a/packages/sol-tracing-utils/test/fixtures/contracts/AllSolidityFeatures.sol b/packages/sol-tracing-utils/test/fixtures/contracts/AllSolidityFeatures.sol new file mode 100644 index 000000000..21137347e --- /dev/null +++ b/packages/sol-tracing-utils/test/fixtures/contracts/AllSolidityFeatures.sol @@ -0,0 +1,413 @@ +// Examples taken from the Solidity documentation online. + +// for pragma version numbers, see https://docs.npmjs.com/misc/semver#versions +pragma solidity 0.4.0; +pragma solidity ^0.4.0; + +import "SomeFile.sol"; +import "SomeFile.sol" as SomeOtherFile; +import * as SomeSymbol from "AnotherFile.sol"; +import {symbol1 as alias, symbol2} from "File.sol"; + +interface i { + function f(); +} + +contract c { + function c() + { + val1 = 1 wei; // 1 + val2 = 1 szabo; // 1 * 10 ** 12 + val3 = 1 finney; // 1 * 10 ** 15 + val4 = 1 ether; // 1 * 10 ** 18 + } + uint256 val1; + uint256 val2; + uint256 val3; + uint256 val4; +} + +contract test { + enum ActionChoices { GoLeft, GoRight, GoStraight, SitStill } + + function test() + { + choices = ActionChoices.GoStraight; + } + function getChoice() returns (uint d) + { + d = uint256(choices); + } + ActionChoices choices; +} + +contract Base { + function Base(uint i) + { + m_i = i; + } + uint public m_i; +} +contract Derived is Base(0) { + function Derived(uint i) Base(i) {} +} + +contract C { + uint248 x; // 31 bytes: slot 0, offset 0 + uint16 y; // 2 bytes: slot 1, offset 0 (does not fit in slot 0) + uint240 z; // 30 bytes: slot 1, offset 2 bytes + uint8 a; // 1 byte: slot 2, offset 0 bytes + struct S { + uint8 a; // 1 byte, slot +0, offset 0 bytes + uint256 b; // 32 bytes, slot +1, offset 0 bytes (does not fit) + } + S structData; // 2 slots, slot 3, offset 0 bytes (does not really apply) + uint8 alpha; // 1 byte, slot 4 (start new slot after struct) + uint16[3] beta; // 3*16 bytes, slots 5+6 (start new slot for array) + uint8 gamma; // 1 byte, slot 7 (start new slot after array) +} + +contract test { + function f(uint x, uint y) returns (uint z) { + var c = x + 3; + var b = 7 + (c * (8 - 7)) - x; + return -(-b | 0); + } +} + +contract test { + function f(uint x, uint y) returns (uint z) { + return 10; + } +} + +contract c { + function () returns (uint) { return g(8); } + function g(uint pos) internal returns (uint) { setData(pos, 8); return getData(pos); } + function setData(uint pos, uint value) internal { data[pos] = value; } + function getData(uint pos) internal { return data[pos]; } + mapping(uint => uint) data; +} + +contract Sharer { + function sendHalf(address addr) returns (uint balance) { + if (!addr.send(msg.value/2)) + throw; // also reverts the transfer to Sharer + return address(this).balance; + } +} + +/// @dev Models a modifiable and iterable set of uint values. +library IntegerSet +{ + struct data + { + /// Mapping item => index (or zero if not present) + mapping(uint => uint) index; + /// Items by index (index 0 is invalid), items with index[item] == 0 are invalid. + uint[] items; + /// Number of stored items. + uint size; + } + function insert(data storage self, uint value) returns (bool alreadyPresent) + { + uint index = self.index[value]; + if (index > 0) + return true; + else + { + if (self.items.length == 0) self.items.length = 1; + index = self.items.length++; + self.items[index] = value; + self.index[value] = index; + self.size++; + return false; + } + } + function remove(data storage self, uint value) returns (bool success) + { + uint index = self.index[value]; + if (index == 0) + return false; + delete self.index[value]; + delete self.items[index]; + self.size --; + } + function contains(data storage self, uint value) returns (bool) + { + return self.index[value] > 0; + } + function iterate_start(data storage self) returns (uint index) + { + return iterate_advance(self, 0); + } + function iterate_valid(data storage self, uint index) returns (bool) + { + return index < self.items.length; + } + function iterate_advance(data storage self, uint index) returns (uint r_index) + { + index++; + while (iterate_valid(self, index) && self.index[self.items[index]] == index) + index++; + return index; + } + function iterate_get(data storage self, uint index) returns (uint value) + { + return self.items[index]; + } +} + +/// How to use it: +contract User +{ + /// Just a struct holding our data. + IntegerSet.data data; + /// Insert something + function insert(uint v) returns (uint size) + { + /// Sends `data` via reference, so IntegerSet can modify it. + IntegerSet.insert(data, v); + /// We can access members of the struct - but we should take care not to mess with them. + return data.size; + } + /// Computes the sum of all stored data. + function sum() returns (uint s) + { + for (var i = IntegerSet.iterate_start(data); IntegerSet.iterate_valid(data, i); i = IntegerSet.iterate_advance(data, i)) + s += IntegerSet.iterate_get(data, i); + } +} + +// This broke it at one point (namely the modifiers). +contract DualIndex { + mapping(uint => mapping(uint => uint)) data; + address public admin; + + modifier restricted { if (msg.sender == admin) _; } + + function DualIndex() { + admin = msg.sender; + } + + function set(uint key1, uint key2, uint value) restricted { + uint[2][4] memory defaults; // "memory" broke things at one time. + data[key1][key2] = value; + } + + function transfer_ownership(address _admin) restricted { + admin = _admin; + } + + function lookup(uint key1, uint key2) returns(uint) { + return data[key1][key2]; + } +} + +contract A { + +} + +contract B { + +} + +contract C is A, B { + +} + +contract TestPrivate +{ + uint private value; +} + +contract TestInternal +{ + uint internal value; +} + +contract FromSolparse is A, B, TestPrivate, TestInternal { + function() { + uint a = 6 ** 9; + var (x) = 100; + uint y = 2 days; + } +} + +contract CommentedOutFunction { + // FYI: This empty function, as well as the commented + // out function below (bad code) is important to this test. + function() { + + } + + // function something() + // uint x = 10; + // } +} + +library VarHasBrackets { + string constant specialRight = "}"; + //string storage specialLeft = "{"; +} + +library UsingExampleLibrary { + function sum(uint[] storage self) returns (uint s) { + for (uint i = 0; i < self.length; i++) + s += self[i]; + } +} + +contract UsingExampleContract { + using UsingExampleLibrary for uint[]; +} + +contract NewStuff { + uint[] b; + + function someFunction() payable { + string storage a = hex"ab1248fe"; + b[2+2]; + } +} + +// modifier with expression +contract MyContract { + function fun() mymodifier(foo.bar()) {} +} + +library GetCode { + function at(address _addr) returns (bytes o_code) { + assembly { + // retrieve the size of the code, this needs assembly + let size := extcodesize(_addr) + // allocate output byte array - this could also be done without assembly + // by using o_code = new bytes(size) + o_code := mload(0x40) + // new "memory end" including padding + mstore(0x40, add(o_code, and(add(add(size, 0x20), 0x1f), not(0x1f)))) + // store length in memory + mstore(o_code, size) + // actually retrieve the code, this needs assembly + extcodecopy(_addr, add(o_code, 0x20), 0, size) + } + } +} + +contract assemblyLocalBinding { + function test(){ + assembly { + let v := 1 + let x := 0x00 + let y := x + let z := "hello" + } + } +} + +contract assemblyReturn { + uint a = 10; + + function get() constant returns(uint) { + assembly { + mstore(0x40, sload(0)) + byte(0) + address(0) + return(0x40,32) + } + } +} + +contract usesConst { + uint const = 0; +} + +contract memoryArrays { + uint seven = 7; + + function returnNumber(uint number) returns (uint){ + return number; + } + + function alloc() { + uint[] memory a = new uint[](7); + uint[] memory b = new uint[](returnNumber(seven)); + } +} + +contract DeclarativeExpressions { + uint a; + uint b = 7; + uint b2=0; + uint public c; + uint constant public d; + uint public constant e; + uint private constant f = 7; + struct S { uint q;} + + function ham(S storage s1, uint[] storage arr) internal { + uint x; + uint y = 7; + S storage s2 = s1; + uint[] memory stor; + uint[] storage stor2 = arr; + } +} + +contract VariableDeclarationTuple { + function getMyTuple() returns (bool, bool){ + return (true, false); + } + + function ham (){ + var (x, y) = (10, 20); + var (a, b) = getMyTuple(); + var (,c) = (10, 20); + var (d,,) = (10, 20, 30); + var (,e,,f,) = (10, 20, 30, 40, 50); + + var ( + num1, num2, + num3, ,num5 + ) = (10, 20, 30, 40, 50); + } +} + +contract TypeIndexSpacing { + uint [ 7 ] x; + uint [] y; +} + +contract Ballot { + + struct Voter { + uint weight; + bool voted; + } + + function abstain() returns (bool) { + return false; + } + + function foobar() payable owner (myPrice) returns (uint[], address myAdd, string[] names) {} + function foobar() payable owner (myPrice) returns (uint[], address myAdd, string[] names); + + Voter you = Voter(1, true); + + Voter me = Voter({ + weight: 2, + voted: abstain() + }); + + Voter airbnb = Voter({ + weight: 2, + voted: true, + }); +} + +contract multilineReturn { + function a() returns (uint x) { + return + 5; + } +} diff --git a/packages/sol-tracing-utils/test/fixtures/contracts/SimpleStorage.sol b/packages/sol-tracing-utils/test/fixtures/contracts/SimpleStorage.sol new file mode 100644 index 000000000..e4b4ac246 --- /dev/null +++ b/packages/sol-tracing-utils/test/fixtures/contracts/SimpleStorage.sol @@ -0,0 +1,11 @@ +pragma solidity ^0.4.21; + +contract SimpleStorage { + uint public storedData; + function set(uint x) { + storedData = x; + } + function get() constant returns (uint retVal) { + return storedData; + } +} diff --git a/packages/sol-tracing-utils/test/fixtures/contracts/Simplest.sol b/packages/sol-tracing-utils/test/fixtures/contracts/Simplest.sol new file mode 100644 index 000000000..d71016e07 --- /dev/null +++ b/packages/sol-tracing-utils/test/fixtures/contracts/Simplest.sol @@ -0,0 +1,2 @@ +contract Simplest { +} diff --git a/packages/sol-tracing-utils/test/fixtures/contracts/SolcovIgnore.sol b/packages/sol-tracing-utils/test/fixtures/contracts/SolcovIgnore.sol new file mode 100644 index 000000000..a7977ffb4 --- /dev/null +++ b/packages/sol-tracing-utils/test/fixtures/contracts/SolcovIgnore.sol @@ -0,0 +1,22 @@ +pragma solidity ^0.4.21; + +contract SolcovIgnore { + uint public storedData; + + function set(uint x) public { + /* solcov ignore next */ + storedData = x; + } + + /* solcov ignore next */ + function get() constant public returns (uint retVal) { + return storedData; + } +} + +/* solcov ignore next */ +contract Ignore { + function ignored() public returns (bool) { + return false; + } +} diff --git a/packages/sol-tracing-utils/test/instructions_test.ts b/packages/sol-tracing-utils/test/instructions_test.ts new file mode 100644 index 000000000..058053cf9 --- /dev/null +++ b/packages/sol-tracing-utils/test/instructions_test.ts @@ -0,0 +1,19 @@ +import * as chai from 'chai'; +import 'mocha'; + +import { constants } from '../src/constants'; +import { getPcToInstructionIndexMapping } from '../src/instructions'; + +const expect = chai.expect; + +describe('instructions', () => { + describe('#getPcToInstructionIndexMapping', () => { + it('correctly maps pcs to instruction indexed', () => { + // tslint:disable-next-line:custom-no-magic-numbers + const bytecode = new Uint8Array([constants.PUSH1, 42, constants.PUSH2, 1, 2, constants.TIMESTAMP]); + const pcToInstruction = getPcToInstructionIndexMapping(bytecode); + const expectedPcToInstruction = { '0': 0, '2': 1, '5': 2 }; + expect(pcToInstruction).to.be.deep.equal(expectedPcToInstruction); + }); + }); +}); diff --git a/packages/sol-tracing-utils/test/sol_compiler_artifact_adapter_test.ts b/packages/sol-tracing-utils/test/sol_compiler_artifact_adapter_test.ts new file mode 100644 index 000000000..9c58d2cef --- /dev/null +++ b/packages/sol-tracing-utils/test/sol_compiler_artifact_adapter_test.ts @@ -0,0 +1,29 @@ +import * as chai from 'chai'; +import * as _ from 'lodash'; +import 'mocha'; +import * as path from 'path'; + +import { SolCompilerArtifactAdapter } from '../src/artifact_adapters/sol_compiler_artifact_adapter'; + +const expect = chai.expect; + +describe('SolCompilerArtifactAdapter', () => { + describe('#collectContractsData', () => { + it('correctly collects contracts data', async () => { + const artifactsPath = path.resolve(__dirname, 'fixtures/artifacts'); + const sourcesPath = path.resolve(__dirname, 'fixtures/contracts'); + const zeroExArtifactsAdapter = new SolCompilerArtifactAdapter(artifactsPath, sourcesPath); + const contractsData = await zeroExArtifactsAdapter.collectContractsDataAsync(); + _.forEach(contractsData, contractData => { + expect(contractData).to.have.keys([ + 'sourceCodes', + 'sources', + 'sourceMap', + 'sourceMapRuntime', + 'bytecode', + 'runtimeBytecode', + ]); + }); + }); + }); +}); diff --git a/packages/sol-tracing-utils/test/source_maps_test.ts b/packages/sol-tracing-utils/test/source_maps_test.ts new file mode 100644 index 000000000..5820bedd7 --- /dev/null +++ b/packages/sol-tracing-utils/test/source_maps_test.ts @@ -0,0 +1,71 @@ +import * as chai from 'chai'; +import * as fs from 'fs'; +import * as _ from 'lodash'; +import 'mocha'; +import * as path from 'path'; + +import { getLocationByOffset, parseSourceMap } from '../src/source_maps'; + +const expect = chai.expect; + +const simplestContractBaseName = 'Simplest.sol'; +const simplestContractFileName = path.resolve(__dirname, 'fixtures/contracts', simplestContractBaseName); +const simplestContract = fs.readFileSync(simplestContractFileName).toString(); + +describe('source maps', () => { + describe('#getLocationByOffset', () => { + it('correctly computes location by offset', () => { + const locationByOffset = getLocationByOffset(simplestContract); + const expectedLocationByOffset = { + '0': { line: 1, column: 0 }, + '1': { line: 1, column: 1 }, + '2': { line: 1, column: 2 }, + '3': { line: 1, column: 3 }, + '4': { line: 1, column: 4 }, + '5': { line: 1, column: 5 }, + '6': { line: 1, column: 6 }, + '7': { line: 1, column: 7 }, + '8': { line: 1, column: 8 }, + '9': { line: 1, column: 9 }, + '10': { line: 1, column: 10 }, + '11': { line: 1, column: 11 }, + '12': { line: 1, column: 12 }, + '13': { line: 1, column: 13 }, + '14': { line: 1, column: 14 }, + '15': { line: 1, column: 15 }, + '16': { line: 1, column: 16 }, + '17': { line: 1, column: 17 }, + '18': { line: 1, column: 18 }, + '19': { line: 1, column: 19 }, + '20': { line: 2, column: 0 }, + '21': { line: 2, column: 1 }, + '22': { line: 3, column: 0 }, + }; + expect(locationByOffset).to.be.deep.equal(expectedLocationByOffset); + }); + }); + describe('#parseSourceMap', () => { + it('correctly parses the source map', () => { + // This is the source map and bytecode for an empty contract like Example.sol + const srcMap = '0:21:0:-;;;;;;;;;;;;;;;;;'; + const bytecodeHex = + '60606040523415600e57600080fd5b603580601b6000396000f3006060604052600080fd00a165627a7a72305820377cdef690e46589f40efeef14d8ef73504af059fb3fd46f1da3cd2fc52ef7890029'; + const sources = [simplestContractBaseName]; + const pcToSourceRange = parseSourceMap([simplestContract], srcMap, bytecodeHex, sources); + const expectedSourceRange = { + location: { + start: { line: 1, column: 0 }, + end: { line: 2, column: 1 }, + }, + fileName: simplestContractBaseName, + }; + _.forEach(pcToSourceRange, sourceRange => { + // Solidity source maps are too short and we map some instructions to undefined + // Source: https://github.com/ethereum/solidity/issues/3741 + if (!_.isUndefined(sourceRange)) { + expect(sourceRange).to.be.deep.equal(expectedSourceRange); + } + }); + }); + }); +}); diff --git a/packages/sol-tracing-utils/test/trace_test.ts b/packages/sol-tracing-utils/test/trace_test.ts new file mode 100644 index 000000000..7a034362c --- /dev/null +++ b/packages/sol-tracing-utils/test/trace_test.ts @@ -0,0 +1,55 @@ +import * as chai from 'chai'; +import { OpCode, StructLog } from 'ethereum-types'; +import * as _ from 'lodash'; +import 'mocha'; + +import { getTracesByContractAddress } from '../src/trace'; + +const expect = chai.expect; + +const DEFAULT_STRUCT_LOG: StructLog = { + depth: 0, + error: '', + gas: 0, + gasCost: 0, + memory: [], + op: OpCode.Invalid, + pc: 0, + stack: [], + storage: {}, +}; + +function addDefaultStructLogFields(compactStructLog: Partial & { op: OpCode; depth: number }): StructLog { + return { ...DEFAULT_STRUCT_LOG, ...compactStructLog }; +} + +describe('Trace', () => { + describe('#getTracesByContractAddress', () => { + it('correctly splits trace by contract address', () => { + const delegateCallAddress = '0x0000000000000000000000000000000000000002'; + const trace = [ + { + op: OpCode.DelegateCall, + stack: [delegateCallAddress, '0x'], + depth: 0, + }, + { + op: OpCode.Return, + depth: 1, + }, + { + op: OpCode.Return, + depth: 0, + }, + ]; + const fullTrace = _.map(trace, compactStructLog => addDefaultStructLogFields(compactStructLog)); + const startAddress = '0x0000000000000000000000000000000000000001'; + const traceByContractAddress = getTracesByContractAddress(fullTrace, startAddress); + const expectedTraceByContractAddress = { + [startAddress]: [fullTrace[0], fullTrace[2]], + [delegateCallAddress]: [fullTrace[1]], + }; + expect(traceByContractAddress).to.be.deep.equal(expectedTraceByContractAddress); + }); + }); +}); diff --git a/packages/sol-tracing-utils/test/utils_test.ts b/packages/sol-tracing-utils/test/utils_test.ts new file mode 100644 index 000000000..6fc8fcfe1 --- /dev/null +++ b/packages/sol-tracing-utils/test/utils_test.ts @@ -0,0 +1,53 @@ +import * as chai from 'chai'; +import * as dirtyChai from 'dirty-chai'; +import 'mocha'; + +import { utils } from '../src/utils'; + +chai.use(dirtyChai); +const expect = chai.expect; + +describe('utils', () => { + describe('#compareLineColumn', () => { + it('correctly compares LineColumns', () => { + expect(utils.compareLineColumn({ line: 1, column: 3 }, { line: 1, column: 4 })).to.be.lessThan(0); + expect(utils.compareLineColumn({ line: 1, column: 4 }, { line: 1, column: 3 })).to.be.greaterThan(0); + expect(utils.compareLineColumn({ line: 1, column: 3 }, { line: 1, column: 3 })).to.be.equal(0); + expect(utils.compareLineColumn({ line: 0, column: 2 }, { line: 1, column: 0 })).to.be.lessThan(0); + expect(utils.compareLineColumn({ line: 1, column: 0 }, { line: 0, column: 2 })).to.be.greaterThan(0); + }); + }); + + describe('#isRangeInside', () => { + it('returns true if inside', () => { + expect( + utils.isRangeInside( + { start: { line: 1, column: 3 }, end: { line: 1, column: 4 } }, + { start: { line: 1, column: 2 }, end: { line: 1, column: 5 } }, + ), + ).to.be.true(); + }); + it('returns true if the same', () => { + expect( + utils.isRangeInside( + { start: { line: 1, column: 3 }, end: { line: 1, column: 4 } }, + { start: { line: 1, column: 3 }, end: { line: 1, column: 4 } }, + ), + ).to.be.true(); + }); + it('returns false if not inside', () => { + expect( + utils.isRangeInside( + { start: { line: 1, column: 3 }, end: { line: 1, column: 4 } }, + { start: { line: 1, column: 4 }, end: { line: 1, column: 4 } }, + ), + ).to.be.false(); + expect( + utils.isRangeInside( + { start: { line: 1, column: 3 }, end: { line: 1, column: 4 } }, + { start: { line: 1, column: 4 }, end: { line: 1, column: 5 } }, + ), + ).to.be.false(); + }); + }); +}); diff --git a/packages/sol-tracing-utils/tsconfig.json b/packages/sol-tracing-utils/tsconfig.json new file mode 100644 index 000000000..2ee711adc --- /dev/null +++ b/packages/sol-tracing-utils/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig", + "compilerOptions": { + "outDir": "lib", + "rootDir": "." + }, + "include": ["./src/**/*", "./test/**/*"] +} diff --git a/packages/sol-tracing-utils/tslint.json b/packages/sol-tracing-utils/tslint.json new file mode 100644 index 000000000..dd9053357 --- /dev/null +++ b/packages/sol-tracing-utils/tslint.json @@ -0,0 +1,3 @@ +{ + "extends": ["@0x/tslint-config"] +} -- cgit v1.2.3 From a8d9263062e586b90ee4c303d3d3aca72e428edc Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Thu, 10 Jan 2019 11:42:50 +0100 Subject: Add .gitkeep --- packages/sol-tracing-utils/coverage/.gitkeep | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 packages/sol-tracing-utils/coverage/.gitkeep (limited to 'packages') diff --git a/packages/sol-tracing-utils/coverage/.gitkeep b/packages/sol-tracing-utils/coverage/.gitkeep new file mode 100644 index 000000000..e69de29bb -- cgit v1.2.3 From c317a69e7e599e79e6b2e841211cc9ceaffa3c6f Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Thu, 10 Jan 2019 13:33:56 +0100 Subject: Add missing dependencies --- packages/metacoin/package.json | 3 +++ 1 file changed, 3 insertions(+) (limited to 'packages') diff --git a/packages/metacoin/package.json b/packages/metacoin/package.json index 0081e8be1..6812ff823 100644 --- a/packages/metacoin/package.json +++ b/packages/metacoin/package.json @@ -32,6 +32,9 @@ "@0x/abi-gen": "^1.0.20", "@0x/abi-gen-templates": "^1.0.1", "@0x/base-contract": "^3.0.11", + "@0x/sol-coverage": "^1.0.0", + "@0x/sol-profiler": "^1.0.0", + "@0x/sol-trace": "^1.0.0", "@0x/subproviders": "^2.1.9", "@0x/tslint-config": "^2.0.0", "@0x/types": "^1.5.0", -- cgit v1.2.3 From b8e3829fdbd1f516686618562172cb45fbb63bde Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Thu, 10 Jan 2019 14:05:29 +0100 Subject: Fix prettier --- packages/sol-coverage/CHANGELOG.json | 3 +-- packages/sol-profiler/CHANGELOG.json | 3 +-- packages/sol-trace/CHANGELOG.json | 3 +-- 3 files changed, 3 insertions(+), 6 deletions(-) (limited to 'packages') diff --git a/packages/sol-coverage/CHANGELOG.json b/packages/sol-coverage/CHANGELOG.json index c650a4a4b..223400eae 100644 --- a/packages/sol-coverage/CHANGELOG.json +++ b/packages/sol-coverage/CHANGELOG.json @@ -3,8 +3,7 @@ "version": "1.0.0", "changes": [ { - "note": - "Initial release as a separate package. For historical entries see @0x/sol-tracing-utils", + "note": "Initial release as a separate package. For historical entries see @0x/sol-tracing-utils", "pr": 1492 } ] diff --git a/packages/sol-profiler/CHANGELOG.json b/packages/sol-profiler/CHANGELOG.json index c650a4a4b..223400eae 100644 --- a/packages/sol-profiler/CHANGELOG.json +++ b/packages/sol-profiler/CHANGELOG.json @@ -3,8 +3,7 @@ "version": "1.0.0", "changes": [ { - "note": - "Initial release as a separate package. For historical entries see @0x/sol-tracing-utils", + "note": "Initial release as a separate package. For historical entries see @0x/sol-tracing-utils", "pr": 1492 } ] diff --git a/packages/sol-trace/CHANGELOG.json b/packages/sol-trace/CHANGELOG.json index c650a4a4b..223400eae 100644 --- a/packages/sol-trace/CHANGELOG.json +++ b/packages/sol-trace/CHANGELOG.json @@ -3,8 +3,7 @@ "version": "1.0.0", "changes": [ { - "note": - "Initial release as a separate package. For historical entries see @0x/sol-tracing-utils", + "note": "Initial release as a separate package. For historical entries see @0x/sol-tracing-utils", "pr": 1492 } ] -- cgit v1.2.3 From ffd14ab2f2b625574bdd1e8ce96189ef18782f3b Mon Sep 17 00:00:00 2001 From: fragosti Date: Thu, 10 Jan 2019 09:47:46 -0800 Subject: feature: implement logging and friendly wallet name for Opera --- packages/instant/src/constants.ts | 1 + packages/instant/src/types.ts | 1 + packages/instant/src/util/env.ts | 9 ++++++--- 3 files changed, 8 insertions(+), 3 deletions(-) (limited to 'packages') diff --git a/packages/instant/src/constants.ts b/packages/instant/src/constants.ts index 67558c84a..bfd9e9098 100644 --- a/packages/instant/src/constants.ts +++ b/packages/instant/src/constants.ts @@ -74,5 +74,6 @@ export const PROVIDER_TYPE_TO_NAME: { [key in ProviderType]: string } = { [ProviderType.CoinbaseWallet]: 'Coinbase Wallet', [ProviderType.Parity]: 'Parity', [ProviderType.TrustWallet]: 'Trust Wallet', + [ProviderType.Opera]: 'Opera Wallet', [ProviderType.Fallback]: 'Fallback', }; diff --git a/packages/instant/src/types.ts b/packages/instant/src/types.ts index ae672c919..f07a407da 100644 --- a/packages/instant/src/types.ts +++ b/packages/instant/src/types.ts @@ -183,6 +183,7 @@ export enum ProviderType { CoinbaseWallet = 'COINBASE_WALLET', Cipher = 'CIPHER', TrustWallet = 'TRUST_WALLET', + Opera = 'OPERA', Fallback = 'FALLBACK', } diff --git a/packages/instant/src/util/env.ts b/packages/instant/src/util/env.ts index aedf4f5d6..7d4f836ff 100644 --- a/packages/instant/src/util/env.ts +++ b/packages/instant/src/util/env.ts @@ -42,18 +42,21 @@ export const envUtil = { } }, getProviderType(provider: Provider): ProviderType | undefined { + const anyProvider = provider as any; if (provider.constructor.name === 'EthereumProvider') { return ProviderType.Mist; - } else if ((provider as any).isTrust) { + } else if (anyProvider.isTrust) { return ProviderType.TrustWallet; - } else if ((provider as any).isParity) { + } else if (anyProvider.isParity) { return ProviderType.Parity; - } else if ((provider as any).isMetaMask) { + } else if (anyProvider.isMetaMask) { return ProviderType.MetaMask; } else if (!_.isUndefined(_.get(window, 'SOFA'))) { return ProviderType.CoinbaseWallet; } else if (!_.isUndefined(_.get(window, '__CIPHER__'))) { return ProviderType.Cipher; + } else if (envUtil.getBrowser() === Browser.Opera && !anyProvider.isMetaMask) { + return ProviderType.Opera; } return; }, -- cgit v1.2.3 From 53fc860d6190c19f13c451eb89dd5c0c1cdcd1f4 Mon Sep 17 00:00:00 2001 From: Alex Browne Date: Thu, 10 Jan 2019 13:04:34 -0800 Subject: Add new deployment related instructions to pipeline README (#1499) * Add new deployment related instructions to pipeline README * Apply prettier * Respond to PR feedback --- packages/pipeline/README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) (limited to 'packages') diff --git a/packages/pipeline/README.md b/packages/pipeline/README.md index 794488cac..40a64cdd1 100644 --- a/packages/pipeline/README.md +++ b/packages/pipeline/README.md @@ -147,6 +147,20 @@ set the`ZEROEX_DATA_PIPELINE_DB_URL` environment variable to a valid starting from that block number. 7. Run the migrations and then run your new script locally and verify it works as expected. +8. After all tests pass and you can run the script locally, open a new PR to + the monorepo. Don't merge this yet! +9. If you added any new scripts or dependencies between scripts, you will need + to make changes to https://github.com/0xProject/0x-pipeline-orchestration + and make a separate PR there. Don't merge this yet! +10. After your PR passes code review, ask @feuGeneA or @xianny to deploy your + changes to the QA environment. Check the [QA Airflow dashboard](http://airflow-qa.0x.org:8080) + to make sure everything works correctly in the QA environment. +11. Merge your PR to 0x-monorepo (and + https://github.com/0xProject/0x-pipeline-orchestration if needed). Then ask + @feuGeneA or @xianny to deploy to production. +12. Monitor the [production Airflow dashboard](http://airflow.0x.org:8080) to + make sure everything still works. +13. Celebrate! :tada: #### Additional guidelines and tips: @@ -164,3 +178,6 @@ set the`ZEROEX_DATA_PIPELINE_DB_URL` environment variable to a valid floating point numbers. * [TypeORM documentation](http://typeorm.io/#/) is pretty robust and can be a helpful resource. +* Scripts/parsers should perform minimum data transformation/normalization. + The idea here is to have a raw data feed that will be cleaned up and + synthesized in a separate step. -- cgit v1.2.3 From a5b7a351609a5e6689bb97990216153f64302462 Mon Sep 17 00:00:00 2001 From: Xianny <8582774+xianny@users.noreply.github.com> Date: Thu, 10 Jan 2019 13:07:52 -0800 Subject: upgrade to ddex api v3 (#1507) --- .../migrations/1547153875669-UpdateDDexAPIToV3.ts | 21 +++++++++++++++++++++ packages/pipeline/src/data_sources/ddex/index.ts | 3 +-- packages/pipeline/src/parsers/ddex_orders/index.ts | 10 ++++------ .../pipeline/test/parsers/ddex_orders/index_test.ts | 13 +++++-------- 4 files changed, 31 insertions(+), 16 deletions(-) create mode 100644 packages/pipeline/migrations/1547153875669-UpdateDDexAPIToV3.ts (limited to 'packages') diff --git a/packages/pipeline/migrations/1547153875669-UpdateDDexAPIToV3.ts b/packages/pipeline/migrations/1547153875669-UpdateDDexAPIToV3.ts new file mode 100644 index 000000000..957af4941 --- /dev/null +++ b/packages/pipeline/migrations/1547153875669-UpdateDDexAPIToV3.ts @@ -0,0 +1,21 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class UpdateDDexAPIToV31547153875669 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + UPDATE raw.token_orderbook_snapshots + SET quote_asset_symbol='WETH' + WHERE quote_asset_symbol='ETH' AND + source='ddex'; + `); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + UPDATE raw.token_orderbook_snapshots + SET quote_asset_symbol='ETH' + WHERE quote_asset_symbol='WETH' AND + source='ddex'; + `); + } +} diff --git a/packages/pipeline/src/data_sources/ddex/index.ts b/packages/pipeline/src/data_sources/ddex/index.ts index 2bbd8c29b..7ef92b90f 100644 --- a/packages/pipeline/src/data_sources/ddex/index.ts +++ b/packages/pipeline/src/data_sources/ddex/index.ts @@ -1,6 +1,6 @@ import { fetchAsync, logUtils } from '@0x/utils'; -const DDEX_BASE_URL = 'https://api.ddex.io/v2'; +const DDEX_BASE_URL = 'https://api.ddex.io/v3'; const ACTIVE_MARKETS_URL = `${DDEX_BASE_URL}/markets`; const NO_AGGREGATION_LEVEL = 3; // See https://docs.ddex.io/#get-orderbook const ORDERBOOK_ENDPOINT = `/orderbook?level=${NO_AGGREGATION_LEVEL}`; @@ -23,7 +23,6 @@ export interface DdexMarket { baseTokenDecimals: number; baseTokenAddress: string; minOrderSize: string; - maxOrderSize: string; pricePrecision: number; priceDecimals: number; amountDecimals: number; diff --git a/packages/pipeline/src/parsers/ddex_orders/index.ts b/packages/pipeline/src/parsers/ddex_orders/index.ts index eeb9c9d5b..562f894ab 100644 --- a/packages/pipeline/src/parsers/ddex_orders/index.ts +++ b/packages/pipeline/src/parsers/ddex_orders/index.ts @@ -58,14 +58,12 @@ export function parseDdexOrder( tokenOrder.orderType = orderType; tokenOrder.price = price; - // ddex currently confuses quote and base assets. - // We switch them here to maintain our internal consistency. - tokenOrder.baseAssetSymbol = ddexMarket.quoteToken; - tokenOrder.baseAssetAddress = ddexMarket.quoteTokenAddress; + tokenOrder.baseAssetSymbol = ddexMarket.baseToken; + tokenOrder.baseAssetAddress = ddexMarket.baseTokenAddress; tokenOrder.baseVolume = amount; - tokenOrder.quoteAssetSymbol = ddexMarket.baseToken; - tokenOrder.quoteAssetAddress = ddexMarket.baseTokenAddress; + tokenOrder.quoteAssetSymbol = ddexMarket.quoteToken; + tokenOrder.quoteAssetAddress = ddexMarket.quoteTokenAddress; tokenOrder.quoteVolume = price.times(amount); return tokenOrder; } diff --git a/packages/pipeline/test/parsers/ddex_orders/index_test.ts b/packages/pipeline/test/parsers/ddex_orders/index_test.ts index f30e86b02..d6f69e090 100644 --- a/packages/pipeline/test/parsers/ddex_orders/index_test.ts +++ b/packages/pipeline/test/parsers/ddex_orders/index_test.ts @@ -25,7 +25,6 @@ describe('ddex_orders', () => { baseTokenDecimals: 2, baseTokenAddress: '0xb45df06e38540a675fdb5b598abf2c0dbe9d6b81', minOrderSize: '0.1', - maxOrderSize: '1000', pricePrecision: 1, priceDecimals: 1, amountDecimals: 0, @@ -39,14 +38,12 @@ describe('ddex_orders', () => { expected.observedTimestamp = observedTimestamp; expected.orderType = OrderType.Bid; expected.price = new BigNumber(0.5); - // ddex currently confuses base and quote assets. - // Switch them to maintain our internal consistency. - expected.baseAssetSymbol = 'ABC'; - expected.baseAssetAddress = '0x0000000000000000000000000000000000000000'; - expected.baseVolume = new BigNumber(10); - expected.quoteAssetSymbol = 'DEF'; - expected.quoteAssetAddress = '0xb45df06e38540a675fdb5b598abf2c0dbe9d6b81'; + expected.quoteAssetSymbol = 'ABC'; + expected.quoteAssetAddress = '0x0000000000000000000000000000000000000000'; expected.quoteVolume = new BigNumber(5); + expected.baseAssetSymbol = 'DEF'; + expected.baseAssetAddress = '0xb45df06e38540a675fdb5b598abf2c0dbe9d6b81'; + expected.baseVolume = new BigNumber(10); const actual = parseDdexOrder(ddexMarket, observedTimestamp, orderType, source, ddexOrder); expect(actual).deep.equal(expected); -- cgit v1.2.3