From 82d1412d456ee28d80597208a241a2b62f561837 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Mon, 11 Jun 2018 09:19:52 +0200 Subject: Simplified handling of source < 32 edge case --- .../src/contracts/current/utils/LibMem/LibMem.sol | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) (limited to 'packages') diff --git a/packages/contracts/src/contracts/current/utils/LibMem/LibMem.sol b/packages/contracts/src/contracts/current/utils/LibMem/LibMem.sol index 6afb9973a..97fb5fb0f 100644 --- a/packages/contracts/src/contracts/current/utils/LibMem/LibMem.sol +++ b/packages/contracts/src/contracts/current/utils/LibMem/LibMem.sol @@ -80,9 +80,6 @@ contract LibMem // if (source > dest) { assembly { - // Record the total number of full words to copy - let nWords := div(length, 32) - // We subtract 32 from `sEnd` and `dEnd` because it // is easier to compare with in the loop, and these // are also the addresses we need for copying the @@ -98,20 +95,19 @@ contract LibMem let last := mload(sEnd) // Copy whole words front to back - for {let i := 0} lt(i, nWords) {i := add(i, 1)} { + // Note: the first check is always true, + // this could have been a do-while loop. + for {} lt(source, sEnd) {} { mstore(dest, mload(source)) source := add(source, 32) dest := add(dest, 32) } - + // Write the last 32 bytes mstore(dEnd, last) } } else { assembly { - // Record the total number of full words to copy - let nWords := div(length, 32) - // We subtract 32 from `sEnd` and `dEnd` because those // are the starting points when copying a word at the end. length := sub(length, 32) @@ -125,12 +121,18 @@ contract LibMem let first := mload(source) // Copy whole words back to front - for {let i := 0} lt(i, nWords) {i := add(i, 1)} { + // We use a signed comparisson here to allow dEnd to become + // negative (happens when source and dest < 32). Valid + // addresses in local memory will never be larger than + // 2**255, so they can be safely re-interpreted as signed. + // Note: the first check is always true, + // this could have been a do-while loop. + for {} slt(dest, dEnd) {} { mstore(dEnd, mload(sEnd)) sEnd := sub(sEnd, 32) dEnd := sub(dEnd, 32) } - + // Write the first 32 bytes mstore(dest, first) } -- cgit v1.2.3 From 2c7d6a7711e44294e64858095971a2aebc6d1829 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Thu, 14 Jun 2018 10:54:54 +0200 Subject: Handle tokens that do not return bool --- .../current/protocol/AssetProxy/ERC20Proxy.sol | 30 +++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC20Proxy.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC20Proxy.sol index ddcd78e93..eeddb9707 100644 --- a/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC20Proxy.sol +++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC20Proxy.sol @@ -50,7 +50,35 @@ contract ERC20Proxy is address token = readAddress(assetData, 0); // Transfer tokens. - bool success = IERC20Token(token).transferFrom(from, to, amount); + // We do a raw call so we can check the success separate + // from the return data. + bool success = token.call(abi.encodeWithSelector( + IERC20Token(token).transferFrom.selector, + from, + to, + amount + )); + require( + success, + TRANSFER_FAILED + ); + + // Check return data. + // If there is no return data, we assume the token incorrectly + // does not return a bool. In this case we expect it to revert + // on failure, which was handled above. + // If the token does return data, we require that it is a single + // value that evaluates to true. + assembly { + if returndatasize { + success := 0 + if eq(returndatasize, 32) { + // First 64 bytes of memory are reserved scratch space + returndatacopy(0, 0, 32) + success := mload(0) + } + } + } require( success, TRANSFER_FAILED -- cgit v1.2.3 From 4a2e4d2b55c89c91cebf434570feee91ccc449fa Mon Sep 17 00:00:00 2001 From: "F. Eugene Aumson" Date: Wed, 13 Jun 2018 23:09:36 -0400 Subject: Document contract_templates --- packages/contract_templates/README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 packages/contract_templates/README.md (limited to 'packages') diff --git a/packages/contract_templates/README.md b/packages/contract_templates/README.md new file mode 100644 index 000000000..39aa2d2c0 --- /dev/null +++ b/packages/contract_templates/README.md @@ -0,0 +1,15 @@ +These templates are used with [abi-gen](https://github.com/0xProject/0x-monorepo/tree/development/packages/abi-gen). + +To successfully compile the generated TypeScript contract wrappers, you must: +* Install the packages on which the main contract template directly depends: `yarn add @0xproject/base-contract @0xproject/sol-compiler @0xproject/types @0xproject/utils @0xproject/web3-wrapper ethers lodash` +* Install the packages on which the main contract template *in*directly depends: `yarn add @types/lodash` +* Ensure that your TypeScript configuration includes the following: +``` +"compilerOptions": { + "lib": ["ES2015"], + "typeRoots": [ + "node_modules/@0xproject/typescript-typings/types", + "node_modules/@types" + ] +} +``` -- cgit v1.2.3 From 15a63c4bc5e05583d15910a562d524ca9b1e4b9d Mon Sep 17 00:00:00 2001 From: "F. Eugene Aumson" Date: Wed, 13 Jun 2018 23:09:59 -0400 Subject: workaround for TypeScript trailing comma bug before this change, TypeScript compilation of the generated contract wrapper was giving me the following errors: $ abi-gen --abis 'build/contracts/*.json' --out build/types --template contract_templates/contract.handlebars --partials 'contract_templates/partials/*.handlebars' Found 7 partial templates Found 1 ABI files Processing: Migrations... Created: build/types/migrations.ts $ tsc build/types/migrations.ts(81,23): error TS1013: A rest parameter or binding pattern may not have a trailing comma. build/types/migrations.ts(108,23): error TS1013: A rest parameter or binding pattern may not have a trailing comma. build/types/migrations.ts(130,23): error TS1013: A rest parameter or binding pattern may not have a trailing comma. build/types/migrations.ts(146,25): error TS1013: A rest parameter or binding pattern may not have a trailing comma. build/types/migrations.ts(173,25): error TS1013: A rest parameter or binding pattern may not have a trailing comma. build/types/migrations.ts(195,25): error TS1013: A rest parameter or binding pattern may not have a trailing comma. info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command. Here is the generated code around the first error: 74: public setCompleted = { 75: async sendTransactionAsync( 76: completed: BigNumber, 77: txData: Partial = {}, 78: ): Promise { 79: const self = this as any as MigrationsContract; 80: const inputAbi = self._lookupAbi('setCompleted(uint256)').inputs; 81: [completed, 82: ] = BaseContract._formatABIDataItemList(inputAbi, [completed, 83: ], BaseContract._bigNumberToString.bind(self)); All of the other errors are the same, a destructuring assignment with a single element but with a trailing comma. This is legal JavaScript but it is not allowed by the TypeScript compiler, apparently per the bug described at https://github.com/Microsoft/TypeScript/issues/24628 . While awaiting the 3.0 version of TypeScript, it's a simple enough change to have the template not append a trailing comma. --- packages/contract_templates/partials/params.handlebars | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/contract_templates/partials/params.handlebars b/packages/contract_templates/partials/params.handlebars index ac5d4ae85..2d9bb8ed9 100644 --- a/packages/contract_templates/partials/params.handlebars +++ b/packages/contract_templates/partials/params.handlebars @@ -1,3 +1,3 @@ {{#each inputs}} -{{name}}, +{{name}}{{#if @last}}{{else}},{{/if}} {{/each}} -- cgit v1.2.3 From f9e05d0cad9ab4624be2ce546e04aba109dec829 Mon Sep 17 00:00:00 2001 From: "F. Eugene Aumson" Date: Thu, 14 Jun 2018 09:38:18 -0400 Subject: remove mistaken comment It must have been left over from when an abi-gen output was modified to be the source of this template. --- packages/contract_templates/contract.handlebars | 4 ---- 1 file changed, 4 deletions(-) (limited to 'packages') diff --git a/packages/contract_templates/contract.handlebars b/packages/contract_templates/contract.handlebars index 431359109..997088c8f 100644 --- a/packages/contract_templates/contract.handlebars +++ b/packages/contract_templates/contract.handlebars @@ -1,7 +1,3 @@ -/** - * This file is auto-generated using abi-gen. Don't edit directly. - * Templates can be found at https://github.com/0xProject/0x-monorepo/tree/development/packages/contract_templates. - */ // tslint:disable:no-consecutive-blank-lines ordered-imports align trailing-comma whitespace // tslint:disable:no-unused-variable import { BaseContract } from '@0xproject/base-contract'; -- cgit v1.2.3 From 4811dfa66310edfb00cf2db9def4565770edb742 Mon Sep 17 00:00:00 2001 From: fragosti Date: Thu, 14 Jun 2018 13:56:36 -0700 Subject: Fix filling orders on Portal --- packages/contract-wrappers/src/contract_wrappers/contract_wrapper.ts | 3 ++- .../contract-wrappers/src/contract_wrappers/ether_token_wrapper.ts | 3 ++- packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts | 2 ++ .../contract-wrappers/src/contract_wrappers/token_registry_wrapper.ts | 3 ++- .../src/contract_wrappers/token_transfer_proxy_wrapper.ts | 2 ++ packages/contract-wrappers/src/contract_wrappers/token_wrapper.ts | 3 ++- packages/website/ts/blockchain.ts | 1 + 7 files changed, 13 insertions(+), 4 deletions(-) (limited to 'packages') diff --git a/packages/contract-wrappers/src/contract_wrappers/contract_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/contract_wrapper.ts index e646bae78..04f69bc3d 100644 --- a/packages/contract-wrappers/src/contract_wrappers/contract_wrapper.ts +++ b/packages/contract-wrappers/src/contract_wrappers/contract_wrapper.ts @@ -35,7 +35,8 @@ const CONTRACT_NAME_TO_NOT_FOUND_ERROR: { Exchange: ContractWrappersError.ExchangeContractDoesNotExist, }; -export class ContractWrapper { +export abstract class ContractWrapper { + public abstract abi: ContractAbi; protected _web3Wrapper: Web3Wrapper; protected _networkId: number; private _blockAndLogStreamerIfExists?: BlockAndLogStreamer; diff --git a/packages/contract-wrappers/src/contract_wrappers/ether_token_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/ether_token_wrapper.ts index 713d4d3f6..36b7a234a 100644 --- a/packages/contract-wrappers/src/contract_wrappers/ether_token_wrapper.ts +++ b/packages/contract-wrappers/src/contract_wrappers/ether_token_wrapper.ts @@ -1,5 +1,5 @@ import { schemas } from '@0xproject/json-schemas'; -import { LogWithDecodedArgs } from '@0xproject/types'; +import { ContractAbi, LogWithDecodedArgs } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; import { Web3Wrapper } from '@0xproject/web3-wrapper'; import * as _ from 'lodash'; @@ -17,6 +17,7 @@ import { TokenWrapper } from './token_wrapper'; * The caller can convert ETH into the equivalent number of wrapped ETH ERC20 tokens and back. */ export class EtherTokenWrapper extends ContractWrapper { + public abi: ContractAbi = artifacts.EtherToken.abi; private _etherTokenContractsByAddress: { [address: string]: EtherTokenContract; } = {}; diff --git a/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts index 430ce9c5e..2c517502a 100644 --- a/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts +++ b/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts @@ -2,6 +2,7 @@ import { schemas } from '@0xproject/json-schemas'; import { formatters, getOrderHashHex, OrderStateUtils } from '@0xproject/order-utils'; import { BlockParamLiteral, + ContractAbi DecodedLogArgs, ECSignature, ExchangeContractErrs, @@ -54,6 +55,7 @@ interface ExchangeContractErrCodesToMsgs { * events of the 0x Exchange smart contract. */ export class ExchangeWrapper extends ContractWrapper { + public abi: ContractAbi = artifacts.Exchange.abi; private _exchangeContractIfExists?: ExchangeContract; private _orderValidationUtilsIfExists?: OrderValidationUtils; private _tokenWrapper: TokenWrapper; diff --git a/packages/contract-wrappers/src/contract_wrappers/token_registry_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/token_registry_wrapper.ts index fb9396281..7b558ed69 100644 --- a/packages/contract-wrappers/src/contract_wrappers/token_registry_wrapper.ts +++ b/packages/contract-wrappers/src/contract_wrappers/token_registry_wrapper.ts @@ -1,4 +1,4 @@ -import { Token } from '@0xproject/types'; +import { ContractAbi, Token } from '@0xproject/types'; import { Web3Wrapper } from '@0xproject/web3-wrapper'; import * as _ from 'lodash'; @@ -14,6 +14,7 @@ import { TokenRegistryContract } from './generated/token_registry'; * This class includes all the functionality related to interacting with the 0x Token Registry smart contract. */ export class TokenRegistryWrapper extends ContractWrapper { + public abi: ContractAbi = artifacts.TokenRegistry.abi; private _tokenRegistryContractIfExists?: TokenRegistryContract; private _contractAddressIfExists?: string; private static _createTokenFromMetadata(metadata: TokenMetadata): Token | undefined { diff --git a/packages/contract-wrappers/src/contract_wrappers/token_transfer_proxy_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/token_transfer_proxy_wrapper.ts index 2fe3e6620..5194931d7 100644 --- a/packages/contract-wrappers/src/contract_wrappers/token_transfer_proxy_wrapper.ts +++ b/packages/contract-wrappers/src/contract_wrappers/token_transfer_proxy_wrapper.ts @@ -1,4 +1,5 @@ import { Web3Wrapper } from '@0xproject/web3-wrapper'; +import { ContractAbi } from '@0xproject/types'; import * as _ from 'lodash'; import { artifacts } from '../artifacts'; @@ -11,6 +12,7 @@ import { TokenTransferProxyContract } from './generated/token_transfer_proxy'; * This class includes the functionality related to interacting with the TokenTransferProxy contract. */ export class TokenTransferProxyWrapper extends ContractWrapper { + public abi: ContractAbi = artifacts.TokenTransferProxy.abi; private _tokenTransferProxyContractIfExists?: TokenTransferProxyContract; private _contractAddressIfExists?: string; constructor(web3Wrapper: Web3Wrapper, networkId: number, contractAddressIfExists?: string) { diff --git a/packages/contract-wrappers/src/contract_wrappers/token_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/token_wrapper.ts index 7c7907fd2..d9364715f 100644 --- a/packages/contract-wrappers/src/contract_wrappers/token_wrapper.ts +++ b/packages/contract-wrappers/src/contract_wrappers/token_wrapper.ts @@ -1,5 +1,5 @@ import { schemas } from '@0xproject/json-schemas'; -import { LogWithDecodedArgs } from '@0xproject/types'; +import { ContractAbi, LogWithDecodedArgs } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; import { Web3Wrapper } from '@0xproject/web3-wrapper'; import * as _ from 'lodash'; @@ -26,6 +26,7 @@ import { TokenTransferProxyWrapper } from './token_transfer_proxy_wrapper'; * to the 0x Proxy smart contract. */ export class TokenWrapper extends ContractWrapper { + public abi: ContractAbi = artifacts.Token.abi; public UNLIMITED_ALLOWANCE_IN_BASE_UNITS = constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS; private _tokenContractsByAddress: { [address: string]: TokenContract }; private _tokenTransferProxyWrapper: TokenTransferProxyWrapper; diff --git a/packages/website/ts/blockchain.ts b/packages/website/ts/blockchain.ts index c955e1033..3ebdd1dee 100644 --- a/packages/website/ts/blockchain.ts +++ b/packages/website/ts/blockchain.ts @@ -625,6 +625,7 @@ export class Blockchain { ); const provider = this._contractWrappers.getProvider(); const web3Wrapper = new Web3Wrapper(provider); + web3Wrapper.abiDecoder.addABI(this._contractWrappers.exchange.abi); const receipt = await web3Wrapper.awaitTransactionSuccessAsync(txHash); return receipt; } -- cgit v1.2.3 From 7ab921669bf52c1cb2d43350b2cccc8efe91bdbd Mon Sep 17 00:00:00 2001 From: Alex Browne Date: Thu, 14 Jun 2018 13:58:28 -0700 Subject: Introduce subprovider for printing revert stack traces --- packages/contracts/package.json | 1 + packages/contracts/src/utils/revert_trace.ts | 21 ++ packages/contracts/src/utils/web3_wrapper.ts | 51 ++--- packages/dev-utils/src/env.ts | 1 + packages/sol-cov/src/index.ts | 1 + packages/sol-cov/src/revert_trace.ts | 82 ++++++++ packages/sol-cov/src/revert_trace_subprovider.ts | 237 +++++++++++++++++++++++ packages/sol-cov/src/trace.ts | 41 ++-- packages/sol-cov/src/types.ts | 7 + packages/sol-cov/src/utils.ts | 24 +++ 10 files changed, 416 insertions(+), 50 deletions(-) create mode 100644 packages/contracts/src/utils/revert_trace.ts create mode 100644 packages/sol-cov/src/revert_trace.ts create mode 100644 packages/sol-cov/src/revert_trace_subprovider.ts (limited to 'packages') diff --git a/packages/contracts/package.json b/packages/contracts/package.json index d6ca3727b..ad7cbf476 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -19,6 +19,7 @@ "rebuild_and_test": "run-s build test", "test:coverage": "SOLIDITY_COVERAGE=true run-s build run_mocha coverage:report:text coverage:report:lcov", "test:profiler": "SOLIDITY_PROFILER=true run-s build run_mocha profiler:report:html", + "test:trace": "SOLIDITY_REVERT_TRACE=true run-s run_mocha", "run_mocha": "mocha --require source-map-support/register 'lib/test/**/*.js' --timeout 100000 --bail --exit", "compile": "sol-compiler", "clean": "shx rm -rf lib src/generated_contract_wrappers", diff --git a/packages/contracts/src/utils/revert_trace.ts b/packages/contracts/src/utils/revert_trace.ts new file mode 100644 index 000000000..0bf8384bc --- /dev/null +++ b/packages/contracts/src/utils/revert_trace.ts @@ -0,0 +1,21 @@ +import { devConstants } from '@0xproject/dev-utils'; +import { RevertTraceSubprovider, SolCompilerArtifactAdapter } from '@0xproject/sol-cov'; +import * as _ from 'lodash'; + +let revertTraceSubprovider: RevertTraceSubprovider; + +export const revertTrace = { + getRevertTraceSubproviderSingleton(): RevertTraceSubprovider { + if (_.isUndefined(revertTraceSubprovider)) { + revertTraceSubprovider = revertTrace._getRevertTraceSubprovider(); + } + return revertTraceSubprovider; + }, + _getRevertTraceSubprovider(): RevertTraceSubprovider { + const defaultFromAddress = devConstants.TESTRPC_FIRST_ADDRESS; + const solCompilerArtifactAdapter = new SolCompilerArtifactAdapter(); + const isVerbose = true; + const subprovider = new RevertTraceSubprovider(solCompilerArtifactAdapter, defaultFromAddress, isVerbose); + return subprovider; + }, +}; diff --git a/packages/contracts/src/utils/web3_wrapper.ts b/packages/contracts/src/utils/web3_wrapper.ts index c475d96a9..b8e8ed8ce 100644 --- a/packages/contracts/src/utils/web3_wrapper.ts +++ b/packages/contracts/src/utils/web3_wrapper.ts @@ -5,6 +5,7 @@ import { Web3Wrapper } from '@0xproject/web3-wrapper'; import { coverage } from './coverage'; import { profiler } from './profiler'; +import { revertTrace } from './revert_trace'; enum ProviderType { Ganache = 'ganache', @@ -48,28 +49,34 @@ const providerConfigs = testProvider === ProviderType.Ganache ? ganacheConfigs : export const provider = web3Factory.getRpcProvider(providerConfigs); const isCoverageEnabled = env.parseBoolean(EnvVars.SolidityCoverage); const isProfilerEnabled = env.parseBoolean(EnvVars.SolidityProfiler); -if (isCoverageEnabled && isProfilerEnabled) { - throw new Error( - `Unfortunately for now you can't enable both coverage and profiler at the same time. They both use coverage.json file and there is no way to configure that.`, - ); -} -if (isCoverageEnabled) { - const coverageSubprovider = coverage.getCoverageSubproviderSingleton(); - prependSubprovider(provider, coverageSubprovider); -} -if (isProfilerEnabled) { - if (testProvider === ProviderType.Ganache) { - logUtils.warn( - "Gas costs in Ganache traces are incorrect and we don't recommend using it for profiling. Please switch to Geth", - ); - process.exit(1); - } - const profilerSubprovider = profiler.getProfilerSubproviderSingleton(); - logUtils.log( - "By default profilerSubprovider is stopped so that you don't get noise from setup code. Don't forget to start it before the code you want to profile and stop it afterwards", - ); - profilerSubprovider.stop(); - prependSubprovider(provider, profilerSubprovider); +const isRevertTraceEnabled = env.parseBoolean(EnvVars.SolidityRevertTrace); +// TODO(albrow): Include revertTrace checks in the warnings below. +// if (isCoverageEnabled && isProfilerEnabled) { +// throw new Error( +// `Unfortunately for now you can't enable both coverage and profiler at the same time. They both use coverage.json file and there is no way to configure that.`, +// ); +// } +// if (isCoverageEnabled) { +// const coverageSubprovider = coverage.getCoverageSubproviderSingleton(); +// prependSubprovider(provider, coverageSubprovider); +// } +// if (isProfilerEnabled) { +// if (testProvider === ProviderType.Ganache) { +// logUtils.warn( +// "Gas costs in Ganache traces are incorrect and we don't recommend using it for profiling. Please switch to Geth", +// ); +// process.exit(1); +// } +// const profilerSubprovider = profiler.getProfilerSubproviderSingleton(); +// logUtils.log( +// "By default profilerSubprovider is stopped so that you don't get noise from setup code. Don't forget to start it before the code you want to profile and stop it afterwards", +// ); +// profilerSubprovider.stop(); +// prependSubprovider(provider, profilerSubprovider); +// } +if (isRevertTraceEnabled) { + const revertTraceSubprovider = revertTrace.getRevertTraceSubproviderSingleton(); + prependSubprovider(provider, revertTraceSubprovider); } export const web3Wrapper = new Web3Wrapper(provider); diff --git a/packages/dev-utils/src/env.ts b/packages/dev-utils/src/env.ts index e74ef3d23..024162c2f 100644 --- a/packages/dev-utils/src/env.ts +++ b/packages/dev-utils/src/env.ts @@ -4,6 +4,7 @@ import * as process from 'process'; export enum EnvVars { SolidityCoverage = 'SOLIDITY_COVERAGE', SolidityProfiler = 'SOLIDITY_PROFILER', + SolidityRevertTrace = 'SOLIDITY_REVERT_TRACE', VerboseGanache = 'VERBOSE_GANACHE', } diff --git a/packages/sol-cov/src/index.ts b/packages/sol-cov/src/index.ts index 10f6d9597..003a27374 100644 --- a/packages/sol-cov/src/index.ts +++ b/packages/sol-cov/src/index.ts @@ -1,6 +1,7 @@ export { CoverageSubprovider } from './coverage_subprovider'; // 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 { SolCompilerArtifactAdapter } from './artifact_adapters/sol_compiler_artifact_adapter'; export { TruffleArtifactAdapter } from './artifact_adapters/truffle_artifact_adapter'; export { AbstractArtifactAdapter } from './artifact_adapters/abstract_artifact_adapter'; diff --git a/packages/sol-cov/src/revert_trace.ts b/packages/sol-cov/src/revert_trace.ts new file mode 100644 index 000000000..59edf5db4 --- /dev/null +++ b/packages/sol-cov/src/revert_trace.ts @@ -0,0 +1,82 @@ +import { logUtils } from '@0xproject/utils'; +import { OpCode, StructLog } from 'ethereum-types'; + +import * as _ from 'lodash'; + +import { EvmCallStack, EvmCallStackEntry } from './types'; +import { utils } from './utils'; + +export function getRevertTrace(structLogs: StructLog[], startAddress: string): EvmCallStack { + const evmCallStack: EvmCallStack = []; + let currentAddress = 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 !== evmCallStack.length) { + 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 + + // TODO(albrow): split out isCallLike and isEndOpcode + if (utils.isCallLike(structLog.op)) { + const evmCallStackEntry = _.last(evmCallStack) as EvmCallStackEntry; + const prevAddress = _.isUndefined(evmCallStackEntry) ? currentAddress : evmCallStackEntry.address; + const jumpAddressOffset = 1; + currentAddress = utils.getAddressFromStackEntry( + structLog.stack[structLog.stack.length - jumpAddressOffset - 1], + ); + + if (structLog === _.last(normalizedStructLogs)) { + throw new Error('Malformed trace. CALL-like opcode can not be the last one'); + } + // Sometimes calls don't change the execution context (current address). When we do a transfer to an + // externally owned account - it does the call and immediately returns because there is no fallback + // function. We manually check if the call depth had changed to handle that case. + const nextStructLog = normalizedStructLogs[i + 1]; + if (nextStructLog.depth !== structLog.depth) { + evmCallStack.push({ + address: prevAddress, + 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(); + } + 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: currentAddress, + 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 []; + } + } + 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 new file mode 100644 index 000000000..ea878058c --- /dev/null +++ b/packages/sol-cov/src/revert_trace_subprovider.ts @@ -0,0 +1,237 @@ +import { BlockchainLifecycle } from '@0xproject/dev-utils'; +import { Callback, ErrorCallback, NextCallback, Subprovider } from '@0xproject/subproviders'; +import { Web3Wrapper } from '@0xproject/web3-wrapper'; +import { CallData, JSONRPCRequestPayload, Provider, TxData } from 'ethereum-types'; +import { stripHexPrefix } from 'ethereumjs-util'; +import * as _ from 'lodash'; +import { getLogger, levels, Logger } from 'loglevel'; +import { Lock } from 'semaphore-async-await'; + +import { AbstractArtifactAdapter } from './artifact_adapters/abstract_artifact_adapter'; +import { constants } from './constants'; +import { getRevertTrace } from './revert_trace'; +import { parseSourceMap } from './source_maps'; +import { BlockParamLiteral, ContractData, EvmCallStack, SourceRange } from './types'; +import { utils } from './utils'; + +interface MaybeFakeTxData extends TxData { + isFakeTransaction?: boolean; +} + +const BLOCK_GAS_LIMIT = 6000000; + +/** + * 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 Subprovider { + // Lock is used to not accept normal transactions while doing call/snapshot magic because they'll be reverted later otherwise + private _lock = new Lock(); + private _defaultFromAddress: string; + private _web3Wrapper!: Web3Wrapper; + private _isEnabled = true; + private _artifactAdapter: AbstractArtifactAdapter; + private _contractsData!: ContractData[]; + private _logger: Logger; + + /** + * Instantiates a TraceCollectionSubprovider instance + * @param defaultFromAddress default from address to use when sending transactions + */ + constructor(artifactAdapter: AbstractArtifactAdapter, defaultFromAddress: string, isVerbose: boolean) { + super(); + this._artifactAdapter = artifactAdapter; + this._defaultFromAddress = defaultFromAddress; + this._logger = getLogger('sol-cov'); + this._logger.setLevel(isVerbose ? levels.TRACE : levels.ERROR); + } + /** + * 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': + const txData = payload.params[0]; + next(this._onTransactionSentAsync.bind(this, txData)); + return; + + case 'eth_call': + const callData = payload.params[0]; + next(this._onCallOrGasEstimateExecutedAsync.bind(this, callData)); + return; + + case 'eth_estimateGas': + 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. + */ + public setEngine(engine: Provider): void { + super.setEngine(engine); + this._web3Wrapper = new Web3Wrapper(engine); + } + 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 _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 _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: MaybeFakeTxData = { + gas: BLOCK_GAS_LIMIT, + 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 txHash = await this._web3Wrapper.sendTransactionAsync(fakeTxData); + await this._web3Wrapper.awaitTransactionMinedAsync(txHash, 0); + } catch (err) { + // Even if this transaction failed - we've already recorded it's trace. + _.noop(); + } + await blockchainLifecycle.revertAsync(); + this._lock.release(); + } + private async _printStackTraceAsync(evmCallStack: EvmCallStack): Promise { + const sourceRanges: SourceRange[] = []; + if (_.isUndefined(this._contractsData)) { + this._contractsData = await this._artifactAdapter.collectContractsDataAsync(); + } + for (const evmCallStackEntry of evmCallStack) { + const isContractCreation = evmCallStackEntry.address === constants.NEW_CONTRACT; + 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)) { + sourceRange = pcToSourceRange[pc]; + pc -= 1; + if (pc <= 0) { + this._logger.warn( + `could not find matching sourceRange for structLog: ${evmCallStackEntry.structLog}`, + ); + continue; + } + } + sourceRanges.push(sourceRange); + } + if (sourceRanges.length > 0) { + this._logger.error('\n\nStack trace:\n'); + _.forEach(sourceRanges, sourceRange => { + this._logger.error( + `${sourceRange.fileName}:${sourceRange.location.start.line}:${sourceRange.location.start.column}`, + ); + }); + this._logger.error('\n'); + } else { + this._logger.error('Could not determine stack trace'); + } + } +} diff --git a/packages/sol-cov/src/trace.ts b/packages/sol-cov/src/trace.ts index 45e45e9c5..b43fd19a2 100644 --- a/packages/sol-cov/src/trace.ts +++ b/packages/sol-cov/src/trace.ts @@ -1,17 +1,13 @@ -import { addressUtils, BigNumber, logUtils } from '@0xproject/utils'; +import { logUtils } from '@0xproject/utils'; import { OpCode, StructLog } from 'ethereum-types'; -import { addHexPrefix } from 'ethereumjs-util'; import * as _ from 'lodash'; +import { utils } from './utils'; + export interface TraceByContractAddress { [contractAddress: string]: StructLog[]; } -function getAddressFromStackEntry(stackEntry: string): string { - const hexBase = 16; - return addressUtils.padZeros(new BigNumber(addHexPrefix(stackEntry)).toString(hexBase)); -} - export function getTracesByContractAddress(structLogs: StructLog[], startAddress: string): TraceByContractAddress { const traceByContractAddress: TraceByContractAddress = {}; let currentTraceSegment = []; @@ -19,13 +15,10 @@ export function getTracesByContractAddress(structLogs: StructLog[], startAddress if (_.isEmpty(structLogs)) { return traceByContractAddress; } - if (structLogs[0].depth === 1) { - // Geth uses 1-indexed depth counter whilst ganache starts from 0 - _.forEach(structLogs, structLog => structLog.depth--); - } + const normalizedStructLogs = utils.normalizeStructLogs(structLogs); // tslint:disable-next-line:prefer-for-of - for (let i = 0; i < structLogs.length; i++) { - const structLog = structLogs[i]; + for (let i = 0; i < normalizedStructLogs.length; i++) { + const structLog = normalizedStructLogs[i]; if (structLog.depth !== callStack.length - 1) { throw new Error("Malformed trace. Trace depth doesn't match call stack depth"); } @@ -34,27 +27,19 @@ export function getTracesByContractAddress(structLogs: StructLog[], startAddress // That means that we can always safely pop from it currentTraceSegment.push(structLog); - const isCallLike = _.includes( - [OpCode.CallCode, OpCode.StaticCall, OpCode.Call, OpCode.DelegateCall], - structLog.op, - ); - const isEndOpcode = _.includes( - [OpCode.Return, OpCode.Stop, OpCode.Revert, OpCode.Invalid, OpCode.SelfDestruct], - structLog.op, - ); - if (isCallLike) { + if (utils.isCallLike(structLog.op)) { const currentAddress = _.last(callStack) as string; const jumpAddressOffset = 1; - const newAddress = getAddressFromStackEntry( + const newAddress = utils.getAddressFromStackEntry( structLog.stack[structLog.stack.length - jumpAddressOffset - 1], ); - if (structLog === _.last(structLogs)) { + if (structLog === _.last(normalizedStructLogs)) { throw new Error('Malformed trace. CALL-like opcode can not be the last one'); } // Sometimes calls don't change the execution context (current address). When we do a transfer to an // externally owned account - it does the call and immediately returns because there is no fallback // function. We manually check if the call depth had changed to handle that case. - const nextStructLog = structLogs[i + 1]; + const nextStructLog = normalizedStructLogs[i + 1]; if (nextStructLog.depth !== structLog.depth) { callStack.push(newAddress); traceByContractAddress[currentAddress] = (traceByContractAddress[currentAddress] || []).concat( @@ -62,7 +47,7 @@ export function getTracesByContractAddress(structLogs: StructLog[], startAddress ); currentTraceSegment = []; } - } else if (isEndOpcode) { + } else if (utils.isEndOpcode(structLog.op)) { const currentAddress = callStack.pop() as string; traceByContractAddress[currentAddress] = (traceByContractAddress[currentAddress] || []).concat( currentTraceSegment, @@ -85,8 +70,8 @@ export function getTracesByContractAddress(structLogs: StructLog[], startAddress ); return traceByContractAddress; } else { - if (structLog !== _.last(structLogs)) { - const nextStructLog = structLogs[i + 1]; + if (structLog !== _.last(normalizedStructLogs)) { + const nextStructLog = normalizedStructLogs[i + 1]; if (nextStructLog.depth === structLog.depth) { continue; } else if (nextStructLog.depth === structLog.depth - 1) { diff --git a/packages/sol-cov/src/types.ts b/packages/sol-cov/src/types.ts index 896d4a7b5..cef7141cb 100644 --- a/packages/sol-cov/src/types.ts +++ b/packages/sol-cov/src/types.ts @@ -107,3 +107,10 @@ export type TraceInfo = TraceInfoNewContract | TraceInfoExistingContract; export enum BlockParamLiteral { Latest = 'latest', } + +export interface EvmCallStackEntry { + structLog: StructLog; + address: string; +} + +export type EvmCallStack = EvmCallStackEntry[]; diff --git a/packages/sol-cov/src/utils.ts b/packages/sol-cov/src/utils.ts index 0b32df02e..4f16a1cda 100644 --- a/packages/sol-cov/src/utils.ts +++ b/packages/sol-cov/src/utils.ts @@ -1,3 +1,6 @@ +import { addressUtils, BigNumber } from '@0xproject/utils'; +import { OpCode, StructLog } from 'ethereum-types'; +import { addHexPrefix } from 'ethereumjs-util'; import * as _ from 'lodash'; import { ContractData, LineColumn, SingleFileSourceRange } from './types'; @@ -42,4 +45,25 @@ export const utils = { }); 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; + }, }; -- cgit v1.2.3 From 5fa6a2848f2c70f656f8cc711d10d708edb10949 Mon Sep 17 00:00:00 2001 From: fragosti Date: Thu, 14 Jun 2018 14:07:41 -0700 Subject: Fix typo --- packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts index 2c517502a..8548a06b6 100644 --- a/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts +++ b/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts @@ -2,7 +2,7 @@ import { schemas } from '@0xproject/json-schemas'; import { formatters, getOrderHashHex, OrderStateUtils } from '@0xproject/order-utils'; import { BlockParamLiteral, - ContractAbi + ContractAbi, DecodedLogArgs, ECSignature, ExchangeContractErrs, -- cgit v1.2.3 From fadd91b6a27327c8f4eba76b5e0e5756c119a69e Mon Sep 17 00:00:00 2001 From: fragosti Date: Thu, 14 Jun 2018 14:22:53 -0700 Subject: Add to changelog for contract-wrappers --- packages/contract-wrappers/CHANGELOG.json | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'packages') diff --git a/packages/contract-wrappers/CHANGELOG.json b/packages/contract-wrappers/CHANGELOG.json index 84e773933..0669b9c17 100644 --- a/packages/contract-wrappers/CHANGELOG.json +++ b/packages/contract-wrappers/CHANGELOG.json @@ -1,4 +1,12 @@ [ + { + "version": "0.0.3", + "changes": [ + { + "note": "Expose 'abi' ContractAbi property on all contract wrappers" + } + ] + }, { "version": "0.0.2", "changes": [ -- cgit v1.2.3 From 263bfb1bdad8acdb9b534edf6e79024e8da35721 Mon Sep 17 00:00:00 2001 From: Alex Browne Date: Thu, 14 Jun 2018 15:46:59 -0700 Subject: Fix a bug in revert_trace.ts --- packages/contracts/src/utils/web3_wrapper.ts | 46 ++++++++++++------------ packages/sol-cov/src/revert_trace.ts | 27 +++++++++----- packages/sol-cov/src/revert_trace_subprovider.ts | 6 +++- packages/sol-cov/src/trace.ts | 16 ++++----- 4 files changed, 55 insertions(+), 40 deletions(-) (limited to 'packages') diff --git a/packages/contracts/src/utils/web3_wrapper.ts b/packages/contracts/src/utils/web3_wrapper.ts index b8e8ed8ce..f51ad435b 100644 --- a/packages/contracts/src/utils/web3_wrapper.ts +++ b/packages/contracts/src/utils/web3_wrapper.ts @@ -51,29 +51,29 @@ const isCoverageEnabled = env.parseBoolean(EnvVars.SolidityCoverage); const isProfilerEnabled = env.parseBoolean(EnvVars.SolidityProfiler); const isRevertTraceEnabled = env.parseBoolean(EnvVars.SolidityRevertTrace); // TODO(albrow): Include revertTrace checks in the warnings below. -// if (isCoverageEnabled && isProfilerEnabled) { -// throw new Error( -// `Unfortunately for now you can't enable both coverage and profiler at the same time. They both use coverage.json file and there is no way to configure that.`, -// ); -// } -// if (isCoverageEnabled) { -// const coverageSubprovider = coverage.getCoverageSubproviderSingleton(); -// prependSubprovider(provider, coverageSubprovider); -// } -// if (isProfilerEnabled) { -// if (testProvider === ProviderType.Ganache) { -// logUtils.warn( -// "Gas costs in Ganache traces are incorrect and we don't recommend using it for profiling. Please switch to Geth", -// ); -// process.exit(1); -// } -// const profilerSubprovider = profiler.getProfilerSubproviderSingleton(); -// logUtils.log( -// "By default profilerSubprovider is stopped so that you don't get noise from setup code. Don't forget to start it before the code you want to profile and stop it afterwards", -// ); -// profilerSubprovider.stop(); -// prependSubprovider(provider, profilerSubprovider); -// } +if (isCoverageEnabled && isProfilerEnabled) { + throw new Error( + `Unfortunately for now you can't enable both coverage and profiler at the same time. They both use coverage.json file and there is no way to configure that.`, + ); +} +if (isCoverageEnabled) { + const coverageSubprovider = coverage.getCoverageSubproviderSingleton(); + prependSubprovider(provider, coverageSubprovider); +} +if (isProfilerEnabled) { + if (testProvider === ProviderType.Ganache) { + logUtils.warn( + "Gas costs in Ganache traces are incorrect and we don't recommend using it for profiling. Please switch to Geth", + ); + process.exit(1); + } + const profilerSubprovider = profiler.getProfilerSubproviderSingleton(); + logUtils.log( + "By default profilerSubprovider is stopped so that you don't get noise from setup code. Don't forget to start it before the code you want to profile and stop it afterwards", + ); + profilerSubprovider.stop(); + prependSubprovider(provider, profilerSubprovider); +} if (isRevertTraceEnabled) { const revertTraceSubprovider = revertTrace.getRevertTraceSubproviderSingleton(); prependSubprovider(provider, revertTraceSubprovider); diff --git a/packages/sol-cov/src/revert_trace.ts b/packages/sol-cov/src/revert_trace.ts index 59edf5db4..04d410f0a 100644 --- a/packages/sol-cov/src/revert_trace.ts +++ b/packages/sol-cov/src/revert_trace.ts @@ -8,7 +8,7 @@ import { utils } from './utils'; export function getRevertTrace(structLogs: StructLog[], startAddress: string): EvmCallStack { const evmCallStack: EvmCallStack = []; - let currentAddress = startAddress; + const addressStack = [startAddress]; if (_.isEmpty(structLogs)) { return []; } @@ -16,19 +16,17 @@ export function getRevertTrace(structLogs: StructLog[], startAddress: string): E // tslint:disable-next-line:prefer-for-of for (let i = 0; i < normalizedStructLogs.length; i++) { const structLog = normalizedStructLogs[i]; - if (structLog.depth !== evmCallStack.length) { + 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 - // TODO(albrow): split out isCallLike and isEndOpcode if (utils.isCallLike(structLog.op)) { - const evmCallStackEntry = _.last(evmCallStack) as EvmCallStackEntry; - const prevAddress = _.isUndefined(evmCallStackEntry) ? currentAddress : evmCallStackEntry.address; + const currentAddress = _.last(addressStack) as string; const jumpAddressOffset = 1; - currentAddress = utils.getAddressFromStackEntry( + const newAddress = utils.getAddressFromStackEntry( structLog.stack[structLog.stack.length - jumpAddressOffset - 1], ); @@ -40,8 +38,9 @@ export function getRevertTrace(structLogs: StructLog[], startAddress: string): E // 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: prevAddress, + address: currentAddress, structLog, }); } @@ -50,6 +49,7 @@ export function getRevertTrace(structLogs: StructLog[], startAddress: string): E 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 @@ -63,7 +63,7 @@ export function getRevertTrace(structLogs: StructLog[], startAddress: string): E } } else if (structLog.op === OpCode.Revert) { evmCallStack.push({ - address: currentAddress, + address: _.last(addressStack) as string, structLog, }); return evmCallStack; @@ -73,6 +73,17 @@ export function getRevertTrace(structLogs: StructLog[], startAddress: string): E "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) { diff --git a/packages/sol-cov/src/revert_trace_subprovider.ts b/packages/sol-cov/src/revert_trace_subprovider.ts index ea878058c..aef9c7568 100644 --- a/packages/sol-cov/src/revert_trace_subprovider.ts +++ b/packages/sol-cov/src/revert_trace_subprovider.ts @@ -186,6 +186,10 @@ export class RevertTraceSubprovider extends Subprovider { } 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)) { @@ -223,7 +227,7 @@ export class RevertTraceSubprovider extends Subprovider { sourceRanges.push(sourceRange); } if (sourceRanges.length > 0) { - this._logger.error('\n\nStack trace:\n'); + this._logger.error('\n\nStack trace for REVERT:\n'); _.forEach(sourceRanges, sourceRange => { this._logger.error( `${sourceRange.fileName}:${sourceRange.location.start.line}:${sourceRange.location.start.column}`, diff --git a/packages/sol-cov/src/trace.ts b/packages/sol-cov/src/trace.ts index b43fd19a2..fad2e5e08 100644 --- a/packages/sol-cov/src/trace.ts +++ b/packages/sol-cov/src/trace.ts @@ -11,7 +11,7 @@ export interface TraceByContractAddress { export function getTracesByContractAddress(structLogs: StructLog[], startAddress: string): TraceByContractAddress { const traceByContractAddress: TraceByContractAddress = {}; let currentTraceSegment = []; - const callStack = [startAddress]; + const addressStack = [startAddress]; if (_.isEmpty(structLogs)) { return traceByContractAddress; } @@ -19,7 +19,7 @@ export function getTracesByContractAddress(structLogs: StructLog[], startAddress // tslint:disable-next-line:prefer-for-of for (let i = 0; i < normalizedStructLogs.length; i++) { const structLog = normalizedStructLogs[i]; - if (structLog.depth !== callStack.length - 1) { + 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 @@ -28,7 +28,7 @@ export function getTracesByContractAddress(structLogs: StructLog[], startAddress currentTraceSegment.push(structLog); if (utils.isCallLike(structLog.op)) { - const currentAddress = _.last(callStack) as string; + const currentAddress = _.last(addressStack) as string; const jumpAddressOffset = 1; const newAddress = utils.getAddressFromStackEntry( structLog.stack[structLog.stack.length - jumpAddressOffset - 1], @@ -41,14 +41,14 @@ export function getTracesByContractAddress(structLogs: StructLog[], startAddress // function. We manually check if the call depth had changed to handle that case. const nextStructLog = normalizedStructLogs[i + 1]; if (nextStructLog.depth !== structLog.depth) { - callStack.push(newAddress); + addressStack.push(newAddress); traceByContractAddress[currentAddress] = (traceByContractAddress[currentAddress] || []).concat( currentTraceSegment, ); currentTraceSegment = []; } } else if (utils.isEndOpcode(structLog.op)) { - const currentAddress = callStack.pop() as string; + const currentAddress = addressStack.pop() as string; traceByContractAddress[currentAddress] = (traceByContractAddress[currentAddress] || []).concat( currentTraceSegment, ); @@ -75,7 +75,7 @@ export function getTracesByContractAddress(structLogs: StructLog[], startAddress if (nextStructLog.depth === structLog.depth) { continue; } else if (nextStructLog.depth === structLog.depth - 1) { - const currentAddress = callStack.pop() as string; + const currentAddress = addressStack.pop() as string; traceByContractAddress[currentAddress] = (traceByContractAddress[currentAddress] || []).concat( currentTraceSegment, ); @@ -86,11 +86,11 @@ export function getTracesByContractAddress(structLogs: StructLog[], startAddress } } } - if (callStack.length !== 0) { + if (addressStack.length !== 0) { logUtils.warn('Malformed trace. Call stack non empty at the end'); } if (currentTraceSegment.length !== 0) { - const currentAddress = callStack.pop() as string; + const currentAddress = addressStack.pop() as string; traceByContractAddress[currentAddress] = (traceByContractAddress[currentAddress] || []).concat( currentTraceSegment, ); -- cgit v1.2.3 From a9c23b7c2857a826756b919e6be73456536e344d Mon Sep 17 00:00:00 2001 From: Alex Browne Date: Thu, 14 Jun 2018 15:50:54 -0700 Subject: Reverse order of stack trace to match behavior of most other language stack traces --- packages/sol-cov/src/revert_trace_subprovider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/sol-cov/src/revert_trace_subprovider.ts b/packages/sol-cov/src/revert_trace_subprovider.ts index aef9c7568..1889f13c4 100644 --- a/packages/sol-cov/src/revert_trace_subprovider.ts +++ b/packages/sol-cov/src/revert_trace_subprovider.ts @@ -228,7 +228,7 @@ export class RevertTraceSubprovider extends Subprovider { } if (sourceRanges.length > 0) { this._logger.error('\n\nStack trace for REVERT:\n'); - _.forEach(sourceRanges, sourceRange => { + _.forEach(_.reverse(sourceRanges), sourceRange => { this._logger.error( `${sourceRange.fileName}:${sourceRange.location.start.line}:${sourceRange.location.start.column}`, ); -- cgit v1.2.3 From d9292a70bfa00715b6b007b0ecd696db02b1d042 Mon Sep 17 00:00:00 2001 From: Alex Browne Date: Thu, 14 Jun 2018 16:00:24 -0700 Subject: Remove unused variables and other small fixes --- packages/contracts/package.json | 2 +- packages/contracts/src/utils/web3_wrapper.ts | 11 ++++++----- packages/sol-cov/src/revert_trace.ts | 2 +- packages/sol-cov/src/revert_trace_subprovider.ts | 6 +++--- 4 files changed, 11 insertions(+), 10 deletions(-) (limited to 'packages') diff --git a/packages/contracts/package.json b/packages/contracts/package.json index ad7cbf476..2495795dc 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -19,7 +19,7 @@ "rebuild_and_test": "run-s build test", "test:coverage": "SOLIDITY_COVERAGE=true run-s build run_mocha coverage:report:text coverage:report:lcov", "test:profiler": "SOLIDITY_PROFILER=true run-s build run_mocha profiler:report:html", - "test:trace": "SOLIDITY_REVERT_TRACE=true run-s run_mocha", + "test:trace": "SOLIDITY_REVERT_TRACE=true run-s build run_mocha", "run_mocha": "mocha --require source-map-support/register 'lib/test/**/*.js' --timeout 100000 --bail --exit", "compile": "sol-compiler", "clean": "shx rm -rf lib src/generated_contract_wrappers", diff --git a/packages/contracts/src/utils/web3_wrapper.ts b/packages/contracts/src/utils/web3_wrapper.ts index f51ad435b..772e4c613 100644 --- a/packages/contracts/src/utils/web3_wrapper.ts +++ b/packages/contracts/src/utils/web3_wrapper.ts @@ -7,6 +7,8 @@ import { coverage } from './coverage'; import { profiler } from './profiler'; import { revertTrace } from './revert_trace'; +import * as _ from 'lodash'; + enum ProviderType { Ganache = 'ganache', Geth = 'geth', @@ -50,11 +52,10 @@ export const provider = web3Factory.getRpcProvider(providerConfigs); const isCoverageEnabled = env.parseBoolean(EnvVars.SolidityCoverage); const isProfilerEnabled = env.parseBoolean(EnvVars.SolidityProfiler); const isRevertTraceEnabled = env.parseBoolean(EnvVars.SolidityRevertTrace); -// TODO(albrow): Include revertTrace checks in the warnings below. -if (isCoverageEnabled && isProfilerEnabled) { - throw new Error( - `Unfortunately for now you can't enable both coverage and profiler at the same time. They both use coverage.json file and there is no way to configure that.`, - ); +const enabledSubproviderCount = _.filter([isCoverageEnabled, isProfilerEnabled, isRevertTraceEnabled], _.identity) + .length; +if (enabledSubproviderCount > 1) { + throw new Error(`Only one of coverage, profiler, and revert trace subproviders can be enabled at a time`); } if (isCoverageEnabled) { const coverageSubprovider = coverage.getCoverageSubproviderSingleton(); diff --git a/packages/sol-cov/src/revert_trace.ts b/packages/sol-cov/src/revert_trace.ts index 04d410f0a..1d52b969e 100644 --- a/packages/sol-cov/src/revert_trace.ts +++ b/packages/sol-cov/src/revert_trace.ts @@ -3,7 +3,7 @@ import { OpCode, StructLog } from 'ethereum-types'; import * as _ from 'lodash'; -import { EvmCallStack, EvmCallStackEntry } from './types'; +import { EvmCallStack } from './types'; import { utils } from './utils'; export function getRevertTrace(structLogs: StructLog[], startAddress: string): EvmCallStack { diff --git a/packages/sol-cov/src/revert_trace_subprovider.ts b/packages/sol-cov/src/revert_trace_subprovider.ts index 1889f13c4..68e752aee 100644 --- a/packages/sol-cov/src/revert_trace_subprovider.ts +++ b/packages/sol-cov/src/revert_trace_subprovider.ts @@ -117,14 +117,14 @@ export class RevertTraceSubprovider extends Subprovider { 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); + await this._recordTxTraceAsync(toAddress, 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); + await this._recordTxTraceAsync(toAddress, transaction.hash); } } if (!txData.isFakeTransaction) { @@ -143,7 +143,7 @@ export class RevertTraceSubprovider extends Subprovider { await this._recordCallOrGasEstimateTraceAsync(callData); cb(); } - private async _recordTxTraceAsync(address: string, data: string | undefined, txHash: string): Promise { + private async _recordTxTraceAsync(address: string, txHash: string): Promise { await this._web3Wrapper.awaitTransactionMinedAsync(txHash, 0); const trace = await this._web3Wrapper.getTransactionTraceAsync(txHash, { disableMemory: true, -- cgit v1.2.3 From 5a8539a1228baeb085ed7851245337f27ee1d974 Mon Sep 17 00:00:00 2001 From: Alex Browne Date: Thu, 14 Jun 2018 16:04:08 -0700 Subject: Fix linter errors and remove coverage.json --- packages/contracts/src/utils/web3_wrapper.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'packages') diff --git a/packages/contracts/src/utils/web3_wrapper.ts b/packages/contracts/src/utils/web3_wrapper.ts index 772e4c613..67b71cdeb 100644 --- a/packages/contracts/src/utils/web3_wrapper.ts +++ b/packages/contracts/src/utils/web3_wrapper.ts @@ -2,13 +2,12 @@ import { devConstants, env, EnvVars, web3Factory } from '@0xproject/dev-utils'; import { prependSubprovider } from '@0xproject/subproviders'; import { logUtils } from '@0xproject/utils'; import { Web3Wrapper } from '@0xproject/web3-wrapper'; +import * as _ from 'lodash'; import { coverage } from './coverage'; import { profiler } from './profiler'; import { revertTrace } from './revert_trace'; -import * as _ from 'lodash'; - enum ProviderType { Ganache = 'ganache', Geth = 'geth', -- cgit v1.2.3 From 897560745a7e528691e03ecfa99ca25da26135ba Mon Sep 17 00:00:00 2001 From: Alex Browne Date: Thu, 14 Jun 2018 16:33:09 -0700 Subject: De-duplicate code by refactoring subprovider classes --- packages/sol-cov/src/coverage_subprovider.ts | 6 +- packages/sol-cov/src/profiler_subprovider.ts | 6 +- packages/sol-cov/src/revert_trace_subprovider.ts | 158 ++------------------- .../sol-cov/src/trace_collection_subprovider.ts | 64 ++------- packages/sol-cov/src/trace_info_subprovider.ts | 59 ++++++++ 5 files changed, 89 insertions(+), 204 deletions(-) create mode 100644 packages/sol-cov/src/trace_info_subprovider.ts (limited to 'packages') diff --git a/packages/sol-cov/src/coverage_subprovider.ts b/packages/sol-cov/src/coverage_subprovider.ts index 0fa7f873e..065a48434 100644 --- a/packages/sol-cov/src/coverage_subprovider.ts +++ b/packages/sol-cov/src/coverage_subprovider.ts @@ -2,8 +2,8 @@ import * as _ from 'lodash'; import { AbstractArtifactAdapter } from './artifact_adapters/abstract_artifact_adapter'; import { collectCoverageEntries } from './collect_coverage_entries'; -import { TraceCollectionSubprovider } from './trace_collection_subprovider'; import { SingleFileSubtraceHandler, TraceCollector } from './trace_collector'; +import { TraceInfoSubprovider } from './trace_info_subprovider'; import { BranchCoverage, ContractData, @@ -22,7 +22,7 @@ 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 TraceCollectionSubprovider { +export class CoverageSubprovider extends TraceInfoSubprovider { private _coverageCollector: TraceCollector; /** * Instantiates a CoverageSubprovider instance @@ -39,7 +39,7 @@ export class CoverageSubprovider extends TraceCollectionSubprovider { super(defaultFromAddress, traceCollectionSubproviderConfig); this._coverageCollector = new TraceCollector(artifactAdapter, isVerbose, coverageHandler); } - public async handleTraceInfoAsync(traceInfo: TraceInfo): Promise { + protected async _handleTraceInfoAsync(traceInfo: TraceInfo): Promise { await this._coverageCollector.computeSingleTraceCoverageAsync(traceInfo); } /** diff --git a/packages/sol-cov/src/profiler_subprovider.ts b/packages/sol-cov/src/profiler_subprovider.ts index 62ed1b472..9f98da524 100644 --- a/packages/sol-cov/src/profiler_subprovider.ts +++ b/packages/sol-cov/src/profiler_subprovider.ts @@ -2,8 +2,8 @@ import * as _ from 'lodash'; import { AbstractArtifactAdapter } from './artifact_adapters/abstract_artifact_adapter'; import { collectCoverageEntries } from './collect_coverage_entries'; -import { TraceCollectionSubprovider } from './trace_collection_subprovider'; import { SingleFileSubtraceHandler, TraceCollector } from './trace_collector'; +import { TraceInfoSubprovider } from './trace_info_subprovider'; import { ContractData, Coverage, SourceRange, Subtrace, TraceInfo } from './types'; import { utils } from './utils'; @@ -11,7 +11,7 @@ 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 TraceCollectionSubprovider { +export class ProfilerSubprovider extends TraceInfoSubprovider { private _profilerCollector: TraceCollector; /** * Instantiates a ProfilerSubprovider instance @@ -28,7 +28,7 @@ export class ProfilerSubprovider extends TraceCollectionSubprovider { super(defaultFromAddress, traceCollectionSubproviderConfig); this._profilerCollector = new TraceCollector(artifactAdapter, isVerbose, profilerHandler); } - public async handleTraceInfoAsync(traceInfo: TraceInfo): Promise { + protected async _handleTraceInfoAsync(traceInfo: TraceInfo): Promise { await this._profilerCollector.computeSingleTraceCoverageAsync(traceInfo); } /** diff --git a/packages/sol-cov/src/revert_trace_subprovider.ts b/packages/sol-cov/src/revert_trace_subprovider.ts index 68e752aee..f6501757a 100644 --- a/packages/sol-cov/src/revert_trace_subprovider.ts +++ b/packages/sol-cov/src/revert_trace_subprovider.ts @@ -1,149 +1,43 @@ -import { BlockchainLifecycle } from '@0xproject/dev-utils'; -import { Callback, ErrorCallback, NextCallback, Subprovider } from '@0xproject/subproviders'; -import { Web3Wrapper } from '@0xproject/web3-wrapper'; -import { CallData, JSONRPCRequestPayload, Provider, TxData } from 'ethereum-types'; import { stripHexPrefix } from 'ethereumjs-util'; import * as _ from 'lodash'; import { getLogger, levels, Logger } from 'loglevel'; -import { Lock } from 'semaphore-async-await'; import { AbstractArtifactAdapter } from './artifact_adapters/abstract_artifact_adapter'; import { constants } from './constants'; import { getRevertTrace } from './revert_trace'; import { parseSourceMap } from './source_maps'; -import { BlockParamLiteral, ContractData, EvmCallStack, SourceRange } from './types'; +import { TraceCollectionSubprovider } from './trace_collection_subprovider'; +import { ContractData, EvmCallStack, SourceRange } from './types'; import { utils } from './utils'; -interface MaybeFakeTxData extends TxData { - isFakeTransaction?: boolean; -} - -const BLOCK_GAS_LIMIT = 6000000; - /** * 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 Subprovider { +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 _lock = new Lock(); - private _defaultFromAddress: string; - private _web3Wrapper!: Web3Wrapper; - private _isEnabled = true; - private _artifactAdapter: AbstractArtifactAdapter; private _contractsData!: ContractData[]; + private _artifactAdapter: AbstractArtifactAdapter; private _logger: Logger; /** - * Instantiates a TraceCollectionSubprovider instance + * 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) { - super(); + constructor(artifactAdapter: AbstractArtifactAdapter, defaultFromAddress: string, isVerbose: boolean = true) { + const traceCollectionSubproviderConfig = { + shouldCollectTransactionTraces: true, + shouldCollectGasEstimateTraces: true, + shouldCollectCallTraces: true, + }; + super(defaultFromAddress, traceCollectionSubproviderConfig); this._artifactAdapter = artifactAdapter; - this._defaultFromAddress = defaultFromAddress; this._logger = getLogger('sol-cov'); this._logger.setLevel(isVerbose ? levels.TRACE : levels.ERROR); } - /** - * 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': - const txData = payload.params[0]; - next(this._onTransactionSentAsync.bind(this, txData)); - return; - - case 'eth_call': - const callData = payload.params[0]; - next(this._onCallOrGasEstimateExecutedAsync.bind(this, callData)); - return; - - case 'eth_estimateGas': - 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. - */ - public setEngine(engine: Provider): void { - super.setEngine(engine); - this._web3Wrapper = new Web3Wrapper(engine); - } - 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, 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.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 _recordTxTraceAsync(address: string, txHash: string): 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, @@ -157,28 +51,6 @@ export class RevertTraceSubprovider extends Subprovider { await this._printStackTraceAsync(evmCallStack); } } - 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: MaybeFakeTxData = { - gas: BLOCK_GAS_LIMIT, - 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 txHash = await this._web3Wrapper.sendTransactionAsync(fakeTxData); - await this._web3Wrapper.awaitTransactionMinedAsync(txHash, 0); - } catch (err) { - // Even if this transaction failed - we've already recorded it's trace. - _.noop(); - } - await blockchainLifecycle.revertAsync(); - this._lock.release(); - } private async _printStackTraceAsync(evmCallStack: EvmCallStack): Promise { const sourceRanges: SourceRange[] = []; if (_.isUndefined(this._contractsData)) { diff --git a/packages/sol-cov/src/trace_collection_subprovider.ts b/packages/sol-cov/src/trace_collection_subprovider.ts index 742735935..9866472b9 100644 --- a/packages/sol-cov/src/trace_collection_subprovider.ts +++ b/packages/sol-cov/src/trace_collection_subprovider.ts @@ -6,8 +6,7 @@ import * as _ from 'lodash'; import { Lock } from 'semaphore-async-await'; import { constants } from './constants'; -import { getTracesByContractAddress } from './trace'; -import { BlockParamLiteral, TraceInfo, TraceInfoExistingContract, TraceInfoNewContract } from './types'; +import { BlockParamLiteral } from './types'; interface MaybeFakeTxData extends TxData { isFakeTransaction?: boolean; @@ -27,13 +26,14 @@ export interface TraceCollectionSubproviderConfig { /** * 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 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 _lock = new Lock(); private _defaultFromAddress: string; - private _web3Wrapper!: Web3Wrapper; private _isEnabled = true; private _config: TraceCollectionSubproviderConfig; /** @@ -57,11 +57,6 @@ export abstract class TraceCollectionSubprovider extends Subprovider { public stop(): void { this._isEnabled = false; } - /** - * Called for each subtrace. - * @param traceInfo Trace info for this subtrace. - */ - public abstract handleTraceInfoAsync(traceInfo: TraceInfo): Promise; /** * This method conforms to the web3-provider-engine interface. * It is called internally by the ProviderEngine when it is this subproviders @@ -119,6 +114,11 @@ export abstract class TraceCollectionSubprovider extends Subprovider { 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, @@ -160,52 +160,6 @@ export abstract class TraceCollectionSubprovider extends Subprovider { await this._recordCallOrGasEstimateTraceAsync(callData); cb(); } - private 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); - } - } - } 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. diff --git a/packages/sol-cov/src/trace_info_subprovider.ts b/packages/sol-cov/src/trace_info_subprovider.ts new file mode 100644 index 000000000..635a68f58 --- /dev/null +++ b/packages/sol-cov/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); + } + } + } +} -- cgit v1.2.3 From ef61c3543fba2030515002ec418afc388e0e1033 Mon Sep 17 00:00:00 2001 From: Alex Browne Date: Thu, 14 Jun 2018 16:38:21 -0700 Subject: Fix linter errors --- packages/sol-cov/src/revert_trace_subprovider.ts | 1 + 1 file changed, 1 insertion(+) (limited to 'packages') diff --git a/packages/sol-cov/src/revert_trace_subprovider.ts b/packages/sol-cov/src/revert_trace_subprovider.ts index f6501757a..b1d4da10c 100644 --- a/packages/sol-cov/src/revert_trace_subprovider.ts +++ b/packages/sol-cov/src/revert_trace_subprovider.ts @@ -37,6 +37,7 @@ export class RevertTraceSubprovider extends TraceCollectionSubprovider { 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, { -- cgit v1.2.3 From 35f4f7573316812571a463b2b1743d8a5bea327e Mon Sep 17 00:00:00 2001 From: fragosti Date: Thu, 14 Jun 2018 16:49:06 -0700 Subject: Prettier --- packages/contract-wrappers/CHANGELOG.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/contract-wrappers/CHANGELOG.json b/packages/contract-wrappers/CHANGELOG.json index 0669b9c17..58ef090f7 100644 --- a/packages/contract-wrappers/CHANGELOG.json +++ b/packages/contract-wrappers/CHANGELOG.json @@ -6,7 +6,7 @@ "note": "Expose 'abi' ContractAbi property on all contract wrappers" } ] - }, + }, { "version": "0.0.2", "changes": [ -- cgit v1.2.3 From d118533d876fdf290af5e0e5a08dd84ff001eee4 Mon Sep 17 00:00:00 2001 From: Alex Browne Date: Thu, 14 Jun 2018 16:53:29 -0700 Subject: Remove redundant check in trace.ts and revert_trace.ts --- packages/sol-cov/src/revert_trace.ts | 3 --- packages/sol-cov/src/trace.ts | 4 +--- 2 files changed, 1 insertion(+), 6 deletions(-) (limited to 'packages') diff --git a/packages/sol-cov/src/revert_trace.ts b/packages/sol-cov/src/revert_trace.ts index 1d52b969e..a78d1afa8 100644 --- a/packages/sol-cov/src/revert_trace.ts +++ b/packages/sol-cov/src/revert_trace.ts @@ -30,9 +30,6 @@ export function getRevertTrace(structLogs: StructLog[], startAddress: string): E structLog.stack[structLog.stack.length - jumpAddressOffset - 1], ); - if (structLog === _.last(normalizedStructLogs)) { - throw new Error('Malformed trace. CALL-like opcode can not be the last one'); - } // Sometimes calls don't change the execution context (current address). When we do a transfer to an // externally owned account - it does the call and immediately returns because there is no fallback // function. We manually check if the call depth had changed to handle that case. diff --git a/packages/sol-cov/src/trace.ts b/packages/sol-cov/src/trace.ts index fad2e5e08..635019fc0 100644 --- a/packages/sol-cov/src/trace.ts +++ b/packages/sol-cov/src/trace.ts @@ -33,9 +33,7 @@ export function getTracesByContractAddress(structLogs: StructLog[], startAddress const newAddress = utils.getAddressFromStackEntry( structLog.stack[structLog.stack.length - jumpAddressOffset - 1], ); - if (structLog === _.last(normalizedStructLogs)) { - throw new Error('Malformed trace. CALL-like opcode can not be the last one'); - } + // Sometimes calls don't change the execution context (current address). When we do a transfer to an // externally owned account - it does the call and immediately returns because there is no fallback // function. We manually check if the call depth had changed to handle that case. -- cgit v1.2.3 From 7032825e35e825e42196d4ccc23b1a1f7fb8038a Mon Sep 17 00:00:00 2001 From: Alex Browne Date: Thu, 14 Jun 2018 16:53:48 -0700 Subject: Change wording of error message when you try to use more than one subprovider --- packages/contracts/src/utils/web3_wrapper.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/contracts/src/utils/web3_wrapper.ts b/packages/contracts/src/utils/web3_wrapper.ts index 67b71cdeb..c9d83a02d 100644 --- a/packages/contracts/src/utils/web3_wrapper.ts +++ b/packages/contracts/src/utils/web3_wrapper.ts @@ -54,7 +54,7 @@ const isRevertTraceEnabled = env.parseBoolean(EnvVars.SolidityRevertTrace); const enabledSubproviderCount = _.filter([isCoverageEnabled, isProfilerEnabled, isRevertTraceEnabled], _.identity) .length; if (enabledSubproviderCount > 1) { - throw new Error(`Only one of coverage, profiler, and revert trace subproviders can be enabled at a time`); + throw new Error(`Only one of coverage, profiler, or revert trace subproviders can be enabled at a time`); } if (isCoverageEnabled) { const coverageSubprovider = coverage.getCoverageSubproviderSingleton(); -- cgit v1.2.3 From d0a3779091661cfa099ec84f12e5d944287d1e3f Mon Sep 17 00:00:00 2001 From: fragosti Date: Thu, 14 Jun 2018 18:19:07 -0700 Subject: Add Pointer component --- .../components/onboarding/onboarding_tooltip.tsx | 35 ++++++----- packages/website/ts/components/ui/pointer.tsx | 68 ++++++++++++++++++++++ 2 files changed, 87 insertions(+), 16 deletions(-) create mode 100644 packages/website/ts/components/ui/pointer.tsx (limited to 'packages') diff --git a/packages/website/ts/components/onboarding/onboarding_tooltip.tsx b/packages/website/ts/components/onboarding/onboarding_tooltip.tsx index 155c70c5f..9e0744dcb 100644 --- a/packages/website/ts/components/onboarding/onboarding_tooltip.tsx +++ b/packages/website/ts/components/onboarding/onboarding_tooltip.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import { Container } from 'ts/components/ui/container'; import { Island } from 'ts/components/ui/island'; +import { Pointer } from 'ts/components/ui/pointer'; export type ContinueButtonDisplay = 'enabled' | 'disabled'; @@ -34,22 +35,24 @@ export const ContinueButton: React.StatelessComponent = (pr }; export const OnboardingTooltip: React.StatelessComponent = (props: OnboardingTooltipProps) => ( - - -
- {props.title} - {props.content} - {props.continueButtonDisplay && ( - - Continue - - )} - {!props.hideBackButton && } - {!props.hideNextButton && } - -
-
-
+ + + +
+ {props.title} + {props.content} + {props.continueButtonDisplay && ( + + Continue + + )} + {!props.hideBackButton && } + {!props.hideNextButton && } + +
+
+
+
); OnboardingTooltip.displayName = 'OnboardingTooltip'; diff --git a/packages/website/ts/components/ui/pointer.tsx b/packages/website/ts/components/ui/pointer.tsx new file mode 100644 index 000000000..dcd1b8e54 --- /dev/null +++ b/packages/website/ts/components/ui/pointer.tsx @@ -0,0 +1,68 @@ +import { colors } from '@0xproject/react-shared'; +import { Island } from 'ts/components/ui/island'; +import * as React from 'react'; +import { styled } from 'ts/style/theme'; + +export type PointerDirection = 'top' | 'right' | 'bottom' | 'left'; + +export interface PointerProps { + className?: string; + color?: string; + size?: number; + direction: PointerDirection; +} + +const PlainPointer: React.StatelessComponent = props =>
; + +const positionToCss = (props: PointerProps) => { + const position = { + top: `bottom: 100%; left: 50%;`, + right: `left: 100%; top: 50%;`, + bottom: `top: 100%; left: 50%;`, + left: `right: 100%; top: 50%;`, + }[props.direction]; + + const borderColorSide = { + top: 'border-bottom-color', + right: 'border-left-color', + bottom: 'border-top-color', + left: 'border-right-color', + }[props.direction]; + const border = `${borderColorSide}: ${props.color};`; + const marginSide = { + top: 'margin-left', + right: 'margin-top', + bottom: 'margin-left', + left: 'margin-top', + }[props.direction]; + const margin = `${marginSide}: -${props.size}px`; + return { + position, + border, + margin, + }; +}; + +export const Pointer = styled(PlainPointer)` + position: relative; + &:after { + border: solid transparent; + content: " "; + height: 0; + width: 0; + position: absolute; + pointer-events: none; + border-color: rgba(136, 183, 213, 0); + border-width: ${props => `${props.size}px`}; + ${props => positionToCss(props).position} + ${props => positionToCss(props).border} + ${props => positionToCss(props).margin} + } +`; + +Pointer.defaultProps = { + color: colors.white, + size: 16, +}; + +Pointer.displayName = 'Pointer'; -- cgit v1.2.3 From 8bac1706a119c0c4c02bbca77553976080f5ee20 Mon Sep 17 00:00:00 2001 From: "F. Eugene Aumson" Date: Fri, 15 Jun 2018 10:37:37 -0400 Subject: change @0xproject/types to ethereum-types --- packages/contract_templates/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/contract_templates/README.md b/packages/contract_templates/README.md index 39aa2d2c0..9546506c5 100644 --- a/packages/contract_templates/README.md +++ b/packages/contract_templates/README.md @@ -1,7 +1,7 @@ These templates are used with [abi-gen](https://github.com/0xProject/0x-monorepo/tree/development/packages/abi-gen). To successfully compile the generated TypeScript contract wrappers, you must: -* Install the packages on which the main contract template directly depends: `yarn add @0xproject/base-contract @0xproject/sol-compiler @0xproject/types @0xproject/utils @0xproject/web3-wrapper ethers lodash` +* Install the packages on which the main contract template directly depends: `yarn add @0xproject/base-contract @0xproject/sol-compiler @0xproject/utils @0xproject/web3-wrapper ethereum-types ethers lodash` * Install the packages on which the main contract template *in*directly depends: `yarn add @types/lodash` * Ensure that your TypeScript configuration includes the following: ``` -- cgit v1.2.3 From 54f79c2798c095344b003139144b77fc1024d8c3 Mon Sep 17 00:00:00 2001 From: fragosti Date: Fri, 15 Jun 2018 13:17:02 -0700 Subject: Improve styles of onboarding tooltip --- .../ts/components/onboarding/onboarding_flow.tsx | 10 +-- .../components/onboarding/onboarding_tooltip.tsx | 87 ++++++++++++++-------- .../onboarding/portal_onboarding_flow.tsx | 20 +++-- packages/website/ts/components/ui/button.tsx | 17 +++-- packages/website/ts/components/ui/container.tsx | 5 ++ packages/website/ts/components/ui/pointer.tsx | 3 +- packages/website/ts/components/ui/text.tsx | 28 ++++++- 7 files changed, 117 insertions(+), 53 deletions(-) (limited to 'packages') diff --git a/packages/website/ts/components/onboarding/onboarding_flow.tsx b/packages/website/ts/components/onboarding/onboarding_flow.tsx index 9879cd387..7a5a6e40f 100644 --- a/packages/website/ts/components/onboarding/onboarding_flow.tsx +++ b/packages/website/ts/components/onboarding/onboarding_flow.tsx @@ -10,8 +10,8 @@ export interface Step { title?: string; content: React.ReactNode; placement?: Placement; - hideBackButton?: boolean; - hideNextButton?: boolean; + shouldHideBackButton?: boolean; + shouldHideNextButton?: boolean; continueButtonDisplay?: ContinueButtonDisplay; } @@ -54,13 +54,13 @@ export class OnboardingFlow extends React.Component { const step = steps[stepIndex]; const isLastStep = steps.length - 1 === stepIndex; return ( - + void; onClickBack: () => void; continueButtonDisplay?: ContinueButtonDisplay; - hideBackButton?: boolean; - hideNextButton?: boolean; + shouldHideBackButton?: boolean; + shouldHideNextButton?: boolean; + pointerDirection?: PointerDirection; + className?: string; } -// TODO: Make this more general button. -export interface ContinueButtonProps { - display: ContinueButtonDisplay; - children?: string; - onClick: () => void; -} - -export const ContinueButton: React.StatelessComponent = (props: ContinueButtonProps) => { - const isDisabled = props.display === 'disabled'; - return ( - - ); -}; - -export const OnboardingTooltip: React.StatelessComponent = (props: OnboardingTooltipProps) => ( - +export const OnboardingTooltip: React.StatelessComponent = ({ + title, + content, + continueButtonDisplay, + onClickNext, + onClickBack, + onClose, + shouldHideBackButton, + shouldHideNextButton, + pointerDirection, + className, +}) => ( +
- {props.title} - {props.content} - {props.continueButtonDisplay && ( - +
+ {title} + + + Close + + +
+ + {content} + + {continueButtonDisplay && ( + )} - {!props.hideBackButton && } - {!props.hideNextButton && } - + + {!shouldHideBackButton && ( + + Back + + )} + {!shouldHideNextButton && ( + + Skip + + )} +
); +OnboardingTooltip.defaultProps = { + pointerDirection: 'left', +}; + OnboardingTooltip.displayName = 'OnboardingTooltip'; diff --git a/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx b/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx index efb844cb5..3deefec3c 100644 --- a/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx +++ b/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx @@ -47,41 +47,47 @@ export class PortalOnboardingFlow extends React.Component Unlock your tokens for trading. You only need to do this once for each token. @@ -94,9 +100,11 @@ export class PortalOnboardingFlow extends React.Component) => void; } -const PlainButton: React.StatelessComponent = ({ children, onClick, type, className }) => ( - ); export const Button = styled(PlainButton)` - cursor: pointer; + cursor: ${props => (props.isDisabled ? 'default' : 'pointer')}; font-size: ${props => props.fontSize}; color: ${props => props.fontColor}; + transition: background-color 0.5s ease; padding: 0.8em 2.2em; border-radius: 6px; box-shadow: 0px 0px 4px rgba(0, 0, 0, 0.25); font-weight: 500; font-family: ${props => props.fontFamily}; width: ${props => props.width}; - background-color: ${props => props.backgroundColor}; + background-color: ${props => (props.isDisabled ? grayscale(props.backgroundColor) : props.backgroundColor)}; border: ${props => (props.borderColor ? `1px solid ${props.borderColor}` : 'none')}; &:hover { - background-color: ${props => darken(0.1, props.backgroundColor)}; + background-color: ${props => (!props.isDisabled ? darken(0.1, props.backgroundColor) : '')}; } &:active { - background-color: ${props => darken(0.2, props.backgroundColor)}; + background-color: ${props => (!props.isDisabled ? darken(0.2, props.backgroundColor) : '')}; } `; @@ -46,6 +48,7 @@ Button.defaultProps = { backgroundColor: colors.white, width: 'auto', fontFamily: 'Roboto', + isDisabled: false, }; Button.displayName = 'Button'; diff --git a/packages/website/ts/components/ui/container.tsx b/packages/website/ts/components/ui/container.tsx index c6a78e181..3b55a18ce 100644 --- a/packages/website/ts/components/ui/container.tsx +++ b/packages/website/ts/components/ui/container.tsx @@ -16,6 +16,11 @@ export interface ContainerProps { maxWidth?: StringOrNum; isHidden?: boolean; className?: string; + position?: 'absolute' | 'fixed' | 'relative' | 'unset'; + top?: string; + left?: string; + right?: string; + bottom?: string; } export const Container: React.StatelessComponent = ({ children, className, isHidden, ...style }) => { diff --git a/packages/website/ts/components/ui/pointer.tsx b/packages/website/ts/components/ui/pointer.tsx index dcd1b8e54..448786bb4 100644 --- a/packages/website/ts/components/ui/pointer.tsx +++ b/packages/website/ts/components/ui/pointer.tsx @@ -1,5 +1,4 @@ import { colors } from '@0xproject/react-shared'; -import { Island } from 'ts/components/ui/island'; import * as React from 'react'; import { styled } from 'ts/style/theme'; @@ -12,7 +11,7 @@ export interface PointerProps { direction: PointerDirection; } -const PlainPointer: React.StatelessComponent = props =>
; +const PlainPointer: React.StatelessComponent = props =>
; const positionToCss = (props: PointerProps) => { const position = { diff --git a/packages/website/ts/components/ui/text.tsx b/packages/website/ts/components/ui/text.tsx index 7e47f1d09..073bfc2d2 100644 --- a/packages/website/ts/components/ui/text.tsx +++ b/packages/website/ts/components/ui/text.tsx @@ -1,8 +1,9 @@ import { colors } from '@0xproject/react-shared'; +import { darken } from 'polished'; import * as React from 'react'; import { styled } from 'ts/style/theme'; -export type TextTag = 'p' | 'div' | 'span' | 'label'; +export type TextTag = 'p' | 'div' | 'span' | 'label' | 'h1' | 'h2' | 'h3' | 'h4'; export interface TextProps { className?: string; @@ -14,10 +15,13 @@ export interface TextProps { minHeight?: string; center?: boolean; fontWeight?: number | string; + onClick?: () => void; } -const PlainText: React.StatelessComponent = ({ children, className, Tag }) => ( - {children} +const PlainText: React.StatelessComponent = ({ children, className, onClick, Tag }) => ( + + {children} + ); export const Text = styled(PlainText)` @@ -28,14 +32,30 @@ export const Text = styled(PlainText)` ${props => (props.center ? 'text-align: center' : '')}; color: ${props => props.fontColor}; ${props => (props.minHeight ? `min-height: ${props.minHeight}` : '')}; + ${props => (props.onClick ? 'cursor: pointer' : '')}; + transition: color 0.5s ease; + &:hover { + ${props => (props.onClick ? `color: ${darken(0.1, props.fontColor)}` : '')}; + } `; Text.defaultProps = { fontFamily: 'Roboto', fontWeight: 400, - fontColor: colors.white, + fontColor: colors.black, fontSize: '14px', Tag: 'div', }; Text.displayName = 'Text'; + +export const Title: React.StatelessComponent = props => ; + +Title.defaultProps = { + Tag: 'h2', + fontSize: '20px', + fontWeight: 600, + fontColor: colors.black, +}; + +Title.displayName = 'Title'; -- cgit v1.2.3 From 3d6ce0fb7618ff7531f234929ead39fafecb1d11 Mon Sep 17 00:00:00 2001 From: fragosti Date: Fri, 15 Jun 2018 13:55:37 -0700 Subject: Make start onboarding button pretty --- packages/website/ts/components/portal/portal.tsx | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) (limited to 'packages') diff --git a/packages/website/ts/components/portal/portal.tsx b/packages/website/ts/components/portal/portal.tsx index 48486939b..aca04969d 100644 --- a/packages/website/ts/components/portal/portal.tsx +++ b/packages/website/ts/components/portal/portal.tsx @@ -4,6 +4,7 @@ import * as _ from 'lodash'; import * as React from 'react'; import * as DocumentTitle from 'react-document-title'; import { Route, RouteComponentProps, Switch } from 'react-router-dom'; +import ActionAccountBalanceWallet from 'material-ui/svg-icons/action/account-balance-wallet'; import { Blockchain } from 'ts/blockchain'; import { BlockchainErrDialog } from 'ts/components/dialogs/blockchain_err_dialog'; @@ -22,6 +23,7 @@ import { TokenBalances } from 'ts/components/token_balances'; import { TopBar, TopBarDisplayType } from 'ts/components/top_bar/top_bar'; import { TradeHistory } from 'ts/components/trade_history/trade_history'; import { Container } from 'ts/components/ui/container'; +import { Text } from 'ts/components/ui/text'; import { FlashMessage } from 'ts/components/ui/flash_message'; import { Island } from 'ts/components/ui/island'; import { Wallet } from 'ts/components/wallet/wallet'; @@ -353,8 +355,21 @@ export class Portal extends React.Component { /> - {/** TODO: Implement real styles. */} -

Start onboarding flow.

+ + + + Learn how to set up your account + +
-- cgit v1.2.3 From 0c3430913332c9f082c4278d8d999dd749d13513 Mon Sep 17 00:00:00 2001 From: fragosti Date: Fri, 15 Jun 2018 14:49:01 -0700 Subject: Make metamask part of the fow pretty --- .../onboarding/portal_onboarding_flow.tsx | 29 +++++++++++++++++++--- packages/website/ts/components/portal/portal.tsx | 11 +++++--- packages/website/ts/components/ui/text.tsx | 2 +- 3 files changed, 35 insertions(+), 7 deletions(-) (limited to 'packages') diff --git a/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx b/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx index 3deefec3c..7e6ce6d02 100644 --- a/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx +++ b/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx @@ -1,9 +1,13 @@ +import { colors } from '@0xproject/react-shared'; import * as _ from 'lodash'; import * as React from 'react'; import { BigNumber } from '@0xproject/utils'; +import ActionAccountBalanceWallet from 'material-ui/svg-icons/action/account-balance-wallet'; import { Blockchain } from 'ts/blockchain'; import { OnboardingFlow, Step } from 'ts/components/onboarding/onboarding_flow'; +import { Container } from 'ts/components/ui/container'; +import { Text } from 'ts/components/ui/text'; import { AllowanceToggle } from 'ts/containers/inputs/allowance_toggle'; import { ProviderType, Token, TokenByAddress, TokenStateByAddress } from 'ts/types'; import { utils } from 'ts/utils/utils'; @@ -48,8 +52,20 @@ export class PortalOnboardingFlow extends React.Component + + + + + Before you begin, you need to connect to a wallet. This will be used across all 0x relayers + and dApps. + +
+ ), placement: 'right', shouldHideBackButton: true, shouldHideNextButton: true, @@ -57,7 +73,14 @@ export class PortalOnboardingFlow extends React.Component + + + + Unlock your metamask extension to begin. +
+ ), placement: 'right', shouldHideBackButton: true, shouldHideNextButton: true, diff --git a/packages/website/ts/components/portal/portal.tsx b/packages/website/ts/components/portal/portal.tsx index aca04969d..d1d499314 100644 --- a/packages/website/ts/components/portal/portal.tsx +++ b/packages/website/ts/components/portal/portal.tsx @@ -1,10 +1,10 @@ import { colors, Styles } from '@0xproject/react-shared'; import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; +import ActionAccountBalanceWallet from 'material-ui/svg-icons/action/account-balance-wallet'; import * as React from 'react'; import * as DocumentTitle from 'react-document-title'; import { Route, RouteComponentProps, Switch } from 'react-router-dom'; -import ActionAccountBalanceWallet from 'material-ui/svg-icons/action/account-balance-wallet'; import { Blockchain } from 'ts/blockchain'; import { BlockchainErrDialog } from 'ts/components/dialogs/blockchain_err_dialog'; @@ -23,9 +23,9 @@ import { TokenBalances } from 'ts/components/token_balances'; import { TopBar, TopBarDisplayType } from 'ts/components/top_bar/top_bar'; import { TradeHistory } from 'ts/components/trade_history/trade_history'; import { Container } from 'ts/components/ui/container'; -import { Text } from 'ts/components/ui/text'; import { FlashMessage } from 'ts/components/ui/flash_message'; import { Island } from 'ts/components/ui/island'; +import { Text } from 'ts/components/ui/text'; import { Wallet } from 'ts/components/wallet/wallet'; import { GenerateOrderForm } from 'ts/containers/generate_order_form'; import { PortalOnboardingFlow } from 'ts/containers/portal_onboarding_flow'; @@ -366,7 +366,12 @@ export class Portal extends React.Component { style={{ width: '30px', height: '30px' }} color={colors.orange} /> - + Learn how to set up your account diff --git a/packages/website/ts/components/ui/text.tsx b/packages/website/ts/components/ui/text.tsx index 073bfc2d2..2bc4a1974 100644 --- a/packages/website/ts/components/ui/text.tsx +++ b/packages/website/ts/components/ui/text.tsx @@ -43,7 +43,7 @@ Text.defaultProps = { fontFamily: 'Roboto', fontWeight: 400, fontColor: colors.black, - fontSize: '14px', + fontSize: '15px', Tag: 'div', }; -- cgit v1.2.3 From 5993125cc7d5129aead37a06663741d42ff0189e Mon Sep 17 00:00:00 2001 From: fragosti Date: Fri, 15 Jun 2018 15:17:20 -0700 Subject: Prettify account setup and add eth steps of onboarding --- .../onboarding/portal_onboarding_flow.tsx | 33 ++++++++++++++++++++-- packages/website/ts/components/ui/container.tsx | 1 + packages/website/ts/components/ui/text.tsx | 1 + 3 files changed, 32 insertions(+), 3 deletions(-) (limited to 'packages') diff --git a/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx b/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx index 7e6ce6d02..11f1becfe 100644 --- a/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx +++ b/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx @@ -88,8 +88,24 @@ export class PortalOnboardingFlow extends React.Component + + In order to start trading on any 0x relayer in the 0x ecosystem, you need to complete two + simple steps. + + +
+ + Wrap ETH +
+
+ + Unlock tokens +
+
+ + ), placement: 'right', shouldHideBackButton: true, continueButtonDisplay: 'enabled', @@ -97,7 +113,18 @@ export class PortalOnboardingFlow extends React.Component + Before you begin you will need to send some ETH to your metamask wallet. + + + + + Click on the metamask + extension in your browser and click either BUY or DEPOSIT. + + + ), placement: 'right', continueButtonDisplay: this._userHasVisibleEth() ? 'enabled' : 'disabled', }, diff --git a/packages/website/ts/components/ui/container.tsx b/packages/website/ts/components/ui/container.tsx index 3b55a18ce..d5665ec5d 100644 --- a/packages/website/ts/components/ui/container.tsx +++ b/packages/website/ts/components/ui/container.tsx @@ -14,6 +14,7 @@ export interface ContainerProps { backgroundColor?: string; borderRadius?: StringOrNum; maxWidth?: StringOrNum; + width?: StringOrNum; isHidden?: boolean; className?: string; position?: 'absolute' | 'fixed' | 'relative' | 'unset'; diff --git a/packages/website/ts/components/ui/text.tsx b/packages/website/ts/components/ui/text.tsx index 2bc4a1974..1e2a123b7 100644 --- a/packages/website/ts/components/ui/text.tsx +++ b/packages/website/ts/components/ui/text.tsx @@ -44,6 +44,7 @@ Text.defaultProps = { fontWeight: 400, fontColor: colors.black, fontSize: '15px', + lineHeight: '1.5em', Tag: 'div', }; -- cgit v1.2.3 From 0cf99271328a0bee0730a33659fe30aa426c59da Mon Sep 17 00:00:00 2001 From: fragosti Date: Fri, 15 Jun 2018 18:02:33 -0700 Subject: Add all steps to their own file --- .../onboarding/add_eth_onboarding_step.tsx | 18 +++++ .../onboarding/install_wallet_onboarding_step.tsx | 18 +++++ .../onboarding/intro_onboarding_step.tsx | 23 ++++++ .../onboarding/portal_onboarding_flow.tsx | 91 +++++++--------------- .../onboarding/unlock_wallet_onboarding_step.tsx | 16 ++++ .../onboarding/wrap_eth_onboarding_step.tsx | 73 +++++++++++++++++ packages/website/ts/components/ui/container.tsx | 1 + packages/website/ts/components/ui/icon_button.tsx | 9 ++- packages/website/ts/components/wallet/wallet.tsx | 6 +- packages/website/ts/utils/constants.ts | 1 + packages/website/ts/utils/utils.ts | 11 +++ 11 files changed, 196 insertions(+), 71 deletions(-) create mode 100644 packages/website/ts/components/onboarding/add_eth_onboarding_step.tsx create mode 100644 packages/website/ts/components/onboarding/install_wallet_onboarding_step.tsx create mode 100644 packages/website/ts/components/onboarding/intro_onboarding_step.tsx create mode 100644 packages/website/ts/components/onboarding/unlock_wallet_onboarding_step.tsx create mode 100644 packages/website/ts/components/onboarding/wrap_eth_onboarding_step.tsx (limited to 'packages') diff --git a/packages/website/ts/components/onboarding/add_eth_onboarding_step.tsx b/packages/website/ts/components/onboarding/add_eth_onboarding_step.tsx new file mode 100644 index 000000000..31ce99d31 --- /dev/null +++ b/packages/website/ts/components/onboarding/add_eth_onboarding_step.tsx @@ -0,0 +1,18 @@ +import * as React from 'react'; +import { Container } from 'ts/components/ui/container'; +import { Text } from 'ts/components/ui/text'; + +export interface AddEthOnboardingStepProps {} + +export const AddEthOnboardingStep: React.StatelessComponent = () => ( +
+ Before you begin you will need to send some ETH to your metamask wallet. + + + + + Click on the metamask extension in your + browser and click either BUY or DEPOSIT. + +
+); diff --git a/packages/website/ts/components/onboarding/install_wallet_onboarding_step.tsx b/packages/website/ts/components/onboarding/install_wallet_onboarding_step.tsx new file mode 100644 index 000000000..a54496186 --- /dev/null +++ b/packages/website/ts/components/onboarding/install_wallet_onboarding_step.tsx @@ -0,0 +1,18 @@ +import { colors } from '@0xproject/react-shared'; +import ActionAccountBalanceWallet from 'material-ui/svg-icons/action/account-balance-wallet'; +import * as React from 'react'; +import { Container } from 'ts/components/ui/container'; +import { Text } from 'ts/components/ui/text'; + +export interface InstallWalletOnboardingStepProps {} + +export const InstallWalletOnboardingStep: React.StatelessComponent = () => ( +
+ + + + + Before you begin, you need to connect to a wallet. This will be used across all 0x relayers and dApps. + +
+); diff --git a/packages/website/ts/components/onboarding/intro_onboarding_step.tsx b/packages/website/ts/components/onboarding/intro_onboarding_step.tsx new file mode 100644 index 000000000..548839218 --- /dev/null +++ b/packages/website/ts/components/onboarding/intro_onboarding_step.tsx @@ -0,0 +1,23 @@ +import * as React from 'react'; +import { Container } from 'ts/components/ui/container'; +import { Text } from 'ts/components/ui/text'; + +export interface IntroOnboardingStepProps {} + +export const IntroOnboardingStep: React.StatelessComponent = () => ( +
+ + In order to start trading on any 0x relayer in the 0x ecosystem, you need to complete two simple steps. + + +
+ + Wrap ETH +
+
+ + Unlock tokens +
+
+
+); diff --git a/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx b/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx index 11f1becfe..11ad88a1f 100644 --- a/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx +++ b/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx @@ -1,13 +1,14 @@ -import { colors } from '@0xproject/react-shared'; import * as _ from 'lodash'; import * as React from 'react'; import { BigNumber } from '@0xproject/utils'; -import ActionAccountBalanceWallet from 'material-ui/svg-icons/action/account-balance-wallet'; import { Blockchain } from 'ts/blockchain'; +import { AddEthOnboardingStep } from 'ts/components/onboarding/add_eth_onboarding_step'; +import { InstallWalletOnboardingStep } from 'ts/components/onboarding/install_wallet_onboarding_step'; +import { IntroOnboardingStep } from 'ts/components/onboarding/intro_onboarding_step'; import { OnboardingFlow, Step } from 'ts/components/onboarding/onboarding_flow'; -import { Container } from 'ts/components/ui/container'; -import { Text } from 'ts/components/ui/text'; +import { UnlockWalletOnboardingStep } from 'ts/components/onboarding/unlock_wallet_onboarding_step'; +import { WrapEthOnboardingStep } from 'ts/components/onboarding/wrap_eth_onboarding_step'; import { AllowanceToggle } from 'ts/containers/inputs/allowance_toggle'; import { ProviderType, Token, TokenByAddress, TokenStateByAddress } from 'ts/types'; import { utils } from 'ts/utils/utils'; @@ -52,20 +53,7 @@ export class PortalOnboardingFlow extends React.Component - - - - - Before you begin, you need to connect to a wallet. This will be used across all 0x relayers - and dApps. - - - ), + content: , placement: 'right', shouldHideBackButton: true, shouldHideNextButton: true, @@ -73,14 +61,7 @@ export class PortalOnboardingFlow extends React.Component - - - - Unlock your metamask extension to begin. - - ), + content: , placement: 'right', shouldHideBackButton: true, shouldHideNextButton: true, @@ -88,24 +69,7 @@ export class PortalOnboardingFlow extends React.Component - - In order to start trading on any 0x relayer in the 0x ecosystem, you need to complete two - simple steps. - - -
- - Wrap ETH -
-
- - Unlock tokens -
-
- - ), + content: , placement: 'right', shouldHideBackButton: true, continueButtonDisplay: 'enabled', @@ -113,27 +77,22 @@ export class PortalOnboardingFlow extends React.Component - Before you begin you will need to send some ETH to your metamask wallet. - - - - - Click on the metamask - extension in your browser and click either BUY or DEPOSIT. - - - ), + content: , placement: 'right', continueButtonDisplay: this._userHasVisibleEth() ? 'enabled' : 'disabled', }, { target: '.weth-row', title: 'Step 1/2', - content: 'You need to convert some of your ETH into tradeable Wrapped ETH (WETH)', + content: ( + + ), placement: 'right', - continueButtonDisplay: this._userHasVisibleWeth() ? 'enabled' : 'disabled', + continueButtonDisplay: this._userHasVisibleWeth() ? 'enabled' : undefined, }, { target: '.weth-row', @@ -165,13 +124,21 @@ export class PortalOnboardingFlow extends React.Component new BigNumber(0); } - private _userHasVisibleWeth(): boolean { + private _getWethBalance(): BigNumber { const ethToken = utils.getEthToken(this.props.tokenByAddress); if (!ethToken) { - return false; + return new BigNumber(0); } - const wethTokenState = this.props.trackedTokenStateByAddress[ethToken.address]; - return wethTokenState.balance > new BigNumber(0); + const ethTokenState = this.props.trackedTokenStateByAddress[ethToken.address]; + return ethTokenState.balance; + } + private _getFormattedWethBalance(): string { + const ethToken = utils.getEthToken(this.props.tokenByAddress); + const ethTokenState = this.props.trackedTokenStateByAddress[ethToken.address]; + return utils.getFormattedAmountFromToken(ethToken, ethTokenState); + } + private _userHasVisibleWeth(): boolean { + return this._getWethBalance() > new BigNumber(0); } private _userHasAllowancesForWethAndZrx(): boolean { const ethToken = utils.getEthToken(this.props.tokenByAddress); diff --git a/packages/website/ts/components/onboarding/unlock_wallet_onboarding_step.tsx b/packages/website/ts/components/onboarding/unlock_wallet_onboarding_step.tsx new file mode 100644 index 000000000..6e6a74a06 --- /dev/null +++ b/packages/website/ts/components/onboarding/unlock_wallet_onboarding_step.tsx @@ -0,0 +1,16 @@ +import * as React from 'react'; +import { Container } from 'ts/components/ui/container'; +import { Text } from 'ts/components/ui/text'; + +export interface UnlockWalletOnboardingStepProps {} + +export const UnlockWalletOnboardingStep: React.StatelessComponent = () => ( +
+
+ + + + Unlock your metamask extension to begin. +
+
+); diff --git a/packages/website/ts/components/onboarding/wrap_eth_onboarding_step.tsx b/packages/website/ts/components/onboarding/wrap_eth_onboarding_step.tsx new file mode 100644 index 000000000..b21b39341 --- /dev/null +++ b/packages/website/ts/components/onboarding/wrap_eth_onboarding_step.tsx @@ -0,0 +1,73 @@ +import { colors } from '@0xproject/react-shared'; +import * as React from 'react'; +import { Container } from 'ts/components/ui/container'; +import { IconButton } from 'ts/components/ui/icon_button'; +import { Text } from 'ts/components/ui/text'; + +export interface WrapEthOnboardingStepProps { + formattedEthBalanceIfExists?: string; +} + +export const WrapEthOnboardingStep: React.StatelessComponent = ({ + formattedEthBalanceIfExists, +}) => { + if (formattedEthBalanceIfExists) { + return ( +
+ Congrats you now have {formattedEthBalanceIfExists} in your wallet. + +
+ 1 ETH + +
+ + + + + +
+ 1 WETH + +
+
+
+ ); + } else { + return ( +
+ + You need to convert some of your ETH into tradeable Wrapped ETH (WETH). + + +
+ 1 ETH + +
+ + = + +
+ 1 WETH + +
+
+ + Think of it like the coin version of a paper note. It has the same value, but some machines only + take coins. + + + Click + + + + to wrap your ETH. + +
+ ); + } +}; diff --git a/packages/website/ts/components/ui/container.tsx b/packages/website/ts/components/ui/container.tsx index d5665ec5d..1776345da 100644 --- a/packages/website/ts/components/ui/container.tsx +++ b/packages/website/ts/components/ui/container.tsx @@ -18,6 +18,7 @@ export interface ContainerProps { isHidden?: boolean; className?: string; position?: 'absolute' | 'fixed' | 'relative' | 'unset'; + display?: 'inline-block' | 'block' | 'inline-flex' | 'inline'; top?: string; left?: string; right?: string; diff --git a/packages/website/ts/components/ui/icon_button.tsx b/packages/website/ts/components/ui/icon_button.tsx index 2f5172f05..13cd239da 100644 --- a/packages/website/ts/components/ui/icon_button.tsx +++ b/packages/website/ts/components/ui/icon_button.tsx @@ -5,15 +5,15 @@ import * as React from 'react'; export interface IconButtonProps { iconName: string; labelText?: string; - onClick: () => void; + onClick?: () => void; color?: string; + display?: string; } interface IconButtonState { isHovering: boolean; } export class IconButton extends React.Component { public static defaultProps: Partial = { - onClick: _.noop, labelText: '', color: colors.mediumBlue, }; @@ -26,8 +26,9 @@ export class IconButton extends React.Component { ); } else { - const unitAmount = Web3Wrapper.toUnitAmount(amount, decimals); - const precision = Math.min(TOKEN_AMOUNT_DISPLAY_PRECISION, unitAmount.decimalPlaces()); - const formattedAmount = unitAmount.toFixed(precision); - const result = `${formattedAmount} ${symbol}`; + const result = utils.getFormattedAmount(amount, decimals, symbol); return
{result}
; } } diff --git a/packages/website/ts/utils/constants.ts b/packages/website/ts/utils/constants.ts index d281c5738..25670ef27 100644 --- a/packages/website/ts/utils/constants.ts +++ b/packages/website/ts/utils/constants.ts @@ -6,6 +6,7 @@ export const constants = { ETHER_TOKEN_SYMBOL: 'WETH', ZRX_TOKEN_SYMBOL: 'ZRX', ETHER_SYMBOL: 'ETH', + TOKEN_AMOUNT_DISPLAY_PRECISION: 5, GENESIS_ORDER_BLOCK_BY_NETWORK_ID: { 1: 4145578, 42: 3117574, diff --git a/packages/website/ts/utils/utils.ts b/packages/website/ts/utils/utils.ts index fdee264b2..414361c1b 100644 --- a/packages/website/ts/utils/utils.ts +++ b/packages/website/ts/utils/utils.ts @@ -3,6 +3,7 @@ import { OrderError } from '@0xproject/order-utils'; import { constants as sharedConstants, Networks } from '@0xproject/react-shared'; import { ECSignature, Provider } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; +import { Web3Wrapper } from '@0xproject/web3-wrapper'; import deepEqual = require('deep-equal'); import * as _ from 'lodash'; import * as moment from 'moment'; @@ -17,6 +18,7 @@ import { SideToAssetToken, Token, TokenByAddress, + TokenState, } from 'ts/types'; import { configs } from 'ts/utils/configs'; import { constants } from 'ts/utils/constants'; @@ -332,4 +334,13 @@ export const utils = { const token = _.find(tokens, { symbol }); return token; }, + getFormattedAmountFromToken(token: Token, tokenState: TokenState): string { + return utils.getFormattedAmount(tokenState.balance, token.decimals, token.symbol); + }, + getFormattedAmount(amount: BigNumber, decimals: number, symbol: string): string { + const unitAmount = Web3Wrapper.toUnitAmount(amount, decimals); + const precision = Math.min(constants.TOKEN_AMOUNT_DISPLAY_PRECISION, unitAmount.decimalPlaces()); + const formattedAmount = unitAmount.toFixed(precision); + return `${formattedAmount} ${symbol}`; + }, }; -- cgit v1.2.3 From 8893bc102c7dc4faf1650180b0b1acddc333cd7a Mon Sep 17 00:00:00 2001 From: fragosti Date: Fri, 15 Jun 2018 18:03:10 -0700 Subject: Add onboarding assets --- packages/website/public/.DS_Store | Bin 0 -> 10244 bytes packages/website/public/images/.DS_Store | Bin 0 -> 8196 bytes packages/website/public/images/eth_dollar.svg | 29 +++++++++++++++++++++ packages/website/public/images/eth_token.svg | 20 ++++++++++++++ packages/website/public/images/eth_token_erc20.svg | 22 ++++++++++++++++ packages/website/public/images/ether_alt.svg | 7 +++++ packages/website/public/images/fake_toggle.svg | 4 +++ 7 files changed, 82 insertions(+) create mode 100644 packages/website/public/.DS_Store create mode 100644 packages/website/public/images/.DS_Store create mode 100644 packages/website/public/images/eth_dollar.svg create mode 100644 packages/website/public/images/eth_token.svg create mode 100644 packages/website/public/images/eth_token_erc20.svg create mode 100644 packages/website/public/images/ether_alt.svg create mode 100644 packages/website/public/images/fake_toggle.svg (limited to 'packages') diff --git a/packages/website/public/.DS_Store b/packages/website/public/.DS_Store new file mode 100644 index 000000000..bcd5b6d3e Binary files /dev/null and b/packages/website/public/.DS_Store differ diff --git a/packages/website/public/images/.DS_Store b/packages/website/public/images/.DS_Store new file mode 100644 index 000000000..56b44f004 Binary files /dev/null and b/packages/website/public/images/.DS_Store differ diff --git a/packages/website/public/images/eth_dollar.svg b/packages/website/public/images/eth_dollar.svg new file mode 100644 index 000000000..0afec94fa --- /dev/null +++ b/packages/website/public/images/eth_dollar.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/website/public/images/eth_token.svg b/packages/website/public/images/eth_token.svg new file mode 100644 index 000000000..9392692f9 --- /dev/null +++ b/packages/website/public/images/eth_token.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/packages/website/public/images/eth_token_erc20.svg b/packages/website/public/images/eth_token_erc20.svg new file mode 100644 index 000000000..6313c826d --- /dev/null +++ b/packages/website/public/images/eth_token_erc20.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/website/public/images/ether_alt.svg b/packages/website/public/images/ether_alt.svg new file mode 100644 index 000000000..82199d14d --- /dev/null +++ b/packages/website/public/images/ether_alt.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/packages/website/public/images/fake_toggle.svg b/packages/website/public/images/fake_toggle.svg new file mode 100644 index 000000000..c9632d0da --- /dev/null +++ b/packages/website/public/images/fake_toggle.svg @@ -0,0 +1,4 @@ + + + + -- cgit v1.2.3 From 433f830cf33f6383e83cca5e08b1f58bc18a0828 Mon Sep 17 00:00:00 2001 From: fragosti Date: Fri, 15 Jun 2018 18:15:03 -0700 Subject: Finish set allowance step --- .../onboarding/portal_onboarding_flow.tsx | 10 ++++---- .../onboarding/set_allowances_onboarding_step.tsx | 27 ++++++++++++++++++++++ 2 files changed, 32 insertions(+), 5 deletions(-) create mode 100644 packages/website/ts/components/onboarding/set_allowances_onboarding_step.tsx (limited to 'packages') diff --git a/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx b/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx index 11ad88a1f..ad2ada93b 100644 --- a/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx +++ b/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx @@ -7,6 +7,7 @@ import { AddEthOnboardingStep } from 'ts/components/onboarding/add_eth_onboardin import { InstallWalletOnboardingStep } from 'ts/components/onboarding/install_wallet_onboarding_step'; import { IntroOnboardingStep } from 'ts/components/onboarding/intro_onboarding_step'; import { OnboardingFlow, Step } from 'ts/components/onboarding/onboarding_flow'; +import { SetAllowancesOnboardingStep } from 'ts/components/onboarding/set_allowances_onboarding_step'; import { UnlockWalletOnboardingStep } from 'ts/components/onboarding/unlock_wallet_onboarding_step'; import { WrapEthOnboardingStep } from 'ts/components/onboarding/wrap_eth_onboarding_step'; import { AllowanceToggle } from 'ts/containers/inputs/allowance_toggle'; @@ -98,11 +99,10 @@ export class PortalOnboardingFlow extends React.Component - Unlock your tokens for trading. You only need to do this once for each token. -
ETH: {this._renderEthAllowanceToggle()}
-
ZRX: {this._renderZrxAllowanceToggle()}
- + ), placement: 'right', continueButtonDisplay: this._userHasAllowancesForWethAndZrx() ? 'enabled' : 'disabled', diff --git a/packages/website/ts/components/onboarding/set_allowances_onboarding_step.tsx b/packages/website/ts/components/onboarding/set_allowances_onboarding_step.tsx new file mode 100644 index 000000000..1ff248c40 --- /dev/null +++ b/packages/website/ts/components/onboarding/set_allowances_onboarding_step.tsx @@ -0,0 +1,27 @@ +import * as React from 'react'; +import { Container } from 'ts/components/ui/container'; +import { Text } from 'ts/components/ui/text'; + +export interface SetAllowancesOnboardingStepProps { + zrxAllowanceToggle: React.ReactNode; + ethAllowanceToggle: React.ReactNode; +} + +export const SetAllowancesOnboardingStep: React.StatelessComponent = ({ + ethAllowanceToggle, + zrxAllowanceToggle, +}) => ( +
+ Unlock your tokens for trading. You only need to do this once for each token. + +
+ Enable WETH + {ethAllowanceToggle} +
+
+ Enable ZRX + {zrxAllowanceToggle} +
+
+
+); -- cgit v1.2.3 From 8a76fdc126c4e8d8b388d9e50bbc7fb8c7862186 Mon Sep 17 00:00:00 2001 From: fragosti Date: Fri, 15 Jun 2018 18:27:23 -0700 Subject: Finish last onboarding step --- packages/website/public/.DS_Store | Bin 10244 -> 0 bytes packages/website/public/images/.DS_Store | Bin 8196 -> 0 bytes packages/website/public/images/zrx_ecosystem.svg | 253 +++++++++++++++++++++ .../onboarding/congrats_onboarding_step.tsx | 15 ++ .../ts/components/onboarding/onboarding_flow.tsx | 2 + .../components/onboarding/onboarding_tooltip.tsx | 5 +- .../onboarding/portal_onboarding_flow.tsx | 4 +- 7 files changed, 277 insertions(+), 2 deletions(-) delete mode 100644 packages/website/public/.DS_Store delete mode 100644 packages/website/public/images/.DS_Store create mode 100644 packages/website/public/images/zrx_ecosystem.svg create mode 100644 packages/website/ts/components/onboarding/congrats_onboarding_step.tsx (limited to 'packages') diff --git a/packages/website/public/.DS_Store b/packages/website/public/.DS_Store deleted file mode 100644 index bcd5b6d3e..000000000 Binary files a/packages/website/public/.DS_Store and /dev/null differ diff --git a/packages/website/public/images/.DS_Store b/packages/website/public/images/.DS_Store deleted file mode 100644 index 56b44f004..000000000 Binary files a/packages/website/public/images/.DS_Store and /dev/null differ diff --git a/packages/website/public/images/zrx_ecosystem.svg b/packages/website/public/images/zrx_ecosystem.svg new file mode 100644 index 000000000..f8aed4637 --- /dev/null +++ b/packages/website/public/images/zrx_ecosystem.svg @@ -0,0 +1,253 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/website/ts/components/onboarding/congrats_onboarding_step.tsx b/packages/website/ts/components/onboarding/congrats_onboarding_step.tsx new file mode 100644 index 000000000..3a8db8c36 --- /dev/null +++ b/packages/website/ts/components/onboarding/congrats_onboarding_step.tsx @@ -0,0 +1,15 @@ +import * as React from 'react'; +import { Container } from 'ts/components/ui/container'; +import { Text } from 'ts/components/ui/text'; + +export interface CongratsOnboardingStepProps {} + +export const CongratsOnboardingStep: React.StatelessComponent = () => ( +
+ Your wallet is now set up for trading. Use it on any relayer in the 0x ecosystem. + + + + No need to log in. Each relayer automatically detects and connects to your metamask wallet. +
+); diff --git a/packages/website/ts/components/onboarding/onboarding_flow.tsx b/packages/website/ts/components/onboarding/onboarding_flow.tsx index 7a5a6e40f..34aeace82 100644 --- a/packages/website/ts/components/onboarding/onboarding_flow.tsx +++ b/packages/website/ts/components/onboarding/onboarding_flow.tsx @@ -13,6 +13,7 @@ export interface Step { shouldHideBackButton?: boolean; shouldHideNextButton?: boolean; continueButtonDisplay?: ContinueButtonDisplay; + continueButtonText?: string; } export interface OnboardingFlowProps { @@ -65,6 +66,7 @@ export class OnboardingFlow extends React.Component { onClickNext={this._goToNextStep.bind(this)} onClickBack={this._goToPrevStep.bind(this)} continueButtonDisplay={step.continueButtonDisplay} + continueButtonText={step.continueButtonText} /> ); diff --git a/packages/website/ts/components/onboarding/onboarding_tooltip.tsx b/packages/website/ts/components/onboarding/onboarding_tooltip.tsx index 0858ad326..45851b4de 100644 --- a/packages/website/ts/components/onboarding/onboarding_tooltip.tsx +++ b/packages/website/ts/components/onboarding/onboarding_tooltip.tsx @@ -21,6 +21,7 @@ export interface OnboardingTooltipProps { shouldHideBackButton?: boolean; shouldHideNextButton?: boolean; pointerDirection?: PointerDirection; + continueButtonText?: string; className?: string; } @@ -28,6 +29,7 @@ export const OnboardingTooltip: React.StatelessComponent title, content, continueButtonDisplay, + continueButtonText, onClickNext, onClickBack, onClose, @@ -59,7 +61,7 @@ export const OnboardingTooltip: React.StatelessComponent fontSize="15px" backgroundColor={colors.mediumBlue} > - Continue + {continueButtonText} )} @@ -82,6 +84,7 @@ export const OnboardingTooltip: React.StatelessComponent OnboardingTooltip.defaultProps = { pointerDirection: 'left', + continueButtonText: 'Continue', }; OnboardingTooltip.displayName = 'OnboardingTooltip'; diff --git a/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx b/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx index ad2ada93b..4283022e2 100644 --- a/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx +++ b/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx @@ -4,6 +4,7 @@ import * as React from 'react'; import { BigNumber } from '@0xproject/utils'; import { Blockchain } from 'ts/blockchain'; import { AddEthOnboardingStep } from 'ts/components/onboarding/add_eth_onboarding_step'; +import { CongratsOnboardingStep } from 'ts/components/onboarding/congrats_onboarding_step'; import { InstallWalletOnboardingStep } from 'ts/components/onboarding/install_wallet_onboarding_step'; import { IntroOnboardingStep } from 'ts/components/onboarding/intro_onboarding_step'; import { OnboardingFlow, Step } from 'ts/components/onboarding/onboarding_flow'; @@ -110,10 +111,11 @@ export class PortalOnboardingFlow extends React.Component, placement: 'right', continueButtonDisplay: 'enabled', shouldHideNextButton: true, + continueButtonText: 'Enter the 0x Ecosystem', }, ]; return steps; -- cgit v1.2.3 From 888086010579566aca57e63daa5bacc9c6802830 Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Thu, 14 Jun 2018 13:51:02 -0700 Subject: Set max-width for LargeLayout --- packages/website/ts/components/portal/portal.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'packages') diff --git a/packages/website/ts/components/portal/portal.tsx b/packages/website/ts/components/portal/portal.tsx index 48486939b..d51e40216 100644 --- a/packages/website/ts/components/portal/portal.tsx +++ b/packages/website/ts/components/portal/portal.tsx @@ -100,6 +100,7 @@ const THROTTLE_TIMEOUT = 100; const TOP_BAR_HEIGHT = TopBar.heightForDisplayType(TopBarDisplayType.Expanded); const LEFT_COLUMN_WIDTH = 346; const MENU_PADDING_LEFT = 185; +const LARGE_LAYOUT_MAX_WIDTH = 1200; const styles: Styles = { root: { @@ -660,11 +661,11 @@ interface LargeLayoutProps { } const LargeLayout = (props: LargeLayoutProps) => { return ( -
-
+
+
{props.left}
-
+
{props.right}
@@ -676,7 +677,7 @@ interface SmallLayoutProps { } const SmallLayout = (props: SmallLayoutProps) => { return ( -
+
{props.content}
-- cgit v1.2.3 From 55cbcd728d9b0611e604a389443f94de58726d92 Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Thu, 14 Jun 2018 14:12:05 -0700 Subject: Add max width to top bar --- packages/website/ts/components/portal/portal.tsx | 1 + packages/website/ts/components/top_bar/top_bar.tsx | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/website/ts/components/portal/portal.tsx b/packages/website/ts/components/portal/portal.tsx index d51e40216..ee8c90c2f 100644 --- a/packages/website/ts/components/portal/portal.tsx +++ b/packages/website/ts/components/portal/portal.tsx @@ -255,6 +255,7 @@ export class Portal extends React.Component { translate={this.props.translate} displayType={TopBarDisplayType.Expanded} style={{ backgroundColor: colors.lightestGrey }} + maxWidth={LARGE_LAYOUT_MAX_WIDTH} />
diff --git a/packages/website/ts/components/top_bar/top_bar.tsx b/packages/website/ts/components/top_bar/top_bar.tsx index 05cc6e3ad..1a69827a4 100644 --- a/packages/website/ts/components/top_bar/top_bar.tsx +++ b/packages/website/ts/components/top_bar/top_bar.tsx @@ -45,6 +45,7 @@ export interface TopBarProps { isNightVersion?: boolean; onVersionSelected?: (semver: string) => void; sidebarHeader?: React.ReactNode; + maxWidth?: number; } interface TopBarState { @@ -213,7 +214,7 @@ export class TopBar extends React.Component { const shouldShowPortalV2Drawer = this._isViewingPortal() && utils.shouldShowPortalV2(); return (
-
+
-- cgit v1.2.3 From 2ad411ea290697fc33786f9a0daa32116d51bd09 Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Thu, 14 Jun 2018 15:04:31 -0700 Subject: Update RelayerGridTile render logic to incorportate colors and logos --- .../components/relayer_index/relayer_grid_tile.tsx | 32 ++++++++++++++-------- .../ts/components/relayer_index/relayer_index.tsx | 4 +-- packages/website/ts/types.ts | 2 ++ 3 files changed, 24 insertions(+), 14 deletions(-) (limited to 'packages') diff --git a/packages/website/ts/components/relayer_index/relayer_grid_tile.tsx b/packages/website/ts/components/relayer_index/relayer_grid_tile.tsx index fbb634164..0b0e75312 100644 --- a/packages/website/ts/components/relayer_index/relayer_grid_tile.tsx +++ b/packages/website/ts/components/relayer_index/relayer_grid_tile.tsx @@ -26,7 +26,6 @@ const styles: Styles = { header: { height: '50%', width: '100%', - objectFit: 'cover', borderBottomRightRadius: 4, borderBottomLeftRadius: 4, borderTopRightRadius: 4, @@ -58,21 +57,26 @@ const styles: Styles = { }; const FALLBACK_IMG_SRC = '/images/landing/hero_chip_image.png'; +const FALLBACK_PRIMARY_COLOR = colors.grey200; const NO_CONTENT_MESSAGE = '--'; +const RELAYER_ICON_HEIGHT = '110px'; export const RelayerGridTile: React.StatelessComponent = (props: RelayerGridTileProps) => { const link = props.relayerInfo.appUrl || props.relayerInfo.url; const topTokens = props.relayerInfo.topTokens; const weeklyTxnVolume = props.relayerInfo.weeklyTxnVolume; + const headerImageUrl = props.relayerInfo.logoImgUrl; + const headerBackgroundColor = + !_.isUndefined(headerImageUrl) && !_.isUndefined(props.relayerInfo.primaryColor) + ? props.relayerInfo.primaryColor + : FALLBACK_PRIMARY_COLOR; return (
- +
+ +
@@ -112,7 +116,6 @@ const NoContent = () =>
{NO_CONTENT_MESSAGE}
; interface ImgWithFallbackProps { src?: string; fallbackSrc: string; - style: React.CSSProperties; } interface ImgWithFallbackState { imageLoadFailed: boolean; @@ -125,11 +128,16 @@ class ImgWithFallback extends React.Component; - } else { - return ; - } + return ( +
+ {this._renderImg()} +
+ ); + } + private _renderImg(): React.ReactNode { + const src = + this.state.imageLoadFailed || _.isUndefined(this.props.src) ? this.props.fallbackSrc : this.props.src; + return ; } private _onError(): void { this.setState({ diff --git a/packages/website/ts/components/relayer_index/relayer_index.tsx b/packages/website/ts/components/relayer_index/relayer_index.tsx index 3c5761bcd..8dd4f0fbf 100644 --- a/packages/website/ts/components/relayer_index/relayer_index.tsx +++ b/packages/website/ts/components/relayer_index/relayer_index.tsx @@ -37,8 +37,8 @@ const styles: Styles = { }; const CELL_HEIGHT = 290; -const NUMBER_OF_COLUMNS_LARGE = 4; -const NUMBER_OF_COLUMNS_MEDIUM = 3; +const NUMBER_OF_COLUMNS_LARGE = 3; +const NUMBER_OF_COLUMNS_MEDIUM = 2; const NUMBER_OF_COLUMNS_SMALL = 1; const GRID_PADDING = 20; diff --git a/packages/website/ts/types.ts b/packages/website/ts/types.ts index 24e86a901..d00154652 100644 --- a/packages/website/ts/types.ts +++ b/packages/website/ts/types.ts @@ -519,6 +519,8 @@ export interface WebsiteBackendRelayerInfo { url: string; appUrl?: string; headerImgUrl?: string; + logoImgUrl?: string; + primaryColor?: string; topTokens: WebsiteBackendTokenInfo[]; } -- cgit v1.2.3 From ff95da411b8f7bb791e2ecfc57d2ba03dc591a52 Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Mon, 4 Jun 2018 13:54:56 -0700 Subject: Split transfer impl and AssetProxyMixin --- .../current/protocol/AssetProxy/ERC20Proxy.sol | 59 +--------------- .../current/protocol/AssetProxy/ERC721Proxy.sol | 42 +----------- .../protocol/AssetProxy/MixinERC20Transfer.sol | 71 +++++++++++++++++++ .../protocol/AssetProxy/MixinERC721Transfer.sol | 79 ++++++++++++++++++++++ 4 files changed, 156 insertions(+), 95 deletions(-) create mode 100644 packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC20Transfer.sol create mode 100644 packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC721Transfer.sol (limited to 'packages') diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC20Proxy.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC20Proxy.sol index eeddb9707..7ca823d1f 100644 --- a/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC20Proxy.sol +++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC20Proxy.sol @@ -22,69 +22,16 @@ pragma experimental ABIEncoderV2; import "../../utils/LibBytes/LibBytes.sol"; import "./MixinAssetProxy.sol"; import "./MixinAuthorizable.sol"; -import "../../tokens/ERC20Token/IERC20Token.sol"; +import "./MixinERC20Transfer.sol"; contract ERC20Proxy is - LibBytes, MixinAssetProxy, - MixinAuthorizable + MixinAuthorizable, + MixinERC20Transfer { - // Id of this proxy. uint8 constant PROXY_ID = 1; - /// @dev Internal version of `transferFrom`. - /// @param assetData Encoded byte array. - /// @param from Address to transfer asset from. - /// @param to Address to transfer asset to. - /// @param amount Amount of asset to transfer. - function transferFromInternal( - bytes memory assetData, - address from, - address to, - uint256 amount - ) - internal - { - // Decode asset data. - address token = readAddress(assetData, 0); - - // Transfer tokens. - // We do a raw call so we can check the success separate - // from the return data. - bool success = token.call(abi.encodeWithSelector( - IERC20Token(token).transferFrom.selector, - from, - to, - amount - )); - require( - success, - TRANSFER_FAILED - ); - - // Check return data. - // If there is no return data, we assume the token incorrectly - // does not return a bool. In this case we expect it to revert - // on failure, which was handled above. - // If the token does return data, we require that it is a single - // value that evaluates to true. - assembly { - if returndatasize { - success := 0 - if eq(returndatasize, 32) { - // First 64 bytes of memory are reserved scratch space - returndatacopy(0, 0, 32) - success := mload(0) - } - } - } - require( - success, - TRANSFER_FAILED - ); - } - /// @dev Gets the proxy id associated with the proxy address. /// @return Proxy id. function getProxyId() diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC721Proxy.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC721Proxy.sol index 861fac2c1..2b280add4 100644 --- a/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC721Proxy.sol +++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC721Proxy.sol @@ -22,52 +22,16 @@ pragma experimental ABIEncoderV2; import "../../utils/LibBytes/LibBytes.sol"; import "./MixinAssetProxy.sol"; import "./MixinAuthorizable.sol"; -import "../../tokens/ERC721Token/ERC721Token.sol"; +import "./MixinERC721Transfer.sol"; contract ERC721Proxy is - LibBytes, MixinAssetProxy, - MixinAuthorizable + MixinAuthorizable, + MixinERC721Transfer { - // Id of this proxy. uint8 constant PROXY_ID = 2; - /// @dev Internal version of `transferFrom`. - /// @param assetData Encoded byte array. - /// @param from Address to transfer asset from. - /// @param to Address to transfer asset to. - /// @param amount Amount of asset to transfer. - function transferFromInternal( - bytes memory assetData, - address from, - address to, - uint256 amount - ) - internal - { - // There exists only 1 of each token. - require( - amount == 1, - INVALID_AMOUNT - ); - - // Decode asset data. - ( - address token, - uint256 tokenId, - bytes memory receiverData - ) = decodeERC721AssetData(assetData); - - // Transfer token. Saves gas by calling safeTransferFrom only - // when there is receiverData present. Either succeeds or throws. - if (receiverData.length > 0) { - ERC721Token(token).safeTransferFrom(from, to, tokenId, receiverData); - } else { - ERC721Token(token).transferFrom(from, to, tokenId); - } - } - /// @dev Gets the proxy id associated with the proxy address. /// @return Proxy id. function getProxyId() diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC20Transfer.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC20Transfer.sol new file mode 100644 index 000000000..b0c32db59 --- /dev/null +++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC20Transfer.sol @@ -0,0 +1,71 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.4.24; +pragma experimental ABIEncoderV2; + +import "../../utils/LibBytes/LibBytes.sol"; +import "../../tokens/ERC20Token/IERC20Token.sol"; + +contract MixinERC20Transfer is + LibBytes +{ + // Id of this proxy. + uint8 constant PROXY_ID = 1; + // Revert reasons + string constant INVALID_METADATA_LENGTH = "Metadata must have a length of 21."; + string constant TRANSFER_FAILED = "Transfer failed."; + string constant PROXY_ID_MISMATCH = "Proxy id in metadata does not match this proxy id."; + + /// @dev Internal version of `transferFrom`. + /// @param assetMetadata Encoded byte array. + /// @param from Address to transfer asset from. + /// @param to Address to transfer asset to. + /// @param amount Amount of asset to transfer. + function transferFromInternal( + bytes memory assetMetadata, + address from, + address to, + uint256 amount + ) + internal + { + // Data must be intended for this proxy. + uint256 length = assetMetadata.length; + + require( + length == 21, + INVALID_METADATA_LENGTH + ); + + require( + uint8(assetMetadata[length - 1]) == PROXY_ID, + PROXY_ID_MISMATCH + ); + + // Decode metadata. + address token = readAddress(assetMetadata, 0); + + // Transfer tokens. + bool success = IERC20Token(token).transferFrom(from, to, amount); + require( + success == true, + TRANSFER_FAILED + ); + } +} diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC721Transfer.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC721Transfer.sol new file mode 100644 index 000000000..bbb807956 --- /dev/null +++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC721Transfer.sol @@ -0,0 +1,79 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.4.24; +pragma experimental ABIEncoderV2; + +import "../../utils/LibBytes/LibBytes.sol"; +import "../../tokens/ERC721Token/ERC721Token.sol"; + +contract MixinERC721Transfer is + LibBytes +{ + + // Id of this proxy. + uint8 constant PROXY_ID = 2; + + // Revert reasons + string constant INVALID_TRANSFER_AMOUNT = "Transfer amount must equal 1."; + string constant INVALID_METADATA_LENGTH = "Metadata must have a length of 53."; + string constant PROXY_ID_MISMATCH = "Proxy id in metadata does not match this proxy id."; + + /// @dev Internal version of `transferFrom`. + /// @param assetMetadata Encoded byte array. + /// @param from Address to transfer asset from. + /// @param to Address to transfer asset to. + /// @param amount Amount of asset to transfer. + function transferFromInternal( + bytes memory assetMetadata, + address from, + address to, + uint256 amount + ) + internal + { + // Data must be intended for this proxy. + uint256 length = assetMetadata.length; + + require( + length == 53, + INVALID_METADATA_LENGTH + ); + + require( + uint8(assetMetadata[length - 1]) == PROXY_ID, + PROXY_ID_MISMATCH + ); + + // There exists only 1 of each token. + require( + amount == 1, + INVALID_TRANSFER_AMOUNT + ); + + // Decode metadata + address token = readAddress(assetMetadata, 0); + uint256 tokenId = readUint256(assetMetadata, 20); + + // Transfer token. + // Either succeeds or throws. + // @TODO: Call safeTransferFrom if there is additional + // data stored in `assetMetadata`. + ERC721Token(token).transferFrom(from, to, tokenId); + } +} -- cgit v1.2.3 From 96c90e6295315802aaad1b80e5a31e4cef886675 Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Mon, 18 Jun 2018 16:19:45 +1000 Subject: Rebase with latest removing PROXY_ID from transfer --- .../current/protocol/AssetProxy/ERC721Proxy.sol | 30 -------- .../protocol/AssetProxy/MixinERC20Transfer.sol | 62 +++++++++------- .../protocol/AssetProxy/MixinERC721Transfer.sol | 85 +++++++++++++--------- .../AssetProxy/libs/LibAssetProxyErrors.sol | 4 - .../protocol/AssetProxy/libs/LibTransferErrors.sol | 25 +++++++ 5 files changed, 111 insertions(+), 95 deletions(-) create mode 100644 packages/contracts/src/contracts/current/protocol/AssetProxy/libs/LibTransferErrors.sol (limited to 'packages') diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC721Proxy.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC721Proxy.sol index 2b280add4..7ff25aea3 100644 --- a/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC721Proxy.sol +++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC721Proxy.sol @@ -41,34 +41,4 @@ contract ERC721Proxy is { return PROXY_ID; } - - /// @dev Decodes ERC721 Asset data. - /// @param assetData Encoded byte array. - /// @return proxyId Intended ERC721 proxy id. - /// @return token ERC721 token address. - /// @return tokenId ERC721 token id. - /// @return receiverData Additional data with no specific format, which - /// is passed to the receiving contract's onERC721Received. - function decodeERC721AssetData(bytes memory assetData) - internal - pure - returns ( - address token, - uint256 tokenId, - bytes memory receiverData - ) - { - // Decode asset data. - token = readAddress(assetData, 0); - tokenId = readUint256(assetData, 20); - if (assetData.length > 52) { - receiverData = readBytes(assetData, 52); - } - - return ( - token, - tokenId, - receiverData - ); - } } diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC20Transfer.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC20Transfer.sol index b0c32db59..4af39a00b 100644 --- a/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC20Transfer.sol +++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC20Transfer.sol @@ -21,50 +21,60 @@ pragma experimental ABIEncoderV2; import "../../utils/LibBytes/LibBytes.sol"; import "../../tokens/ERC20Token/IERC20Token.sol"; +import "./libs/LibTransferErrors.sol"; contract MixinERC20Transfer is - LibBytes + LibBytes, + LibTransferErrors { - // Id of this proxy. - uint8 constant PROXY_ID = 1; - // Revert reasons - string constant INVALID_METADATA_LENGTH = "Metadata must have a length of 21."; - string constant TRANSFER_FAILED = "Transfer failed."; - string constant PROXY_ID_MISMATCH = "Proxy id in metadata does not match this proxy id."; - /// @dev Internal version of `transferFrom`. - /// @param assetMetadata Encoded byte array. + /// @param assetData Encoded byte array. /// @param from Address to transfer asset from. /// @param to Address to transfer asset to. /// @param amount Amount of asset to transfer. function transferFromInternal( - bytes memory assetMetadata, + bytes memory assetData, address from, address to, uint256 amount ) internal { - // Data must be intended for this proxy. - uint256 length = assetMetadata.length; - - require( - length == 21, - INVALID_METADATA_LENGTH - ); + // Decode asset data. + address token = readAddress(assetData, 0); + // Transfer tokens. + // We do a raw call so we can check the success separate + // from the return data. + bool success = token.call(abi.encodeWithSelector( + IERC20Token(token).transferFrom.selector, + from, + to, + amount + )); require( - uint8(assetMetadata[length - 1]) == PROXY_ID, - PROXY_ID_MISMATCH + success, + TRANSFER_FAILED ); - - // Decode metadata. - address token = readAddress(assetMetadata, 0); - - // Transfer tokens. - bool success = IERC20Token(token).transferFrom(from, to, amount); + + // Check return data. + // If there is no return data, we assume the token incorrectly + // does not return a bool. In this case we expect it to revert + // on failure, which was handled above. + // If the token does return data, we require that it is a single + // value that evaluates to true. + assembly { + if returndatasize { + success := 0 + if eq(returndatasize, 32) { + // First 64 bytes of memory are reserved scratch space + returndatacopy(0, 0, 32) + success := mload(0) + } + } + } require( - success == true, + success, TRANSFER_FAILED ); } diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC721Transfer.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC721Transfer.sol index bbb807956..d09aba599 100644 --- a/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC721Transfer.sol +++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC721Transfer.sol @@ -21,59 +21,74 @@ pragma experimental ABIEncoderV2; import "../../utils/LibBytes/LibBytes.sol"; import "../../tokens/ERC721Token/ERC721Token.sol"; +import "./libs/LibTransferErrors.sol"; contract MixinERC721Transfer is - LibBytes + LibBytes, + LibTransferErrors { - - // Id of this proxy. - uint8 constant PROXY_ID = 2; - - // Revert reasons - string constant INVALID_TRANSFER_AMOUNT = "Transfer amount must equal 1."; - string constant INVALID_METADATA_LENGTH = "Metadata must have a length of 53."; - string constant PROXY_ID_MISMATCH = "Proxy id in metadata does not match this proxy id."; - /// @dev Internal version of `transferFrom`. - /// @param assetMetadata Encoded byte array. + /// @param assetData Encoded byte array. /// @param from Address to transfer asset from. /// @param to Address to transfer asset to. /// @param amount Amount of asset to transfer. function transferFromInternal( - bytes memory assetMetadata, + bytes memory assetData, address from, address to, uint256 amount ) internal { - // Data must be intended for this proxy. - uint256 length = assetMetadata.length; - - require( - length == 53, - INVALID_METADATA_LENGTH - ); - - require( - uint8(assetMetadata[length - 1]) == PROXY_ID, - PROXY_ID_MISMATCH - ); - // There exists only 1 of each token. require( amount == 1, - INVALID_TRANSFER_AMOUNT + INVALID_AMOUNT ); + + // Decode asset data. + ( + address token, + uint256 tokenId, + bytes memory receiverData + ) = decodeERC721AssetData(assetData); + + // Transfer token. Saves gas by calling safeTransferFrom only + // when there is receiverData present. Either succeeds or throws. + if (receiverData.length > 0) { + ERC721Token(token).safeTransferFrom(from, to, tokenId, receiverData); + } else { + ERC721Token(token).transferFrom(from, to, tokenId); + } + } - // Decode metadata - address token = readAddress(assetMetadata, 0); - uint256 tokenId = readUint256(assetMetadata, 20); - - // Transfer token. - // Either succeeds or throws. - // @TODO: Call safeTransferFrom if there is additional - // data stored in `assetMetadata`. - ERC721Token(token).transferFrom(from, to, tokenId); + /// @dev Decodes ERC721 Asset data. + /// @param assetData Encoded byte array. + /// @return proxyId Intended ERC721 proxy id. + /// @return token ERC721 token address. + /// @return tokenId ERC721 token id. + /// @return receiverData Additional data with no specific format, which + /// is passed to the receiving contract's onERC721Received. + function decodeERC721AssetData(bytes memory assetData) + internal + pure + returns ( + address token, + uint256 tokenId, + bytes memory receiverData + ) + { + // Decode asset data. + token = readAddress(assetData, 0); + tokenId = readUint256(assetData, 20); + if (assetData.length > 52) { + receiverData = readBytes(assetData, 52); + } + + return ( + token, + tokenId, + receiverData + ); } } diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxy/libs/LibAssetProxyErrors.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/libs/LibAssetProxyErrors.sol index 65bdacdb7..dca4f400f 100644 --- a/packages/contracts/src/contracts/current/protocol/AssetProxy/libs/LibAssetProxyErrors.sol +++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/libs/LibAssetProxyErrors.sol @@ -25,8 +25,4 @@ contract LibAssetProxyErrors { string constant TARGET_ALREADY_AUTHORIZED = "TARGET_ALREADY_AUTHORIZED"; // Target address must not already be authorized. string constant INDEX_OUT_OF_BOUNDS = "INDEX_OUT_OF_BOUNDS"; // Specified array index is out of bounds. string constant AUTHORIZED_ADDRESS_MISMATCH = "AUTHORIZED_ADDRESS_MISMATCH"; // Address at index does not match given target address. - - /// AssetProxy errors /// - string constant INVALID_AMOUNT = "INVALID_AMOUNT"; // Transfer amount must equal 1. - string constant TRANSFER_FAILED = "TRANSFER_FAILED"; // Transfer failed. } diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxy/libs/LibTransferErrors.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/libs/LibTransferErrors.sol new file mode 100644 index 000000000..ba784ab22 --- /dev/null +++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/libs/LibTransferErrors.sol @@ -0,0 +1,25 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.4.24; + +contract LibTransferErrors { + /// Transfer errors /// + string constant INVALID_AMOUNT = "INVALID_AMOUNT"; // Transfer amount must equal 1. + string constant TRANSFER_FAILED = "TRANSFER_FAILED"; // Transfer failed. +} -- cgit v1.2.3 From 0cdfe7f45802da3e6d3c0fe97148e90c80a13fae Mon Sep 17 00:00:00 2001 From: fragosti Date: Mon, 18 Jun 2018 10:06:38 -0700 Subject: Adjust version in changelog --- packages/contract-wrappers/CHANGELOG.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/contract-wrappers/CHANGELOG.json b/packages/contract-wrappers/CHANGELOG.json index 58ef090f7..39b63178a 100644 --- a/packages/contract-wrappers/CHANGELOG.json +++ b/packages/contract-wrappers/CHANGELOG.json @@ -1,6 +1,6 @@ [ { - "version": "0.0.3", + "version": "0.1.0", "changes": [ { "note": "Expose 'abi' ContractAbi property on all contract wrappers" -- cgit v1.2.3 From da46eefe2eea9507ae12ed678b15047f1c74366f Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Mon, 18 Jun 2018 12:09:31 -0700 Subject: Create a shared Image component --- .../components/relayer_index/relayer_grid_tile.tsx | 46 ++++++---------------- packages/website/ts/components/ui/image.tsx | 37 +++++++++++++++++ 2 files changed, 48 insertions(+), 35 deletions(-) create mode 100644 packages/website/ts/components/ui/image.tsx (limited to 'packages') diff --git a/packages/website/ts/components/relayer_index/relayer_grid_tile.tsx b/packages/website/ts/components/relayer_index/relayer_grid_tile.tsx index 0b0e75312..98d6dc0b3 100644 --- a/packages/website/ts/components/relayer_index/relayer_grid_tile.tsx +++ b/packages/website/ts/components/relayer_index/relayer_grid_tile.tsx @@ -5,6 +5,7 @@ import * as React from 'react'; import { TopTokens } from 'ts/components/relayer_index/relayer_top_tokens'; import { Container } from 'ts/components/ui/container'; +import { Image } from 'ts/components/ui/image'; import { Island } from 'ts/components/ui/island'; import { colors } from 'ts/style/colors'; import { WebsiteBackendRelayerInfo } from 'ts/types'; @@ -74,8 +75,16 @@ export const RelayerGridTile: React.StatelessComponent = (
-
- +
+
@@ -112,36 +121,3 @@ const Section = (props: SectionProps) => { }; const NoContent = () =>
{NO_CONTENT_MESSAGE}
; - -interface ImgWithFallbackProps { - src?: string; - fallbackSrc: string; -} -interface ImgWithFallbackState { - imageLoadFailed: boolean; -} -class ImgWithFallback extends React.Component { - constructor(props: ImgWithFallbackProps) { - super(props); - this.state = { - imageLoadFailed: false, - }; - } - public render(): React.ReactNode { - return ( -
- {this._renderImg()} -
- ); - } - private _renderImg(): React.ReactNode { - const src = - this.state.imageLoadFailed || _.isUndefined(this.props.src) ? this.props.fallbackSrc : this.props.src; - return ; - } - private _onError(): void { - this.setState({ - imageLoadFailed: true, - }); - } -} diff --git a/packages/website/ts/components/ui/image.tsx b/packages/website/ts/components/ui/image.tsx new file mode 100644 index 000000000..0958d2e5e --- /dev/null +++ b/packages/website/ts/components/ui/image.tsx @@ -0,0 +1,37 @@ +import * as _ from 'lodash'; +import * as React from 'react'; + +export interface ImageProps { + className?: string; + src?: string; + fallbackSrc?: string; + height?: string; +} +interface ImageState { + imageLoadFailed: boolean; +} +export class Image extends React.Component { + constructor(props: ImageProps) { + super(props); + this.state = { + imageLoadFailed: false, + }; + } + public render(): React.ReactNode { + const src = + this.state.imageLoadFailed || _.isUndefined(this.props.src) ? this.props.fallbackSrc : this.props.src; + return ( + + ); + } + private _onError(): void { + this.setState({ + imageLoadFailed: true, + }); + } +} -- cgit v1.2.3 From 19668b9b48eb08645f500dd8453b8cb2f7abc400 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Mon, 18 Jun 2018 16:46:54 +0200 Subject: remove remove_tags script --- packages/monorepo-scripts/README.md | 2 -- packages/monorepo-scripts/package.json | 2 -- 2 files changed, 4 deletions(-) (limited to 'packages') diff --git a/packages/monorepo-scripts/README.md b/packages/monorepo-scripts/README.md index 22b449870..d979e27dc 100644 --- a/packages/monorepo-scripts/README.md +++ b/packages/monorepo-scripts/README.md @@ -8,8 +8,6 @@ This repository contains a few helpful scripts for working with this mono repo. **`yarn find_unused_deps`**: Sometimes we accidentally leave dependencies listed in `package.json` that are no longer being used. This script finds potential dependencies that might no longer be in use. Please verify that it is no longer in use before removing, the `depcheck` package we use under-the-hood doesn't handle some TS quirks perfectly. -**`yarn remove_tags`**: Our publishing script calls `lerna publish` under-the-hood. If this command fails, it might have created new versioned git tags for each package. Removing these manually is tedious, so you can also run this command instead. Before doing so, check to see if `lerna` already created the publish commit. If so, first revert that with `git reset --hard HEAD~1`, then run this command. - **`yarn test:publish`**: Execute a test-run of the publish script. This dry run won't actually publish, nor will it commit/push anything to Github. ## Usage diff --git a/packages/monorepo-scripts/package.json b/packages/monorepo-scripts/package.json index 5a6d7b25a..9a466ffd0 100644 --- a/packages/monorepo-scripts/package.json +++ b/packages/monorepo-scripts/package.json @@ -14,12 +14,10 @@ "clean": "shx rm -rf lib", "test:publish": "run-s build script:publish", "find_unused_deps": "run-s build script:find_unused_deps", - "remove_tags": "run-s build script:remove_tags", "script:deps_versions": "node ./lib/deps_versions.js", "script:prepublish_checks": "node ./lib/prepublish_checks.js", "script:publish": "IS_DRY_RUN=true node ./lib/publish.js", "script:find_unused_deps": "node ./lib/find_unused_dependencies.js", - "script:remove_tags": "node ./lib/remove_tags.js" }, "repository": { "type": "git", -- cgit v1.2.3 From 8633fa702436cceeafa52ec39a7fabb5b2650c38 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Mon, 18 Jun 2018 16:55:59 +0200 Subject: Add more prepublish checks --- packages/monorepo-scripts/package.json | 1 + packages/monorepo-scripts/src/prepublish_checks.ts | 96 ++++++++++++++++++++- packages/monorepo-scripts/src/publish.ts | 24 ++---- packages/monorepo-scripts/src/types.ts | 13 +++ .../monorepo-scripts/src/utils/changelog_utils.ts | 55 +++++++++++- packages/monorepo-scripts/src/utils/npm_utils.ts | 28 +++++++ .../monorepo-scripts/src/utils/semver_utils.ts | 56 +++++++++++++ packages/monorepo-scripts/src/utils/utils.ts | 98 +++++++++++++++++----- 8 files changed, 332 insertions(+), 39 deletions(-) create mode 100644 packages/monorepo-scripts/src/utils/npm_utils.ts create mode 100644 packages/monorepo-scripts/src/utils/semver_utils.ts (limited to 'packages') diff --git a/packages/monorepo-scripts/package.json b/packages/monorepo-scripts/package.json index 9a466ffd0..fe9194b2f 100644 --- a/packages/monorepo-scripts/package.json +++ b/packages/monorepo-scripts/package.json @@ -48,6 +48,7 @@ "es6-promisify": "^5.0.0", "glob": "^7.1.2", "lodash": "^4.17.4", + "isomorphic-fetch": "2.2.1", "moment": "2.21.0", "opn": "^5.3.0", "promisify-child-process": "^1.0.5", diff --git a/packages/monorepo-scripts/src/prepublish_checks.ts b/packages/monorepo-scripts/src/prepublish_checks.ts index 2c096d8f6..64de56ece 100644 --- a/packages/monorepo-scripts/src/prepublish_checks.ts +++ b/packages/monorepo-scripts/src/prepublish_checks.ts @@ -1,9 +1,103 @@ +import * as fs from 'fs'; import * as _ from 'lodash'; +import * as path from 'path'; import { exec as execAsync } from 'promisify-child-process'; import { constants } from './constants'; +import { Changelog, PackageRegistryJson } from './types'; +import { changelogUtils } from './utils/changelog_utils'; +import { npmUtils } from './utils/npm_utils'; +import { semverUtils } from './utils/semver_utils'; import { utils } from './utils/utils'; +async function prepublishChecksAsync(): Promise { + const shouldIncludePrivate = false; + const updatedPublicLernaPackages = await utils.getUpdatedLernaPackagesAsync(shouldIncludePrivate); + + await checkCurrentVersionMatchesLatestPublishedNPMPackageAsync(updatedPublicLernaPackages); + await checkChangelogFormatAsync(updatedPublicLernaPackages); + await checkGitTagsForNextVersionAndDeleteIfExistAsync(updatedPublicLernaPackages); + await checkPublishRequiredSetupAsync(); +} + +async function checkGitTagsForNextVersionAndDeleteIfExistAsync( + updatedPublicLernaPackages: LernaPackage[], +): Promise { + const packageNames = _.map(updatedPublicLernaPackages, lernaPackage => lernaPackage.package.name); + const localGitTags = await utils.getLocalGitTagsAsync(); + const localTagVersionsByPackageName = await utils.getGitTagsByPackageNameAsync(packageNames, localGitTags); + + const remoteGitTags = await utils.getRemoteGitTagsAsync(); + const remoteTagVersionsByPackageName = await utils.getGitTagsByPackageNameAsync(packageNames, remoteGitTags); + + for (const lernaPackage of updatedPublicLernaPackages) { + const currentVersion = lernaPackage.package.version; + const packageName = lernaPackage.package.name; + const packageLocation = lernaPackage.location; + const nextVersion = await utils.getNextPackageVersionAsync(currentVersion, packageName, packageLocation); + + const localTagVersions = localTagVersionsByPackageName[packageName]; + if (_.includes(localTagVersions, nextVersion)) { + const tagName = `${packageName}@${nextVersion}`; + await utils.removeLocalTagAsync(tagName); + } + + const remoteTagVersions = remoteTagVersionsByPackageName[packageName]; + if (_.includes(remoteTagVersions, nextVersion)) { + const tagName = `:refs/tags/${packageName}@${nextVersion}`; + await utils.removeRemoteTagAsync(tagName); + } + } +} + +async function checkCurrentVersionMatchesLatestPublishedNPMPackageAsync( + updatedPublicLernaPackages: LernaPackage[], +): Promise { + for (const lernaPackage of updatedPublicLernaPackages) { + const packageName = lernaPackage.package.name; + const packageVersion = lernaPackage.package.version; + const packageRegistryJsonIfExists = await npmUtils.getPackageRegistryJsonIfExistsAsync(packageName); + if (_.isUndefined(packageRegistryJsonIfExists)) { + continue; // noop for packages not yet published to NPM + } + const allVersionsIncludingUnpublished = npmUtils.getPreviouslyPublishedVersions(packageRegistryJsonIfExists); + const latestNPMVersion = semverUtils.getLatestVersion(allVersionsIncludingUnpublished); + if (packageVersion !== latestNPMVersion) { + throw new Error( + `Found verson ${packageVersion} in package.json but version ${latestNPMVersion} + on NPM (could be unpublished version) for ${packageName}. These versions must match. If you update + the package.json version, make sure to also update the internal dependency versions too.`, + ); + } + } +} + +async function checkChangelogFormatAsync(updatedPublicLernaPackages: LernaPackage[]): Promise { + for (const lernaPackage of updatedPublicLernaPackages) { + const packageName = lernaPackage.package.name; + const changelog = changelogUtils.getChangelogOrCreateIfMissing(packageName, lernaPackage.location); + + const currentVersion = lernaPackage.package.version; + if (!_.isEmpty(changelog)) { + const lastEntry = changelog[0]; + const doesLastEntryHaveTimestamp = !_.isUndefined(lastEntry.timestamp); + if (semverUtils.lessThan(lastEntry.version, currentVersion)) { + throw new Error( + `CHANGELOG version cannot be below current package version. + Update ${packageName}'s CHANGELOG. It's current version is ${currentVersion} + but the latest CHANGELOG entry is: ${lastEntry.version}`, + ); + } else if (semverUtils.greaterThan(lastEntry.version, currentVersion) && doesLastEntryHaveTimestamp) { + // Remove incorrectly added timestamp + delete changelog[0].timestamp; + // Save updated CHANGELOG.json + await changelogUtils.writeChangelogJsonFileAsync(lernaPackage.location, changelog); + utils.log(`${packageName}: Removed timestamp from latest CHANGELOG.json entry.`); + } + } + } +} + async function checkPublishRequiredSetupAsync(): Promise { // check to see if logged into npm before publishing try { @@ -65,7 +159,7 @@ async function checkPublishRequiredSetupAsync(): Promise { } } -checkPublishRequiredSetupAsync().catch(err => { +prepublishChecksAsync().catch(err => { utils.log(err.message); process.exit(1); }); diff --git a/packages/monorepo-scripts/src/publish.ts b/packages/monorepo-scripts/src/publish.ts index 2efbc8bf2..637512a5a 100644 --- a/packages/monorepo-scripts/src/publish.ts +++ b/packages/monorepo-scripts/src/publish.ts @@ -119,19 +119,14 @@ async function updateChangeLogsAsync(updatedPublicLernaPackages: LernaPackage[]) const packageToVersionChange: PackageToVersionChange = {}; for (const lernaPackage of updatedPublicLernaPackages) { const packageName = lernaPackage.package.name; - const changelogJSONPath = path.join(lernaPackage.location, 'CHANGELOG.json'); - const changelogJSON = utils.getChangelogJSONOrCreateIfMissing(changelogJSONPath); - let changelog: Changelog; - try { - changelog = JSON.parse(changelogJSON); - } catch (err) { - throw new Error( - `${lernaPackage.package.name}'s CHANGELOG.json contains invalid JSON. Please fix and try again.`, - ); - } + let changelog = changelogUtils.getChangelogOrCreateIfMissing(packageName, lernaPackage.location); const currentVersion = lernaPackage.package.version; - const shouldAddNewEntry = changelogUtils.shouldAddNewChangelogEntry(currentVersion, changelog); + const shouldAddNewEntry = changelogUtils.shouldAddNewChangelogEntry( + lernaPackage.package.name, + currentVersion, + changelog, + ); if (shouldAddNewEntry) { // Create a new entry for a patch version with generic changelog entry. const nextPatchVersion = utils.getNextPatchVersion(currentVersion); @@ -160,14 +155,11 @@ async function updateChangeLogsAsync(updatedPublicLernaPackages: LernaPackage[]) } // Save updated CHANGELOG.json - fs.writeFileSync(changelogJSONPath, JSON.stringify(changelog, null, '\t')); - await utils.prettifyAsync(changelogJSONPath, constants.monorepoRootPath); + await changelogUtils.writeChangelogJsonFileAsync(lernaPackage.location, changelog); utils.log(`${packageName}: Updated CHANGELOG.json`); // Generate updated CHANGELOG.md const changelogMd = changelogUtils.generateChangelogMd(changelog); - const changelogMdPath = path.join(lernaPackage.location, 'CHANGELOG.md'); - fs.writeFileSync(changelogMdPath, changelogMd); - await utils.prettifyAsync(changelogMdPath, constants.monorepoRootPath); + await changelogUtils.writeChangelogMdFileAsync(lernaPackage.location, changelog); utils.log(`${packageName}: Updated CHANGELOG.md`); } diff --git a/packages/monorepo-scripts/src/types.ts b/packages/monorepo-scripts/src/types.ts index 36fb923b3..61bd75732 100644 --- a/packages/monorepo-scripts/src/types.ts +++ b/packages/monorepo-scripts/src/types.ts @@ -27,3 +27,16 @@ export enum SemVerIndex { export interface PackageToVersionChange { [name: string]: string; } + +export interface PackageRegistryJson { + versions: { + [version: string]: any; + }; + time: { + [version: string]: string; + }; +} + +export interface GitTagsByPackageName { + [packageName: string]: string[]; +} diff --git a/packages/monorepo-scripts/src/utils/changelog_utils.ts b/packages/monorepo-scripts/src/utils/changelog_utils.ts index edfe65a80..4e09fc842 100644 --- a/packages/monorepo-scripts/src/utils/changelog_utils.ts +++ b/packages/monorepo-scripts/src/utils/changelog_utils.ts @@ -1,8 +1,15 @@ +import * as fs from 'fs'; import * as _ from 'lodash'; import * as moment from 'moment'; +import * as path from 'path'; +import { exec as execAsync } from 'promisify-child-process'; +import semverSort = require('semver-sort'); +import { constants } from '../constants'; import { Change, Changelog, VersionChangelog } from '../types'; +import { semverUtils } from './semver_utils'; + const CHANGELOG_MD_HEADER = `