aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--packages/contracts/package.json1
-rw-r--r--packages/contracts/src/utils/revert_trace.ts21
-rw-r--r--packages/contracts/src/utils/web3_wrapper.ts15
-rw-r--r--packages/dev-utils/src/env.ts1
-rw-r--r--packages/sol-cov/src/coverage_subprovider.ts6
-rw-r--r--packages/sol-cov/src/index.ts1
-rw-r--r--packages/sol-cov/src/profiler_subprovider.ts6
-rw-r--r--packages/sol-cov/src/revert_trace.ts90
-rw-r--r--packages/sol-cov/src/revert_trace_subprovider.ts114
-rw-r--r--packages/sol-cov/src/trace.ts59
-rw-r--r--packages/sol-cov/src/trace_collection_subprovider.ts64
-rw-r--r--packages/sol-cov/src/trace_info_subprovider.ts59
-rw-r--r--packages/sol-cov/src/types.ts7
-rw-r--r--packages/sol-cov/src/utils.ts24
-rw-r--r--packages/website/ts/components/inputs/allowance_toggle.tsx9
-rw-r--r--packages/website/ts/components/onboarding/portal_onboarding_flow.tsx74
-rw-r--r--packages/website/ts/components/portal/portal.tsx6
-rw-r--r--packages/website/ts/components/token_balances.tsx5
-rw-r--r--packages/website/ts/components/wallet/wallet.tsx7
-rw-r--r--packages/website/ts/containers/inputs/allowance_toggle.ts41
-rw-r--r--packages/website/ts/containers/portal_onboarding_flow.ts3
-rw-r--r--packages/website/ts/utils/utils.ts10
22 files changed, 497 insertions, 126 deletions
diff --git a/packages/contracts/package.json b/packages/contracts/package.json
index d6ca3727b..2495795dc 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 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/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..c9d83a02d 100644
--- a/packages/contracts/src/utils/web3_wrapper.ts
+++ b/packages/contracts/src/utils/web3_wrapper.ts
@@ -2,9 +2,11 @@ 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';
enum ProviderType {
Ganache = 'ganache',
@@ -48,10 +50,11 @@ 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.`,
- );
+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, or revert trace subproviders can be enabled at a time`);
}
if (isCoverageEnabled) {
const coverageSubprovider = coverage.getCoverageSubproviderSingleton();
@@ -71,5 +74,9 @@ if (isProfilerEnabled) {
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/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<void> {
+ protected async _handleTraceInfoAsync(traceInfo: TraceInfo): Promise<void> {
await this._coverageCollector.computeSingleTraceCoverageAsync(traceInfo);
}
/**
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/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<void> {
+ protected async _handleTraceInfoAsync(traceInfo: TraceInfo): Promise<void> {
await this._profilerCollector.computeSingleTraceCoverageAsync(traceInfo);
}
/**
diff --git a/packages/sol-cov/src/revert_trace.ts b/packages/sol-cov/src/revert_trace.ts
new file mode 100644
index 000000000..a78d1afa8
--- /dev/null
+++ b/packages/sol-cov/src/revert_trace.ts
@@ -0,0 +1,90 @@
+import { logUtils } from '@0xproject/utils';
+import { OpCode, StructLog } from 'ethereum-types';
+
+import * as _ from 'lodash';
+
+import { EvmCallStack } from './types';
+import { utils } from './utils';
+
+export function getRevertTrace(structLogs: StructLog[], startAddress: string): EvmCallStack {
+ const evmCallStack: EvmCallStack = [];
+ const addressStack = [startAddress];
+ if (_.isEmpty(structLogs)) {
+ return [];
+ }
+ const normalizedStructLogs = utils.normalizeStructLogs(structLogs);
+ // tslint:disable-next-line:prefer-for-of
+ for (let i = 0; i < normalizedStructLogs.length; i++) {
+ const structLog = normalizedStructLogs[i];
+ if (structLog.depth !== addressStack.length - 1) {
+ throw new Error("Malformed trace. Trace depth doesn't match call stack depth");
+ }
+ // After that check we have a guarantee that call stack is never empty
+ // If it would: callStack.length - 1 === structLog.depth === -1
+ // That means that we can always safely pop from it
+
+ if (utils.isCallLike(structLog.op)) {
+ const currentAddress = _.last(addressStack) as string;
+ const jumpAddressOffset = 1;
+ const newAddress = utils.getAddressFromStackEntry(
+ structLog.stack[structLog.stack.length - jumpAddressOffset - 1],
+ );
+
+ // Sometimes calls don't change the execution context (current address). When we do a transfer to an
+ // externally owned account - it does the call and immediately returns because there is no fallback
+ // function. We manually check if the call depth had changed to handle that case.
+ const nextStructLog = normalizedStructLogs[i + 1];
+ if (nextStructLog.depth !== structLog.depth) {
+ addressStack.push(newAddress);
+ evmCallStack.push({
+ address: currentAddress,
+ structLog,
+ });
+ }
+ } else if (utils.isEndOpcode(structLog.op) && structLog.op !== OpCode.Revert) {
+ // Just like with calls, sometimes returns/stops don't change the execution context (current address).
+ const nextStructLog = normalizedStructLogs[i + 1];
+ if (_.isUndefined(nextStructLog) || nextStructLog.depth !== structLog.depth) {
+ evmCallStack.pop();
+ addressStack.pop();
+ }
+ if (structLog.op === OpCode.SelfDestruct) {
+ // After contract execution, we look at all sub-calls to external contracts, and for each one, fetch
+ // the bytecode and compute the coverage for the call. If the contract is destroyed with a call
+ // to `selfdestruct`, we are unable to fetch it's bytecode and compute coverage.
+ // TODO: Refactor this logic to fetch the sub-called contract bytecode before the selfdestruct is called
+ // in order to handle this edge-case.
+ logUtils.warn(
+ "Detected a selfdestruct. Sol-cov currently doesn't support that scenario. We'll just skip the trace part for a destructed contract",
+ );
+ }
+ } else if (structLog.op === OpCode.Revert) {
+ evmCallStack.push({
+ address: _.last(addressStack) as string,
+ structLog,
+ });
+ return evmCallStack;
+ } else if (structLog.op === OpCode.Create) {
+ // TODO: Extract the new contract address from the stack and handle that scenario
+ logUtils.warn(
+ "Detected a contract created from within another contract. Sol-cov currently doesn't support that scenario. We'll just skip that trace",
+ );
+ return [];
+ } else {
+ if (structLog !== _.last(normalizedStructLogs)) {
+ const nextStructLog = normalizedStructLogs[i + 1];
+ if (nextStructLog.depth === structLog.depth) {
+ continue;
+ } else if (nextStructLog.depth === structLog.depth - 1) {
+ addressStack.pop();
+ } else {
+ throw new Error('Malformed trace. Unexpected call depth change');
+ }
+ }
+ }
+ }
+ if (evmCallStack.length !== 0) {
+ logUtils.warn('Malformed trace. Call stack non empty at the end. (probably out of gas)');
+ }
+ return [];
+}
diff --git a/packages/sol-cov/src/revert_trace_subprovider.ts b/packages/sol-cov/src/revert_trace_subprovider.ts
new file mode 100644
index 000000000..b1d4da10c
--- /dev/null
+++ b/packages/sol-cov/src/revert_trace_subprovider.ts
@@ -0,0 +1,114 @@
+import { stripHexPrefix } from 'ethereumjs-util';
+import * as _ from 'lodash';
+import { getLogger, levels, Logger } from 'loglevel';
+
+import { AbstractArtifactAdapter } from './artifact_adapters/abstract_artifact_adapter';
+import { constants } from './constants';
+import { getRevertTrace } from './revert_trace';
+import { parseSourceMap } from './source_maps';
+import { TraceCollectionSubprovider } from './trace_collection_subprovider';
+import { ContractData, EvmCallStack, SourceRange } from './types';
+import { utils } from './utils';
+
+/**
+ * This class implements the [web3-provider-engine](https://github.com/MetaMask/provider-engine) subprovider interface.
+ * It is used to report call stack traces whenever a revert occurs.
+ */
+export class RevertTraceSubprovider extends TraceCollectionSubprovider {
+ // Lock is used to not accept normal transactions while doing call/snapshot magic because they'll be reverted later otherwise
+ private _contractsData!: ContractData[];
+ private _artifactAdapter: AbstractArtifactAdapter;
+ private _logger: Logger;
+
+ /**
+ * Instantiates a RevertTraceSubprovider instance
+ * @param artifactAdapter Adapter for used artifacts format (0x, truffle, giveth, etc.)
+ * @param defaultFromAddress default from address to use when sending transactions
+ * @param isVerbose If true, we will log any unknown transactions. Otherwise we will ignore them
+ */
+ constructor(artifactAdapter: AbstractArtifactAdapter, defaultFromAddress: string, isVerbose: boolean = true) {
+ const traceCollectionSubproviderConfig = {
+ shouldCollectTransactionTraces: true,
+ shouldCollectGasEstimateTraces: true,
+ shouldCollectCallTraces: true,
+ };
+ super(defaultFromAddress, traceCollectionSubproviderConfig);
+ this._artifactAdapter = artifactAdapter;
+ this._logger = getLogger('sol-cov');
+ this._logger.setLevel(isVerbose ? levels.TRACE : levels.ERROR);
+ }
+ // tslint:disable-next-line:no-unused-variable
+ protected async _recordTxTraceAsync(address: string, data: string | undefined, txHash: string): Promise<void> {
+ await this._web3Wrapper.awaitTransactionMinedAsync(txHash, 0);
+ const trace = await this._web3Wrapper.getTransactionTraceAsync(txHash, {
+ disableMemory: true,
+ disableStack: false,
+ disableStorage: true,
+ });
+ const evmCallStack = getRevertTrace(trace.structLogs, address);
+ if (evmCallStack.length > 0) {
+ // if getRevertTrace returns a call stack it means there was a
+ // revert.
+ await this._printStackTraceAsync(evmCallStack);
+ }
+ }
+ private async _printStackTraceAsync(evmCallStack: EvmCallStack): Promise<void> {
+ 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;
+ if (isContractCreation) {
+ this._logger.error('Contract creation not supported');
+ continue;
+ }
+ const bytecode = await this._web3Wrapper.getContractCodeAsync(evmCallStackEntry.address);
+ const contractData = utils.getContractDataIfExists(this._contractsData, bytecode);
+ if (_.isUndefined(contractData)) {
+ const errMsg = isContractCreation
+ ? `Unknown contract creation transaction`
+ : `Transaction to an unknown address: ${evmCallStackEntry.address}`;
+ this._logger.warn(errMsg);
+ continue;
+ }
+ const bytecodeHex = stripHexPrefix(bytecode);
+ const sourceMap = isContractCreation ? contractData.sourceMap : contractData.sourceMapRuntime;
+ const pcToSourceRange = parseSourceMap(
+ contractData.sourceCodes,
+ sourceMap,
+ bytecodeHex,
+ contractData.sources,
+ );
+ // tslint:disable-next-line:no-unnecessary-initializer
+ let sourceRange: SourceRange | undefined = undefined;
+ let pc = evmCallStackEntry.structLog.pc;
+ // Sometimes there is not a mapping for this pc (e.g. if the revert
+ // actually happens in assembly). In that case, we want to keep
+ // searching backwards by decrementing the pc until we find a
+ // mapped source range.
+ while (_.isUndefined(sourceRange)) {
+ 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 for REVERT:\n');
+ _.forEach(_.reverse(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..635019fc0 100644
--- a/packages/sol-cov/src/trace.ts
+++ b/packages/sol-cov/src/trace.ts
@@ -1,32 +1,25 @@
-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 = [];
- const callStack = [startAddress];
+ const addressStack = [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];
- if (structLog.depth !== callStack.length - 1) {
+ for (let i = 0; i < normalizedStructLogs.length; i++) {
+ const structLog = normalizedStructLogs[i];
+ if (structLog.depth !== addressStack.length - 1) {
throw new Error("Malformed trace. Trace depth doesn't match call stack depth");
}
// After that check we have a guarantee that call stack is never empty
@@ -34,36 +27,26 @@ 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) {
- const currentAddress = _.last(callStack) as string;
+ if (utils.isCallLike(structLog.op)) {
+ const currentAddress = _.last(addressStack) as string;
const jumpAddressOffset = 1;
- const newAddress = getAddressFromStackEntry(
+ const newAddress = utils.getAddressFromStackEntry(
structLog.stack[structLog.stack.length - jumpAddressOffset - 1],
);
- if (structLog === _.last(structLogs)) {
- throw new Error('Malformed trace. CALL-like opcode can not be the last one');
- }
+
// Sometimes calls don't change the execution context (current address). When we do a transfer to an
// externally owned account - it does the call and immediately returns because there is no fallback
// function. We manually check if the call depth had changed to handle that case.
- const nextStructLog = structLogs[i + 1];
+ 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 (isEndOpcode) {
- const currentAddress = callStack.pop() as string;
+ } else if (utils.isEndOpcode(structLog.op)) {
+ const currentAddress = addressStack.pop() as string;
traceByContractAddress[currentAddress] = (traceByContractAddress[currentAddress] || []).concat(
currentTraceSegment,
);
@@ -85,12 +68,12 @@ 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) {
- const currentAddress = callStack.pop() as string;
+ const currentAddress = addressStack.pop() as string;
traceByContractAddress[currentAddress] = (traceByContractAddress[currentAddress] || []).concat(
currentTraceSegment,
);
@@ -101,11 +84,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,
);
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;
/**
@@ -58,11 +58,6 @@ export abstract class TraceCollectionSubprovider extends Subprovider {
this._isEnabled = false;
}
/**
- * Called for each subtrace.
- * @param traceInfo Trace info for this subtrace.
- */
- public abstract handleTraceInfoAsync(traceInfo: TraceInfo): Promise<void>;
- /**
* 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.
@@ -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<void>;
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<void> {
- 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<CallData>): Promise<void> {
// We don't want other transactions to be exeucted during snashotting period, that's why we lock the
// transaction execution for all transactions except our fake ones.
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<void>;
+ protected async _recordTxTraceAsync(address: string, data: string | undefined, txHash: string): Promise<void> {
+ await this._web3Wrapper.awaitTransactionMinedAsync(txHash, 0);
+ const trace = await this._web3Wrapper.getTransactionTraceAsync(txHash, {
+ disableMemory: true,
+ disableStack: false,
+ disableStorage: true,
+ });
+ const tracesByContractAddress = getTracesByContractAddress(trace.structLogs, address);
+ const subcallAddresses = _.keys(tracesByContractAddress);
+ if (address === constants.NEW_CONTRACT) {
+ for (const subcallAddress of subcallAddresses) {
+ let traceInfo: TraceInfoNewContract | TraceInfoExistingContract;
+ if (subcallAddress === 'NEW_CONTRACT') {
+ const traceForThatSubcall = tracesByContractAddress[subcallAddress];
+ traceInfo = {
+ subtrace: traceForThatSubcall,
+ txHash,
+ address: subcallAddress,
+ bytecode: data as string,
+ };
+ } else {
+ const runtimeBytecode = await this._web3Wrapper.getContractCodeAsync(subcallAddress);
+ const traceForThatSubcall = tracesByContractAddress[subcallAddress];
+ traceInfo = {
+ subtrace: traceForThatSubcall,
+ txHash,
+ address: subcallAddress,
+ runtimeBytecode,
+ };
+ }
+ await this._handleTraceInfoAsync(traceInfo);
+ }
+ } else {
+ for (const subcallAddress of subcallAddresses) {
+ const runtimeBytecode = await this._web3Wrapper.getContractCodeAsync(subcallAddress);
+ const traceForThatSubcall = tracesByContractAddress[subcallAddress];
+ const traceInfo: TraceInfoExistingContract = {
+ subtrace: traceForThatSubcall,
+ txHash,
+ address: subcallAddress,
+ runtimeBytecode,
+ };
+ await this._handleTraceInfoAsync(traceInfo);
+ }
+ }
+ }
+}
diff --git a/packages/sol-cov/src/types.ts b/packages/sol-cov/src/types.ts
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;
+ },
};
diff --git a/packages/website/ts/components/inputs/allowance_toggle.tsx b/packages/website/ts/components/inputs/allowance_toggle.tsx
index 09791f125..0dd2a5aa5 100644
--- a/packages/website/ts/components/inputs/allowance_toggle.tsx
+++ b/packages/website/ts/components/inputs/allowance_toggle.tsx
@@ -1,5 +1,6 @@
import { constants as sharedConstants, Styles } from '@0xproject/react-shared';
import { BigNumber, logUtils } from '@0xproject/utils';
+import * as _ from 'lodash';
import Toggle from 'material-ui/Toggle';
import * as React from 'react';
import { Blockchain } from 'ts/blockchain';
@@ -16,11 +17,11 @@ interface AllowanceToggleProps {
networkId: number;
blockchain: Blockchain;
dispatcher: Dispatcher;
- onErrorOccurred: (errType: BalanceErrs) => void;
token: Token;
tokenState: TokenState;
userAddress: string;
- isDisabled: boolean;
+ isDisabled?: boolean;
+ onErrorOccurred?: (errType: BalanceErrs) => void;
refetchTokenStateAsync: () => Promise<void>;
}
@@ -55,6 +56,10 @@ const styles: Styles = {
};
export class AllowanceToggle extends React.Component<AllowanceToggleProps, AllowanceToggleState> {
+ public static defaultProps = {
+ onErrorOccurred: _.noop,
+ isDisabled: false,
+ };
constructor(props: AllowanceToggleProps) {
super(props);
this.state = {
diff --git a/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx b/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx
index bf52684d7..efb844cb5 100644
--- a/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx
+++ b/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx
@@ -2,11 +2,14 @@ import * as _ from 'lodash';
import * as React from 'react';
import { BigNumber } from '@0xproject/utils';
+import { Blockchain } from 'ts/blockchain';
import { OnboardingFlow, Step } from 'ts/components/onboarding/onboarding_flow';
-import { ProviderType, TokenByAddress, TokenStateByAddress } from 'ts/types';
+import { AllowanceToggle } from 'ts/containers/inputs/allowance_toggle';
+import { ProviderType, Token, TokenByAddress, TokenStateByAddress } from 'ts/types';
import { utils } from 'ts/utils/utils';
export interface PortalOnboardingFlowProps {
+ blockchain: Blockchain;
stepIndex: number;
isRunning: boolean;
userAddress: string;
@@ -19,6 +22,7 @@ export interface PortalOnboardingFlowProps {
trackedTokenStateByAddress: TokenStateByAddress;
updateIsRunning: (isRunning: boolean) => void;
updateOnboardingStep: (stepIndex: number) => void;
+ refetchTokenStateAsync: (tokenAddress: string) => Promise<void>;
}
export class PortalOnboardingFlow extends React.Component<PortalOnboardingFlowProps> {
@@ -39,7 +43,6 @@ export class PortalOnboardingFlow extends React.Component<PortalOnboardingFlowPr
/>
);
}
-
private _getSteps(): Step[] {
const steps: Step[] = [
{
@@ -77,18 +80,33 @@ export class PortalOnboardingFlow extends React.Component<PortalOnboardingFlowPr
placement: 'right',
continueButtonDisplay: this._userHasVisibleWeth() ? 'enabled' : 'disabled',
},
+ {
+ target: '.weth-row',
+ content: (
+ <div>
+ Unlock your tokens for trading. You only need to do this once for each token.
+ <div> ETH: {this._renderEthAllowanceToggle()}</div>
+ <div> ZRX: {this._renderZrxAllowanceToggle()}</div>
+ </div>
+ ),
+ placement: 'right',
+ continueButtonDisplay: this._userHasAllowancesForWethAndZrx() ? 'enabled' : 'disabled',
+ },
+ {
+ target: '.wallet',
+ content: 'Congrats! Your wallet is now set up for trading. Use it on any relayer in the 0x ecosystem.',
+ placement: 'right',
+ continueButtonDisplay: 'enabled',
+ },
];
return steps;
}
-
private _isAddressAvailable(): boolean {
return !_.isEmpty(this.props.userAddress);
}
-
private _userHasVisibleEth(): boolean {
return this.props.userEtherBalanceInWei > new BigNumber(0);
}
-
private _userHasVisibleWeth(): boolean {
const ethToken = utils.getEthToken(this.props.tokenByAddress);
if (!ethToken) {
@@ -97,15 +115,25 @@ export class PortalOnboardingFlow extends React.Component<PortalOnboardingFlowPr
const wethTokenState = this.props.trackedTokenStateByAddress[ethToken.address];
return wethTokenState.balance > new BigNumber(0);
}
-
+ private _userHasAllowancesForWethAndZrx(): boolean {
+ const ethToken = utils.getEthToken(this.props.tokenByAddress);
+ const zrxToken = utils.getZrxToken(this.props.tokenByAddress);
+ if (ethToken && zrxToken) {
+ const ethTokenAllowance = this.props.trackedTokenStateByAddress[ethToken.address].allowance;
+ const zrxTokenAllowance = this.props.trackedTokenStateByAddress[zrxToken.address].allowance;
+ return ethTokenAllowance > new BigNumber(0) && zrxTokenAllowance > new BigNumber(0);
+ }
+ return false;
+ }
private _overrideOnboardingStateIfShould(): void {
this._autoStartOnboardingIfShould();
this._adjustStepIfShould();
}
private _adjustStepIfShould(): void {
+ const stepIndex = this.props.stepIndex;
if (this._isAddressAvailable()) {
- if (this.props.stepIndex < 2) {
+ if (stepIndex < 2) {
this.props.updateOnboardingStep(2);
}
return;
@@ -115,14 +143,42 @@ export class PortalOnboardingFlow extends React.Component<PortalOnboardingFlowPr
this.props.injectedProviderName,
);
if (isExternallyInjected) {
- this.props.updateOnboardingStep(1);
+ if (stepIndex !== 1) {
+ this.props.updateOnboardingStep(1);
+ }
return;
}
- this.props.updateOnboardingStep(0);
+ if (stepIndex !== 0) {
+ this.props.updateOnboardingStep(0);
+ }
}
private _autoStartOnboardingIfShould(): void {
if (!this.props.isRunning && !this.props.hasBeenSeen && this.props.blockchainIsLoaded) {
this.props.updateIsRunning(true);
}
}
+ private _renderZrxAllowanceToggle(): React.ReactNode {
+ const zrxToken = utils.getZrxToken(this.props.tokenByAddress);
+ return this._renderAllowanceToggle(zrxToken);
+ }
+ private _renderEthAllowanceToggle(): React.ReactNode {
+ const ethToken = utils.getEthToken(this.props.tokenByAddress);
+ return this._renderAllowanceToggle(ethToken);
+ }
+ private _renderAllowanceToggle(token: Token): React.ReactNode {
+ if (!token) {
+ return null;
+ }
+ const tokenState = this.props.trackedTokenStateByAddress[token.address];
+ return (
+ <AllowanceToggle
+ token={token}
+ tokenState={tokenState}
+ isDisabled={!tokenState.isLoaded}
+ blockchain={this.props.blockchain}
+ // tslint:disable-next-line:jsx-no-lambda
+ refetchTokenStateAsync={async () => this.props.refetchTokenStateAsync(token.address)}
+ />
+ );
+ }
}
diff --git a/packages/website/ts/components/portal/portal.tsx b/packages/website/ts/components/portal/portal.tsx
index 1b3bf3dae..48486939b 100644
--- a/packages/website/ts/components/portal/portal.tsx
+++ b/packages/website/ts/components/portal/portal.tsx
@@ -235,7 +235,11 @@ export class Portal extends React.Component<PortalProps, PortalState> {
: TokenVisibility.TRACKED;
return (
<div style={styles.root}>
- <PortalOnboardingFlow trackedTokenStateByAddress={this.state.trackedTokenStateByAddress} />
+ <PortalOnboardingFlow
+ blockchain={this._blockchain}
+ trackedTokenStateByAddress={this.state.trackedTokenStateByAddress}
+ refetchTokenStateAsync={this._refetchTokenStateAsync.bind(this)}
+ />
<DocumentTitle title="0x Portal DApp" />
<TopBar
userAddress={this.props.userAddress}
diff --git a/packages/website/ts/components/token_balances.tsx b/packages/website/ts/components/token_balances.tsx
index 2a051651d..7af80745c 100644
--- a/packages/website/ts/components/token_balances.tsx
+++ b/packages/website/ts/components/token_balances.tsx
@@ -20,11 +20,11 @@ import ReactTooltip = require('react-tooltip');
import firstBy = require('thenby');
import { Blockchain } from 'ts/blockchain';
import { AssetPicker } from 'ts/components/generate_order/asset_picker';
-import { AllowanceToggle } from 'ts/components/inputs/allowance_toggle';
import { SendButton } from 'ts/components/send_button';
import { HelpTooltip } from 'ts/components/ui/help_tooltip';
import { LifeCycleRaisedButton } from 'ts/components/ui/lifecycle_raised_button';
import { TokenIcon } from 'ts/components/ui/token_icon';
+import { AllowanceToggle } from 'ts/containers/inputs/allowance_toggle';
import { trackedTokenStorage } from 'ts/local_storage/tracked_token_storage';
import { Dispatcher } from 'ts/redux/dispatcher';
import {
@@ -362,13 +362,10 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala
</TableRowColumn>
<TableRowColumn>
<AllowanceToggle
- networkId={this.props.networkId}
blockchain={this.props.blockchain}
- dispatcher={this.props.dispatcher}
token={token}
tokenState={tokenState}
onErrorOccurred={this._onErrorOccurred.bind(this)}
- userAddress={this.props.userAddress}
isDisabled={!tokenState.isLoaded}
refetchTokenStateAsync={this._refetchTokenStateAsync.bind(this, token.address)}
/>
diff --git a/packages/website/ts/components/wallet/wallet.tsx b/packages/website/ts/components/wallet/wallet.tsx
index f80be6313..4523b0ac9 100644
--- a/packages/website/ts/components/wallet/wallet.tsx
+++ b/packages/website/ts/components/wallet/wallet.tsx
@@ -13,7 +13,6 @@ import { Link } from 'react-router-dom';
import firstBy = require('thenby');
import { Blockchain } from 'ts/blockchain';
-import { AllowanceToggle } from 'ts/components/inputs/allowance_toggle';
import { Container } from 'ts/components/ui/container';
import { IconButton } from 'ts/components/ui/icon_button';
import { Identicon } from 'ts/components/ui/identicon';
@@ -21,6 +20,7 @@ import { Island } from 'ts/components/ui/island';
import { TokenIcon } from 'ts/components/ui/token_icon';
import { WalletDisconnectedItem } from 'ts/components/wallet/wallet_disconnected_item';
import { WrapEtherItem } from 'ts/components/wallet/wrap_ether_item';
+import { AllowanceToggle } from 'ts/containers/inputs/allowance_toggle';
import { Dispatcher } from 'ts/redux/dispatcher';
import { colors } from 'ts/style/colors';
import { zIndex } from 'ts/style/z_index';
@@ -414,15 +414,12 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
);
}
private _renderAllowanceToggle(config: AllowanceToggleConfig): React.ReactNode {
+ // TODO: Error handling
return (
<AllowanceToggle
- networkId={this.props.networkId}
blockchain={this.props.blockchain}
- dispatcher={this.props.dispatcher}
token={config.token}
tokenState={config.tokenState}
- onErrorOccurred={_.noop} // TODO: Error handling
- userAddress={this.props.userAddress}
isDisabled={!config.tokenState.isLoaded}
refetchTokenStateAsync={async () => this.props.refetchTokenStateAsync(config.token.address)}
/>
diff --git a/packages/website/ts/containers/inputs/allowance_toggle.ts b/packages/website/ts/containers/inputs/allowance_toggle.ts
new file mode 100644
index 000000000..545708f92
--- /dev/null
+++ b/packages/website/ts/containers/inputs/allowance_toggle.ts
@@ -0,0 +1,41 @@
+import * as React from 'react';
+import { connect } from 'react-redux';
+import { Dispatch } from 'redux';
+import { Blockchain } from 'ts/blockchain';
+import { State } from 'ts/redux/reducer';
+import { BalanceErrs, Token, TokenState } from 'ts/types';
+
+import { AllowanceToggle as AllowanceToggleComponent } from 'ts/components/inputs/allowance_toggle';
+import { Dispatcher } from 'ts/redux/dispatcher';
+
+interface AllowanceToggleProps {
+ blockchain: Blockchain;
+ onErrorOccurred?: (errType: BalanceErrs) => void;
+ token: Token;
+ tokenState: TokenState;
+ isDisabled?: boolean;
+ refetchTokenStateAsync: () => Promise<void>;
+}
+
+interface ConnectedState {
+ networkId: number;
+ userAddress: string;
+}
+
+interface ConnectedDispatch {
+ dispatcher: Dispatcher;
+}
+
+const mapStateToProps = (state: State, _ownProps: AllowanceToggleProps): ConnectedState => ({
+ networkId: state.networkId,
+ userAddress: state.userAddress,
+});
+
+const mapDispatchTopProps = (dispatch: Dispatch<State>): ConnectedDispatch => ({
+ dispatcher: new Dispatcher(dispatch),
+});
+
+export const AllowanceToggle: React.ComponentClass<AllowanceToggleProps> = connect(
+ mapStateToProps,
+ mapDispatchTopProps,
+)(AllowanceToggleComponent);
diff --git a/packages/website/ts/containers/portal_onboarding_flow.ts b/packages/website/ts/containers/portal_onboarding_flow.ts
index 4298ab275..746adf0ba 100644
--- a/packages/website/ts/containers/portal_onboarding_flow.ts
+++ b/packages/website/ts/containers/portal_onboarding_flow.ts
@@ -2,6 +2,7 @@ import { BigNumber } from '@0xproject/utils';
import * as React from 'react';
import { connect } from 'react-redux';
import { Dispatch } from 'redux';
+import { Blockchain } from 'ts/blockchain';
import { ActionTypes, ProviderType, TokenByAddress, TokenStateByAddress } from 'ts/types';
import { PortalOnboardingFlow as PortalOnboardingFlowComponent } from 'ts/components/onboarding/portal_onboarding_flow';
@@ -9,6 +10,8 @@ import { State } from 'ts/redux/reducer';
interface PortalOnboardingFlowProps {
trackedTokenStateByAddress: TokenStateByAddress;
+ blockchain: Blockchain;
+ refetchTokenStateAsync: (tokenAddress: string) => Promise<void>;
}
interface ConnectedState {
diff --git a/packages/website/ts/utils/utils.ts b/packages/website/ts/utils/utils.ts
index eb384fbb4..fdee264b2 100644
--- a/packages/website/ts/utils/utils.ts
+++ b/packages/website/ts/utils/utils.ts
@@ -322,8 +322,14 @@ export const utils = {
return this.isDevelopment() || this.isStaging() || this.isDogfood();
},
getEthToken(tokenByAddress: TokenByAddress): Token {
+ return utils.getTokenBySymbol(constants.ETHER_TOKEN_SYMBOL, tokenByAddress);
+ },
+ getZrxToken(tokenByAddress: TokenByAddress): Token {
+ return utils.getTokenBySymbol(constants.ZRX_TOKEN_SYMBOL, tokenByAddress);
+ },
+ getTokenBySymbol(symbol: string, tokenByAddress: TokenByAddress): Token {
const tokens = _.values(tokenByAddress);
- const etherToken = _.find(tokens, { symbol: constants.ETHER_TOKEN_SYMBOL });
- return etherToken;
+ const token = _.find(tokens, { symbol });
+ return token;
},
};