diff options
Diffstat (limited to 'packages')
154 files changed, 2428 insertions, 1908 deletions
diff --git a/packages/0x.js/CHANGELOG.md b/packages/0x.js/CHANGELOG.md index ec44bfc16..83c33ee1c 100644 --- a/packages/0x.js/CHANGELOG.md +++ b/packages/0x.js/CHANGELOG.md @@ -1,10 +1,21 @@ # CHANGELOG -## v0.30.1 - _TBD, 2018_ +## v0.31.0 - _January 30, 2018_ + + * Add the `shouldAddPersonalMessagePrefix` parameter to `signOrderHashAsync` so that the + caller can decide on whether to add the personalMessage prefix before relaying the request + to the signer. Parity Signer, Ledger and TestRPC add the prefix themselves, Metamask expects + it to have already been added. (#349) + +## v0.30.2 - _January 29, 2018_ + + * Add Rinkeby testnet addresses to artifacts (#337) + * Move @0xproject/types to dependencies from devDependencies fixing missing type errors + +## v0.30.1 - _January 24, 2018_ * Fix a bug allowing negative fill values (#212) * Fix a bug that made it impossible to pass a custom ZRX address (#341) - * Add Rinkeby testnet addresses to artifacts (#337) ## v0.30.0 - _January 17, 2018_ diff --git a/packages/0x.js/package.json b/packages/0x.js/package.json index 22ccbbc2d..32063aab2 100644 --- a/packages/0x.js/package.json +++ b/packages/0x.js/package.json @@ -1,6 +1,6 @@ { "name": "0x.js", - "version": "0.30.1", + "version": "0.31.0", "description": "A javascript library for interacting with the 0x protocol", "keywords": ["0x.js", "0xproject", "ethereum", "tokens", "exchange"], "main": "lib/src/index.js", @@ -38,10 +38,9 @@ "node": ">=6.0.0" }, "devDependencies": { - "@0xproject/abi-gen": "^0.1.3", - "@0xproject/dev-utils": "^0.0.6", - "@0xproject/tslint-config": "^0.4.3", - "@0xproject/types": "^0.1.5", + "@0xproject/abi-gen": "^0.1.5", + "@0xproject/dev-utils": "^0.0.8", + "@0xproject/tslint-config": "^0.4.5", "@types/bintrees": "^1.0.2", "@types/jsonschema": "^1.1.1", "@types/lodash": "^4.14.86", @@ -52,7 +51,7 @@ "awesome-typescript-loader": "^3.1.3", "chai": "^4.0.1", "chai-as-promised": "^7.1.0", - "chai-as-promised-typescript-typings": "^0.0.5", + "chai-as-promised-typescript-typings": "^0.0.7", "chai-bignumber": "^2.0.1", "chai-typescript-typings": "^0.0.2", "copyfiles": "^1.2.0", @@ -64,7 +63,7 @@ "nyc": "^11.0.1", "opn-cli": "^3.1.0", "request": "^2.81.0", - "request-promise-native": "^1.0.4", + "request-promise-native": "^1.0.5", "shx": "^0.2.2", "sinon": "^4.0.0", "source-map-support": "^0.5.0", @@ -73,20 +72,21 @@ "typedoc": "~0.8.0", "typescript": "~2.6.1", "web3-provider-engine": "^13.0.1", - "web3-typescript-typings": "^0.9.5", + "web3-typescript-typings": "^0.9.7", "webpack": "^3.1.0" }, "dependencies": { - "@0xproject/assert": "^0.0.12", - "@0xproject/json-schemas": "^0.7.4", - "@0xproject/utils": "^0.2.1", - "@0xproject/web3-wrapper": "^0.1.6", + "@0xproject/assert": "^0.0.14", + "@0xproject/json-schemas": "^0.7.6", + "@0xproject/types": "^0.1.7", + "@0xproject/utils": "^0.2.3", + "@0xproject/web3-wrapper": "^0.1.8", "bintrees": "^1.0.2", "bn.js": "^4.11.8", "ethereumjs-abi": "^0.6.4", "ethereumjs-blockstream": "^2.0.6", "ethereumjs-util": "^5.1.1", - "js-sha3": "^0.6.1", + "js-sha3": "^0.7.0", "lodash": "^4.17.4", "uuid": "^3.1.0", "web3": "^0.20.0" diff --git a/packages/0x.js/src/0x.ts b/packages/0x.js/src/0x.ts index 503a937c4..6cfa65cc2 100644 --- a/packages/0x.js/src/0x.ts +++ b/packages/0x.js/src/0x.ts @@ -1,5 +1,6 @@ import { schemas, SchemaValidator } from '@0xproject/json-schemas'; -import { BigNumber, intervalUtils } from '@0xproject/utils'; +import { TransactionReceiptWithDecodedLogs } from '@0xproject/types'; +import { AbiDecoder, BigNumber, intervalUtils } from '@0xproject/utils'; import { Web3Wrapper } from '@0xproject/web3-wrapper'; import * as ethUtil from 'ethereumjs-util'; import * as _ from 'lodash'; @@ -12,16 +13,7 @@ import { TokenTransferProxyWrapper } from './contract_wrappers/token_transfer_pr import { TokenWrapper } from './contract_wrappers/token_wrapper'; import { OrderStateWatcher } from './order_watcher/order_state_watcher'; import { zeroExConfigSchema } from './schemas/zero_ex_config_schema'; -import { - ECSignature, - Order, - SignedOrder, - TransactionReceiptWithDecodedLogs, - Web3Provider, - ZeroExConfig, - ZeroExError, -} from './types'; -import { AbiDecoder } from './utils/abi_decoder'; +import { ECSignature, Order, SignedOrder, Web3Provider, ZeroExConfig, ZeroExError } from './types'; import { assert } from './utils/assert'; import { constants } from './utils/constants'; import { decorators } from './utils/decorators'; @@ -240,20 +232,22 @@ export class ZeroEx { * @param orderHash Hex encoded orderHash to sign. * @param signerAddress The hex encoded Ethereum address you wish to sign it with. This address * must be available via the Web3.Provider supplied to 0x.js. + * @param shouldAddPersonalMessagePrefix Some signers add the personal message prefix `\x19Ethereum Signed Message` + * themselves (e.g Parity Signer, Ledger, TestRPC) and others expect it to already be done by the client + * (e.g Metamask). Depending on which signer this request is going to, decide on whether to add the prefix + * before sending the request. * @return An object containing the Elliptic curve signature parameters generated by signing the orderHash. */ - public async signOrderHashAsync(orderHash: string, signerAddress: string): Promise<ECSignature> { + public async signOrderHashAsync( + orderHash: string, + signerAddress: string, + shouldAddPersonalMessagePrefix: boolean, + ): Promise<ECSignature> { assert.isHexString('orderHash', orderHash); await assert.isSenderAddressAsync('signerAddress', signerAddress, this._web3Wrapper); - let msgHashHex; - const nodeVersion = await this._web3Wrapper.getNodeVersionAsync(); - const isParityNode = utils.isParityNode(nodeVersion); - const isTestRpc = utils.isTestRpc(nodeVersion); - if (isParityNode || isTestRpc) { - // Parity and TestRpc nodes add the personalMessage prefix itself - msgHashHex = orderHash; - } else { + let msgHashHex = orderHash; + if (shouldAddPersonalMessagePrefix) { const orderHashBuff = ethUtil.toBuffer(orderHash); const msgHashBuff = ethUtil.hashPersonalMessage(orderHashBuff); msgHashHex = ethUtil.bufferToHex(msgHashBuff); @@ -332,8 +326,8 @@ export class ZeroEx { ); }, ); - - return txReceiptPromise; + const txReceipt = await txReceiptPromise; + return txReceipt; } /* * HACK: `TokenWrapper` needs a token transfer proxy address. `TokenTransferProxy` address is fetched from diff --git a/packages/0x.js/src/contract_wrappers/contract_wrapper.ts b/packages/0x.js/src/contract_wrappers/contract_wrapper.ts index 27551c01d..5f11d810a 100644 --- a/packages/0x.js/src/contract_wrappers/contract_wrapper.ts +++ b/packages/0x.js/src/contract_wrappers/contract_wrapper.ts @@ -1,4 +1,5 @@ -import { intervalUtils } from '@0xproject/utils'; +import { LogWithDecodedArgs, RawLog } from '@0xproject/types'; +import { AbiDecoder, intervalUtils } from '@0xproject/utils'; import { Web3Wrapper } from '@0xproject/web3-wrapper'; import { Block, BlockAndLogStreamer } from 'ethereumjs-blockstream'; import * as _ from 'lodash'; @@ -13,11 +14,8 @@ import { EventCallback, IndexedFilterValues, InternalZeroExError, - LogWithDecodedArgs, - RawLog, ZeroExError, } from '../types'; -import { AbiDecoder } from '../utils/abi_decoder'; import { constants } from '../utils/constants'; import { filterUtils } from '../utils/filter_utils'; diff --git a/packages/0x.js/src/contract_wrappers/ether_token_wrapper.ts b/packages/0x.js/src/contract_wrappers/ether_token_wrapper.ts index b03571636..8fa7aa78b 100644 --- a/packages/0x.js/src/contract_wrappers/ether_token_wrapper.ts +++ b/packages/0x.js/src/contract_wrappers/ether_token_wrapper.ts @@ -1,5 +1,6 @@ import { schemas } from '@0xproject/json-schemas'; -import { BigNumber } from '@0xproject/utils'; +import { LogWithDecodedArgs } from '@0xproject/types'; +import { AbiDecoder, BigNumber } from '@0xproject/utils'; import { Web3Wrapper } from '@0xproject/web3-wrapper'; import * as _ from 'lodash'; @@ -10,11 +11,9 @@ import { EtherTokenEvents, EventCallback, IndexedFilterValues, - LogWithDecodedArgs, TransactionOpts, ZeroExError, } from '../types'; -import { AbiDecoder } from '../utils/abi_decoder'; import { assert } from '../utils/assert'; import { ContractWrapper } from './contract_wrapper'; diff --git a/packages/0x.js/src/contract_wrappers/exchange_wrapper.ts b/packages/0x.js/src/contract_wrappers/exchange_wrapper.ts index 2b6117729..fceab851a 100644 --- a/packages/0x.js/src/contract_wrappers/exchange_wrapper.ts +++ b/packages/0x.js/src/contract_wrappers/exchange_wrapper.ts @@ -1,5 +1,6 @@ import { schemas } from '@0xproject/json-schemas'; -import { BigNumber } from '@0xproject/utils'; +import { DecodedLogArgs, LogWithDecodedArgs } from '@0xproject/types'; +import { AbiDecoder, BigNumber } from '@0xproject/utils'; import { Web3Wrapper } from '@0xproject/web3-wrapper'; import * as _ from 'lodash'; import * as Web3 from 'web3'; @@ -8,7 +9,6 @@ import { artifacts } from '../artifacts'; import { BlockParamLiteral, BlockRange, - DecodedLogArgs, ECSignature, EventCallback, ExchangeContractErrCodes, @@ -17,7 +17,6 @@ import { ExchangeEvents, IndexedFilterValues, LogErrorContractEventArgs, - LogWithDecodedArgs, MethodOpts, Order, OrderAddresses, @@ -28,7 +27,6 @@ import { SignedOrder, ValidateOrderFillableOpts, } from '../types'; -import { AbiDecoder } from '../utils/abi_decoder'; import { assert } from '../utils/assert'; import { decorators } from '../utils/decorators'; import { ExchangeTransferSimulator } from '../utils/exchange_transfer_simulator'; @@ -846,9 +844,9 @@ export class ExchangeWrapper extends ContractWrapper { public throwLogErrorsAsErrors(logs: Array<LogWithDecodedArgs<DecodedLogArgs> | Web3.LogEntry>): void { const errLog = _.find(logs, { event: ExchangeEvents.LogError, - }) as LogWithDecodedArgs<LogErrorContractEventArgs> | undefined; + }); if (!_.isUndefined(errLog)) { - const logArgs = errLog.args; + const logArgs = (errLog as LogWithDecodedArgs<LogErrorContractEventArgs>).args; const errCode = logArgs.errorId.toNumber(); const errMessage = this._exchangeContractErrCodesToMsg[errCode]; throw new Error(errMessage); diff --git a/packages/0x.js/src/contract_wrappers/token_wrapper.ts b/packages/0x.js/src/contract_wrappers/token_wrapper.ts index 7943f4a60..4216ff462 100644 --- a/packages/0x.js/src/contract_wrappers/token_wrapper.ts +++ b/packages/0x.js/src/contract_wrappers/token_wrapper.ts @@ -1,5 +1,6 @@ import { schemas } from '@0xproject/json-schemas'; -import { BigNumber } from '@0xproject/utils'; +import { LogWithDecodedArgs } from '@0xproject/types'; +import { AbiDecoder, BigNumber } from '@0xproject/utils'; import { Web3Wrapper } from '@0xproject/web3-wrapper'; import * as _ from 'lodash'; @@ -8,14 +9,12 @@ import { BlockRange, EventCallback, IndexedFilterValues, - LogWithDecodedArgs, MethodOpts, TokenContractEventArgs, TokenEvents, TransactionOpts, ZeroExError, } from '../types'; -import { AbiDecoder } from '../utils/abi_decoder'; import { assert } from '../utils/assert'; import { constants } from '../utils/constants'; diff --git a/packages/0x.js/src/globals.d.ts b/packages/0x.js/src/globals.d.ts index 4f4932b6e..0e103d057 100644 --- a/packages/0x.js/src/globals.d.ts +++ b/packages/0x.js/src/globals.d.ts @@ -41,19 +41,3 @@ declare module 'truffle-hdwallet-provider' { } export = HDWalletProvider; } - -// abi-decoder declarations -interface DecodedLogArg {} -interface DecodedLog { - name: string; - events: DecodedLogArg[]; -} -declare module 'abi-decoder' { - import * as Web3 from 'web3'; - const addABI: (abi: Web3.AbiDefinition) => void; - const decodeLogs: (logs: Web3.LogEntry[]) => DecodedLog[]; -} - -declare module 'web3/lib/solidity/coder' { - const decodeParams: (types: string[], data: string) => any[]; -} diff --git a/packages/0x.js/src/index.ts b/packages/0x.js/src/index.ts index 599c3a2b0..41e67e177 100644 --- a/packages/0x.js/src/index.ts +++ b/packages/0x.js/src/index.ts @@ -28,12 +28,9 @@ export { WithdrawalContractEventArgs, DepositContractEventArgs, ContractEventArgs, - ContractEventArg, Web3Provider, ZeroExConfig, EtherTokenEvents, - TransactionReceiptWithDecodedLogs, - LogWithDecodedArgs, MethodOpts, OrderTransactionOpts, TransactionOpts, @@ -47,4 +44,6 @@ export { OrderState, } from './types'; +export { ContractEventArg, LogWithDecodedArgs, TransactionReceiptWithDecodedLogs } from '@0xproject/types'; + export { TransactionReceipt } from '@0xproject/types'; diff --git a/packages/0x.js/src/order_watcher/order_state_watcher.ts b/packages/0x.js/src/order_watcher/order_state_watcher.ts index 12ac60960..576be00c8 100644 --- a/packages/0x.js/src/order_watcher/order_state_watcher.ts +++ b/packages/0x.js/src/order_watcher/order_state_watcher.ts @@ -1,5 +1,6 @@ import { schemas } from '@0xproject/json-schemas'; -import { intervalUtils } from '@0xproject/utils'; +import { LogWithDecodedArgs } from '@0xproject/types'; +import { AbiDecoder, intervalUtils } from '@0xproject/utils'; import { Web3Wrapper } from '@0xproject/web3-wrapper'; import * as _ from 'lodash'; @@ -19,7 +20,6 @@ import { LogCancelContractEventArgs, LogEvent, LogFillContractEventArgs, - LogWithDecodedArgs, OnOrderStateChangeCallback, OrderState, OrderStateWatcherConfig, @@ -29,7 +29,6 @@ import { WithdrawalContractEventArgs, ZeroExError, } from '../types'; -import { AbiDecoder } from '../utils/abi_decoder'; import { assert } from '../utils/assert'; import { OrderStateUtils } from '../utils/order_state_utils'; import { utils } from '../utils/utils'; @@ -224,12 +223,12 @@ export class OrderStateWatcher { return; } const log = logIfExists as LogEvent; // At this moment we are sure that no error occured and log is defined. - const maybeDecodedLog = this._abiDecoder.tryToDecodeLogOrNoop(log); - const isLogDecoded = !_.isUndefined((maybeDecodedLog as LogWithDecodedArgs<any>).event); + const maybeDecodedLog = this._abiDecoder.tryToDecodeLogOrNoop<ContractEventArgs>(log); + const isLogDecoded = !_.isUndefined(((maybeDecodedLog as any) as LogWithDecodedArgs<ContractEventArgs>).event); if (!isLogDecoded) { return; // noop } - const decodedLog = maybeDecodedLog as LogWithDecodedArgs<ContractEventArgs>; + const decodedLog = (maybeDecodedLog as any) as LogWithDecodedArgs<ContractEventArgs>; let makerToken: string; let makerAddress: string; switch (decodedLog.event) { diff --git a/packages/0x.js/src/types.ts b/packages/0x.js/src/types.ts index 3c93910e9..a0deb91c9 100644 --- a/packages/0x.js/src/types.ts +++ b/packages/0x.js/src/types.ts @@ -1,4 +1,4 @@ -import { TransactionReceipt } from '@0xproject/types'; +import { ContractEventArg, LogWithDecodedArgs } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; import * as Web3 from 'web3'; @@ -53,13 +53,6 @@ export interface DecodedLogEvent<ArgsType> { export type EventCallback<ArgsType> = (err: null | Error, log?: DecodedLogEvent<ArgsType>) => void; export type EventWatcherCallback = (err: null | Error, log?: LogEvent) => void; -export enum SolidityTypes { - Address = 'address', - Uint256 = 'uint256', - Uint8 = 'uint8', - Uint = 'uint', -} - export enum ExchangeContractErrCodes { ERROR_FILL_EXPIRED, // Order has already expired ERROR_FILL_NO_VALUE, // Order has already been fully filled or cancelled @@ -94,8 +87,6 @@ export enum ExchangeContractErrs { BatchOrdersMustHaveAtLeastOneItem = 'BATCH_ORDERS_MUST_HAVE_AT_LEAST_ONE_ITEM', } -export type RawLog = Web3.LogEntry; - export interface ContractEvent { logIndex: number; transactionIndex: number; @@ -163,7 +154,6 @@ export type EtherTokenContractEventArgs = | DepositContractEventArgs | WithdrawalContractEventArgs; export type ContractEventArgs = ExchangeContractEventArgs | TokenContractEventArgs | EtherTokenContractEventArgs; -export type ContractEventArg = string | BigNumber; export interface Order { maker: string; @@ -267,11 +257,6 @@ export type SyncMethod = (...args: any[]) => any; */ export type Web3Provider = Web3.Provider; -export interface JSONRPCPayload { - params: any[]; - method: string; -} - /* * orderExpirationCheckingIntervalMs: How often to check for expired orders. Default: 50 * eventPollingIntervalMs: How often to poll the Ethereum node for new events. Defaults: 200 @@ -305,23 +290,6 @@ export interface ZeroExConfig { orderWatcherConfig?: OrderStateWatcherConfig; } -export enum AbiType { - Function = 'function', - Constructor = 'constructor', - Event = 'event', - Fallback = 'fallback', -} - -export interface DecodedLogArgs { - [argName: string]: ContractEventArg; -} - -export interface LogWithDecodedArgs<ArgsType> extends Web3.DecodedLogEntry<ArgsType> {} - -export interface TransactionReceiptWithDecodedLogs extends TransactionReceipt { - logs: Array<LogWithDecodedArgs<DecodedLogArgs> | Web3.LogEntry>; -} - export type ArtifactContractName = 'ZRX' | 'TokenTransferProxy' | 'TokenRegistry' | 'Token' | 'Exchange' | 'EtherToken'; export interface Artifact { diff --git a/packages/0x.js/src/utils/utils.ts b/packages/0x.js/src/utils/utils.ts index e42a1a853..74f2c5995 100644 --- a/packages/0x.js/src/utils/utils.ts +++ b/packages/0x.js/src/utils/utils.ts @@ -1,10 +1,11 @@ +import { SolidityTypes } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; import BN = require('bn.js'); import * as ethABI from 'ethereumjs-abi'; import * as ethUtil from 'ethereumjs-util'; import * as _ from 'lodash'; -import { Order, SignedOrder, SolidityTypes } from '../types'; +import { Order, SignedOrder } from '../types'; export const utils = { /** @@ -20,12 +21,6 @@ export const utils = { // tslint:disable-next-line: no-console console.log(message); }, - isParityNode(nodeVersion: string): boolean { - return _.includes(nodeVersion, 'Parity'); - }, - isTestRpc(nodeVersion: string): boolean { - return _.includes(nodeVersion, 'TestRPC'); - }, spawnSwitchErr(name: string, value: any): Error { return new Error(`Unexpected switch value: ${value} encountered for ${name}`); }, diff --git a/packages/0x.js/test/0x.js_test.ts b/packages/0x.js/test/0x.js_test.ts index 5bc2c149c..5ebb68c8c 100644 --- a/packages/0x.js/test/0x.js_test.ts +++ b/packages/0x.js/test/0x.js_test.ts @@ -1,4 +1,4 @@ -import { BlockchainLifecycle } from '@0xproject/dev-utils'; +import { BlockchainLifecycle, devConstants, web3Factory } from '@0xproject/dev-utils'; import { BigNumber } from '@0xproject/utils'; import * as chai from 'chai'; import * as _ from 'lodash'; @@ -10,12 +10,13 @@ import { ApprovalContractEventArgs, LogWithDecodedArgs, Order, TokenEvents, Zero import { chaiSetup } from './utils/chai_setup'; import { constants } from './utils/constants'; import { TokenUtils } from './utils/token_utils'; -import { web3Factory } from './utils/web3_factory'; -const blockchainLifecycle = new BlockchainLifecycle(constants.RPC_URL); +const blockchainLifecycle = new BlockchainLifecycle(); chaiSetup.configure(); const expect = chai.expect; +const SHOULD_ADD_PERSONAL_MESSAGE_PREFIX = false; + describe('ZeroEx library', () => { const web3 = web3Factory.create(); const config = { @@ -198,7 +199,11 @@ describe('ZeroEx library', () => { r: '0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33', s: '0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254', }; - const ecSignature = await zeroEx.signOrderHashAsync(orderHash, makerAddress); + const ecSignature = await zeroEx.signOrderHashAsync( + orderHash, + makerAddress, + SHOULD_ADD_PERSONAL_MESSAGE_PREFIX, + ); expect(ecSignature).to.deep.equal(expectedECSignature); }); it('should return the correct ECSignature for signatureHex concatenated as R + S + V', async () => { @@ -215,7 +220,11 @@ describe('ZeroEx library', () => { Sinon.stub(ZeroEx, 'isValidSignature').returns(true), ]; - const ecSignature = await zeroEx.signOrderHashAsync(orderHash, makerAddress); + const ecSignature = await zeroEx.signOrderHashAsync( + orderHash, + makerAddress, + SHOULD_ADD_PERSONAL_MESSAGE_PREFIX, + ); expect(ecSignature).to.deep.equal(expectedECSignature); }); it('should return the correct ECSignature for signatureHex concatenated as V + R + S', async () => { @@ -232,7 +241,11 @@ describe('ZeroEx library', () => { Sinon.stub(ZeroEx, 'isValidSignature').returns(true), ]; - const ecSignature = await zeroEx.signOrderHashAsync(orderHash, makerAddress); + const ecSignature = await zeroEx.signOrderHashAsync( + orderHash, + makerAddress, + SHOULD_ADD_PERSONAL_MESSAGE_PREFIX, + ); expect(ecSignature).to.deep.equal(expectedECSignature); }); }); diff --git a/packages/0x.js/test/assert_test.ts b/packages/0x.js/test/assert_test.ts index 1f2820070..c4451742f 100644 --- a/packages/0x.js/test/assert_test.ts +++ b/packages/0x.js/test/assert_test.ts @@ -1,3 +1,4 @@ +import { web3Factory } from '@0xproject/dev-utils'; import * as chai from 'chai'; import 'mocha'; @@ -5,7 +6,6 @@ import { ZeroEx } from '../src'; import { assert } from '../src/utils/assert'; import { constants } from './utils/constants'; -import { web3Factory } from './utils/web3_factory'; const expect = chai.expect; diff --git a/packages/0x.js/test/ether_token_wrapper_test.ts b/packages/0x.js/test/ether_token_wrapper_test.ts index b810fc9f1..6069b42bf 100644 --- a/packages/0x.js/test/ether_token_wrapper_test.ts +++ b/packages/0x.js/test/ether_token_wrapper_test.ts @@ -1,4 +1,4 @@ -import { BlockchainLifecycle } from '@0xproject/dev-utils'; +import { BlockchainLifecycle, devConstants, web3Factory } from '@0xproject/dev-utils'; import { BigNumber } from '@0xproject/utils'; import * as chai from 'chai'; import 'mocha'; @@ -24,11 +24,10 @@ import { chaiSetup } from './utils/chai_setup'; import { constants } from './utils/constants'; import { reportNodeCallbackErrors } from './utils/report_callback_errors'; import { TokenUtils } from './utils/token_utils'; -import { web3Factory } from './utils/web3_factory'; chaiSetup.configure(); const expect = chai.expect; -const blockchainLifecycle = new BlockchainLifecycle(constants.RPC_URL); +const blockchainLifecycle = new BlockchainLifecycle(); // Since the address depositing/withdrawing ETH/WETH also needs to pay gas costs for the transaction, // a small amount of ETH will be used to pay this gas cost. We therefore check that the difference between diff --git a/packages/0x.js/test/event_watcher_test.ts b/packages/0x.js/test/event_watcher_test.ts index f92fb2b02..93ee9cd1c 100644 --- a/packages/0x.js/test/event_watcher_test.ts +++ b/packages/0x.js/test/event_watcher_test.ts @@ -1,3 +1,4 @@ +import { web3Factory } from '@0xproject/dev-utils'; import { Web3Wrapper } from '@0xproject/web3-wrapper'; import * as chai from 'chai'; import * as _ from 'lodash'; @@ -11,7 +12,6 @@ import { DoneCallback } from '../src/types'; import { chaiSetup } from './utils/chai_setup'; import { reportNodeCallbackErrors } from './utils/report_callback_errors'; -import { web3Factory } from './utils/web3_factory'; chaiSetup.configure(); const expect = chai.expect; diff --git a/packages/0x.js/test/exchange_transfer_simulator_test.ts b/packages/0x.js/test/exchange_transfer_simulator_test.ts index 20b4a05ca..e85a1640f 100644 --- a/packages/0x.js/test/exchange_transfer_simulator_test.ts +++ b/packages/0x.js/test/exchange_transfer_simulator_test.ts @@ -1,4 +1,4 @@ -import { BlockchainLifecycle } from '@0xproject/dev-utils'; +import { BlockchainLifecycle, devConstants, web3Factory } from '@0xproject/dev-utils'; import { BigNumber } from '@0xproject/utils'; import * as chai from 'chai'; @@ -8,11 +8,10 @@ import { ExchangeTransferSimulator } from '../src/utils/exchange_transfer_simula import { chaiSetup } from './utils/chai_setup'; import { constants } from './utils/constants'; -import { web3Factory } from './utils/web3_factory'; chaiSetup.configure(); const expect = chai.expect; -const blockchainLifecycle = new BlockchainLifecycle(constants.RPC_URL); +const blockchainLifecycle = new BlockchainLifecycle(); describe('ExchangeTransferSimulator', () => { const web3 = web3Factory.create(); diff --git a/packages/0x.js/test/exchange_wrapper_test.ts b/packages/0x.js/test/exchange_wrapper_test.ts index 7e0ffd818..c15cd65a9 100644 --- a/packages/0x.js/test/exchange_wrapper_test.ts +++ b/packages/0x.js/test/exchange_wrapper_test.ts @@ -1,4 +1,4 @@ -import { BlockchainLifecycle } from '@0xproject/dev-utils'; +import { BlockchainLifecycle, devConstants, web3Factory } from '@0xproject/dev-utils'; import { BigNumber } from '@0xproject/utils'; import * as chai from 'chai'; import * as _ from 'lodash'; @@ -25,11 +25,10 @@ import { constants } from './utils/constants'; import { FillScenarios } from './utils/fill_scenarios'; import { reportNodeCallbackErrors } from './utils/report_callback_errors'; import { TokenUtils } from './utils/token_utils'; -import { web3Factory } from './utils/web3_factory'; chaiSetup.configure(); const expect = chai.expect; -const blockchainLifecycle = new BlockchainLifecycle(constants.RPC_URL); +const blockchainLifecycle = new BlockchainLifecycle(); const NON_EXISTENT_ORDER_HASH = '0x79370342234e7acd6bbeac335bd3bb1d368383294b64b8160a00f4060e4d3777'; diff --git a/packages/0x.js/test/expiration_watcher_test.ts b/packages/0x.js/test/expiration_watcher_test.ts index 770615f88..b49dee8e5 100644 --- a/packages/0x.js/test/expiration_watcher_test.ts +++ b/packages/0x.js/test/expiration_watcher_test.ts @@ -1,4 +1,4 @@ -import { BlockchainLifecycle } from '@0xproject/dev-utils'; +import { BlockchainLifecycle, devConstants, web3Factory } from '@0xproject/dev-utils'; import { BigNumber } from '@0xproject/utils'; import * as chai from 'chai'; import * as _ from 'lodash'; @@ -13,15 +13,13 @@ import { constants } from '../src/utils/constants'; import { utils } from '../src/utils/utils'; import { chaiSetup } from './utils/chai_setup'; -import { constants as testConstants } from './utils/constants'; import { FillScenarios } from './utils/fill_scenarios'; import { reportNoErrorCallbackErrors } from './utils/report_callback_errors'; import { TokenUtils } from './utils/token_utils'; -import { web3Factory } from './utils/web3_factory'; chaiSetup.configure(); const expect = chai.expect; -const blockchainLifecycle = new BlockchainLifecycle(testConstants.RPC_URL); +const blockchainLifecycle = new BlockchainLifecycle(); describe('ExpirationWatcher', () => { let web3: Web3; diff --git a/packages/0x.js/test/order_state_watcher_test.ts b/packages/0x.js/test/order_state_watcher_test.ts index 2e9202fe2..9e2ad89e1 100644 --- a/packages/0x.js/test/order_state_watcher_test.ts +++ b/packages/0x.js/test/order_state_watcher_test.ts @@ -1,4 +1,4 @@ -import { BlockchainLifecycle } from '@0xproject/dev-utils'; +import { BlockchainLifecycle, devConstants, web3Factory } from '@0xproject/dev-utils'; import { BigNumber } from '@0xproject/utils'; import * as chai from 'chai'; import * as _ from 'lodash'; @@ -22,13 +22,12 @@ import { constants } from './utils/constants'; import { FillScenarios } from './utils/fill_scenarios'; import { reportNodeCallbackErrors } from './utils/report_callback_errors'; import { TokenUtils } from './utils/token_utils'; -import { web3Factory } from './utils/web3_factory'; const TIMEOUT_MS = 150; chaiSetup.configure(); const expect = chai.expect; -const blockchainLifecycle = new BlockchainLifecycle(constants.RPC_URL); +const blockchainLifecycle = new BlockchainLifecycle(); describe('OrderStateWatcher', () => { let web3: Web3; diff --git a/packages/0x.js/test/order_validation_test.ts b/packages/0x.js/test/order_validation_test.ts index be3e0590c..934e2e51f 100644 --- a/packages/0x.js/test/order_validation_test.ts +++ b/packages/0x.js/test/order_validation_test.ts @@ -1,4 +1,4 @@ -import { BlockchainLifecycle } from '@0xproject/dev-utils'; +import { BlockchainLifecycle, devConstants, web3Factory } from '@0xproject/dev-utils'; import { BigNumber } from '@0xproject/utils'; import * as chai from 'chai'; import * as Sinon from 'sinon'; @@ -13,11 +13,10 @@ import { chaiSetup } from './utils/chai_setup'; import { constants } from './utils/constants'; import { FillScenarios } from './utils/fill_scenarios'; import { TokenUtils } from './utils/token_utils'; -import { web3Factory } from './utils/web3_factory'; chaiSetup.configure(); const expect = chai.expect; -const blockchainLifecycle = new BlockchainLifecycle(constants.RPC_URL); +const blockchainLifecycle = new BlockchainLifecycle(); describe('OrderValidation', () => { let web3: Web3; diff --git a/packages/0x.js/test/subscription_test.ts b/packages/0x.js/test/subscription_test.ts index f4c6f748f..f485bf84b 100644 --- a/packages/0x.js/test/subscription_test.ts +++ b/packages/0x.js/test/subscription_test.ts @@ -1,4 +1,4 @@ -import { BlockchainLifecycle } from '@0xproject/dev-utils'; +import { BlockchainLifecycle, devConstants, web3Factory } from '@0xproject/dev-utils'; import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; import 'mocha'; @@ -11,10 +11,9 @@ import { DoneCallback } from '../src/types'; import { chaiSetup } from './utils/chai_setup'; import { constants } from './utils/constants'; import { assertNodeCallbackError } from './utils/report_callback_errors'; -import { web3Factory } from './utils/web3_factory'; chaiSetup.configure(); -const blockchainLifecycle = new BlockchainLifecycle(constants.RPC_URL); +const blockchainLifecycle = new BlockchainLifecycle(); describe('SubscriptionTest', () => { let web3: Web3; diff --git a/packages/0x.js/test/token_registry_wrapper_test.ts b/packages/0x.js/test/token_registry_wrapper_test.ts index 0a170db8f..fefb99b16 100644 --- a/packages/0x.js/test/token_registry_wrapper_test.ts +++ b/packages/0x.js/test/token_registry_wrapper_test.ts @@ -1,4 +1,4 @@ -import { BlockchainLifecycle } from '@0xproject/dev-utils'; +import { BlockchainLifecycle, devConstants, web3Factory } from '@0xproject/dev-utils'; import { schemas, SchemaValidator } from '@0xproject/json-schemas'; import * as chai from 'chai'; import * as _ from 'lodash'; @@ -8,11 +8,10 @@ import { Token, ZeroEx } from '../src'; import { chaiSetup } from './utils/chai_setup'; import { constants } from './utils/constants'; -import { web3Factory } from './utils/web3_factory'; chaiSetup.configure(); const expect = chai.expect; -const blockchainLifecycle = new BlockchainLifecycle(constants.RPC_URL); +const blockchainLifecycle = new BlockchainLifecycle(); const TOKEN_REGISTRY_SIZE_AFTER_MIGRATION = 7; diff --git a/packages/0x.js/test/token_transfer_proxy_wrapper_test.ts b/packages/0x.js/test/token_transfer_proxy_wrapper_test.ts index 15bd7a8ba..dc9ec2064 100644 --- a/packages/0x.js/test/token_transfer_proxy_wrapper_test.ts +++ b/packages/0x.js/test/token_transfer_proxy_wrapper_test.ts @@ -1,10 +1,10 @@ +import { web3Factory } from '@0xproject/dev-utils'; import * as chai from 'chai'; import { ZeroEx } from '../src'; import { chaiSetup } from './utils/chai_setup'; import { constants } from './utils/constants'; -import { web3Factory } from './utils/web3_factory'; chaiSetup.configure(); const expect = chai.expect; diff --git a/packages/0x.js/test/token_wrapper_test.ts b/packages/0x.js/test/token_wrapper_test.ts index 4ba1f07c5..070d6ec47 100644 --- a/packages/0x.js/test/token_wrapper_test.ts +++ b/packages/0x.js/test/token_wrapper_test.ts @@ -1,4 +1,4 @@ -import { BlockchainLifecycle } from '@0xproject/dev-utils'; +import { BlockchainLifecycle, devConstants, web3Factory } from '@0xproject/dev-utils'; import { BigNumber } from '@0xproject/utils'; import { Web3Wrapper } from '@0xproject/web3-wrapper'; import * as chai from 'chai'; @@ -22,11 +22,10 @@ import { chaiSetup } from './utils/chai_setup'; import { constants } from './utils/constants'; import { reportNodeCallbackErrors } from './utils/report_callback_errors'; import { TokenUtils } from './utils/token_utils'; -import { web3Factory } from './utils/web3_factory'; chaiSetup.configure(); const expect = chai.expect; -const blockchainLifecycle = new BlockchainLifecycle(constants.RPC_URL); +const blockchainLifecycle = new BlockchainLifecycle(); describe('TokenWrapper', () => { let web3: Web3; diff --git a/packages/0x.js/test/utils/constants.ts b/packages/0x.js/test/utils/constants.ts index a9e665c25..cf030259c 100644 --- a/packages/0x.js/test/utils/constants.ts +++ b/packages/0x.js/test/utils/constants.ts @@ -1,11 +1,9 @@ export const constants = { NULL_ADDRESS: '0x0000000000000000000000000000000000000000', - RPC_URL: 'http://localhost:8545', ROPSTEN_NETWORK_ID: 3, KOVAN_NETWORK_ID: 42, TESTRPC_NETWORK_ID: 50, KOVAN_RPC_URL: 'https://kovan.infura.io/', ROPSTEN_RPC_URL: 'https://ropsten.infura.io/', ZRX_DECIMALS: 18, - GAS_ESTIMATE: 500000, }; diff --git a/packages/0x.js/test/utils/order_factory.ts b/packages/0x.js/test/utils/order_factory.ts index 60a7c5fb2..08f2081a4 100644 --- a/packages/0x.js/test/utils/order_factory.ts +++ b/packages/0x.js/test/utils/order_factory.ts @@ -3,6 +3,8 @@ import * as _ from 'lodash'; import { SignedOrder, ZeroEx } from '../../src'; +const SHOULD_ADD_PERSONAL_MESSAGE_PREFIX = false; + export const orderFactory = { async createSignedOrderAsync( zeroEx: ZeroEx, @@ -37,7 +39,7 @@ export const orderFactory = { expirationUnixTimestampSec, }; const orderHash = ZeroEx.getOrderHashHex(order); - const ecSignature = await zeroEx.signOrderHashAsync(orderHash, maker); + const ecSignature = await zeroEx.signOrderHashAsync(orderHash, maker, SHOULD_ADD_PERSONAL_MESSAGE_PREFIX); const signedOrder: SignedOrder = _.assign(order, { ecSignature }); return signedOrder; }, diff --git a/packages/abi-gen/package.json b/packages/abi-gen/package.json index 8d2dee18a..b43df00fe 100644 --- a/packages/abi-gen/package.json +++ b/packages/abi-gen/package.json @@ -1,6 +1,6 @@ { "name": "@0xproject/abi-gen", - "version": "0.1.3", + "version": "0.1.5", "description": "Generate contract wrappers from ABI and handlebars templates", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -22,7 +22,7 @@ }, "homepage": "https://github.com/0xProject/0x.js/packages/abi-gen/README.md", "dependencies": { - "@0xproject/utils": "^0.2.1", + "@0xproject/utils": "^0.2.3", "chalk": "^2.3.0", "glob": "^7.1.2", "handlebars": "^4.0.11", @@ -33,7 +33,7 @@ "yargs": "^10.0.3" }, "devDependencies": { - "@0xproject/tslint-config": "^0.4.3", + "@0xproject/tslint-config": "^0.4.5", "@types/glob": "^5.0.33", "@types/handlebars": "^4.0.36", "@types/mkdirp": "^0.5.1", @@ -43,6 +43,6 @@ "shx": "^0.2.2", "tslint": "5.8.0", "typescript": "~2.6.1", - "web3-typescript-typings": "^0.9.5" + "web3-typescript-typings": "^0.9.7" } } diff --git a/packages/assert/package.json b/packages/assert/package.json index 1c25b4e40..fa2de5c0a 100644 --- a/packages/assert/package.json +++ b/packages/assert/package.json @@ -1,6 +1,6 @@ { "name": "@0xproject/assert", - "version": "0.0.12", + "version": "0.0.14", "description": "Provides a standard way of performing type and schema validation across 0x projects", "main": "lib/src/index.js", "types": "lib/src/index.d.ts", @@ -23,7 +23,7 @@ }, "homepage": "https://github.com/0xProject/0x.js/packages/assert/README.md", "devDependencies": { - "@0xproject/tslint-config": "^0.4.3", + "@0xproject/tslint-config": "^0.4.5", "@types/lodash": "^4.14.86", "@types/mocha": "^2.2.42", "@types/valid-url": "^1.0.2", @@ -37,8 +37,8 @@ "typescript": "~2.6.1" }, "dependencies": { - "@0xproject/json-schemas": "^0.7.4", - "@0xproject/utils": "^0.2.1", + "@0xproject/json-schemas": "^0.7.6", + "@0xproject/utils": "^0.2.3", "lodash": "^4.17.4", "valid-url": "^1.0.9" } diff --git a/packages/assert/tsconfig.json b/packages/assert/tsconfig.json index 88a467ccb..10354fa33 100644 --- a/packages/assert/tsconfig.json +++ b/packages/assert/tsconfig.json @@ -3,5 +3,10 @@ "compilerOptions": { "outDir": "lib" }, - "include": ["./src/**/*", "./test/**/*", "../../node_modules/chai-typescript-typings/index.d.ts"] + "include": [ + "./src/**/*", + "./test/**/*", + "../../node_modules/web3-typescript-typings/index.d.ts", + "../../node_modules/chai-typescript-typings/index.d.ts" + ] } diff --git a/packages/chai-as-promised-typescript-typings/package.json b/packages/chai-as-promised-typescript-typings/package.json index 20383594f..5c57a0e04 100644 --- a/packages/chai-as-promised-typescript-typings/package.json +++ b/packages/chai-as-promised-typescript-typings/package.json @@ -1,6 +1,6 @@ { "name": "chai-as-promised-typescript-typings", - "version": "0.0.5", + "version": "0.0.7", "description": "Typescript type definitions for chai-as-promised", "main": "index.d.ts", "types": "index.d.ts", @@ -16,6 +16,6 @@ }, "homepage": "https://github.com/0xProject/0x.js/packages/chai-as-promised-typescript-typings#readme", "dependencies": { - "chai-typescript-typings": "^0.0.0" + "chai-typescript-typings": "^0.0.2" } } diff --git a/packages/connect/package.json b/packages/connect/package.json index fe28be21d..2dbd97dc4 100644 --- a/packages/connect/package.json +++ b/packages/connect/package.json @@ -1,6 +1,6 @@ { "name": "@0xproject/connect", - "version": "0.5.1", + "version": "0.5.3", "description": "A javascript library for interacting with the standard relayer api", "keywords": ["connect", "0xproject", "ethereum", "tokens", "exchange"], "main": "lib/src/index.js", @@ -31,16 +31,16 @@ }, "homepage": "https://github.com/0xProject/0x.js/packages/connect/README.md", "dependencies": { - "@0xproject/assert": "^0.0.12", - "@0xproject/json-schemas": "^0.7.4", - "@0xproject/utils": "^0.2.1", + "@0xproject/assert": "^0.0.14", + "@0xproject/json-schemas": "^0.7.6", + "@0xproject/utils": "^0.2.3", "isomorphic-fetch": "^2.2.1", "lodash": "^4.17.4", "query-string": "^5.0.1", "websocket": "^1.0.25" }, "devDependencies": { - "@0xproject/tslint-config": "^0.4.3", + "@0xproject/tslint-config": "^0.4.5", "@types/fetch-mock": "^5.12.1", "@types/lodash": "^4.14.86", "@types/mocha": "^2.2.42", @@ -48,7 +48,7 @@ "@types/websocket": "^0.0.34", "chai": "^4.0.1", "chai-as-promised": "^7.1.0", - "chai-as-promised-typescript-typings": "^0.0.5", + "chai-as-promised-typescript-typings": "^0.0.7", "chai-typescript-typings": "^0.0.2", "copyfiles": "^1.2.0", "dirty-chai": "^2.0.1", @@ -59,6 +59,6 @@ "tslint": "5.8.0", "typedoc": "~0.8.0", "typescript": "~2.6.1", - "web3-typescript-typings": "^0.9.5" + "web3-typescript-typings": "^0.9.7" } } diff --git a/packages/connect/tsconfig.json b/packages/connect/tsconfig.json index 3c150236e..fd9e604ad 100644 --- a/packages/connect/tsconfig.json +++ b/packages/connect/tsconfig.json @@ -6,6 +6,7 @@ "include": [ "./src/**/*", "./test/**/*", + "../../node_modules/web3-typescript-typings/index.d.ts", "../../node_modules/chai-as-promised-typescript-typings/index.d.ts", "../../node_modules/chai-typescript-typings/index.d.ts" ] diff --git a/packages/contracts/contracts/Exchange.sol b/packages/contracts/contracts/Exchange.sol index a402e7ca5..1da74deef 100644 --- a/packages/contracts/contracts/Exchange.sol +++ b/packages/contracts/contracts/Exchange.sol @@ -600,4 +600,3 @@ contract Exchange is SafeMath { return Token(token).allowance.gas(EXTERNAL_QUERY_GAS_LIMIT)(owner, TOKEN_TRANSFER_PROXY_CONTRACT); // Limit gas to prevent reentrancy } } - diff --git a/packages/contracts/globals.d.ts b/packages/contracts/globals.d.ts index 2e5827324..0e6586a4b 100644 --- a/packages/contracts/globals.d.ts +++ b/packages/contracts/globals.d.ts @@ -32,7 +32,3 @@ declare module 'ethereumjs-abi' { const soliditySHA3: (argTypes: string[], args: any[]) => Buffer; const methodID: (name: string, types: string[]) => Buffer; } - -// Truffle injects the following into the global scope -declare var artifacts: any; -declare var contract: any; diff --git a/packages/contracts/migrations/1_initial_migration.ts b/packages/contracts/migrations/1_initial_migration.ts deleted file mode 100644 index 8661ee218..000000000 --- a/packages/contracts/migrations/1_initial_migration.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { Artifacts } from '../util/artifacts'; -const { Migrations } = new Artifacts(artifacts); - -module.exports = (deployer: any) => { - deployer.deploy(Migrations); -}; diff --git a/packages/contracts/migrations/2_deploy_independent_contracts.ts b/packages/contracts/migrations/2_deploy_independent_contracts.ts deleted file mode 100644 index ac1752347..000000000 --- a/packages/contracts/migrations/2_deploy_independent_contracts.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { Artifacts } from '../util/artifacts'; -import { MultiSigConfigByNetwork } from '../util/types'; -const { MultiSigWalletWithTimeLock, TokenTransferProxy, EtherToken, TokenRegistry } = new Artifacts(artifacts); - -let multiSigConfigByNetwork: MultiSigConfigByNetwork; -try { - /* tslint:disable */ - const multiSigConfig = require('./config/multisig'); - multiSigConfigByNetwork = multiSigConfig.multiSig; - /* tslint:enable */ -} catch (e) { - multiSigConfigByNetwork = {}; -} - -module.exports = (deployer: any, network: string, accounts: string[]) => { - const defaultConfig = { - owners: [accounts[0], accounts[1]], - confirmationsRequired: 2, - secondsRequired: 0, - }; - const config = multiSigConfigByNetwork[network] || defaultConfig; - if (network !== 'live') { - deployer - .deploy(MultiSigWalletWithTimeLock, config.owners, config.confirmationsRequired, config.secondsRequired) - .then(() => { - return deployer.deploy(TokenTransferProxy); - }) - .then(() => { - return deployer.deploy(TokenRegistry); - }) - .then(() => { - return deployer.deploy(EtherToken); - }); - } else { - deployer.deploy([ - [MultiSigWalletWithTimeLock, config.owners, config.confirmationsRequired, config.secondsRequired], - TokenTransferProxy, - TokenRegistry, - ]); - } -}; diff --git a/packages/contracts/migrations/3_register_tokens.ts b/packages/contracts/migrations/3_register_tokens.ts deleted file mode 100644 index d5cf63f94..000000000 --- a/packages/contracts/migrations/3_register_tokens.ts +++ /dev/null @@ -1,95 +0,0 @@ -import * as Bluebird from 'bluebird'; -import * as _ from 'lodash'; - -import { Artifacts } from '../util/artifacts'; -import { constants } from '../util/constants'; -import { ContractInstance, Token } from '../util/types'; - -import { tokenInfo } from './config/token_info'; -const { DummyToken, EtherToken, ZRXToken, TokenRegistry } = new Artifacts(artifacts); - -module.exports = (deployer: any, network: string) => { - const tokens = network === 'live' ? tokenInfo.live : tokenInfo.development; - deployer - .then(() => { - return TokenRegistry.deployed(); - }) - .then((tokenRegistry: ContractInstance) => { - if (network !== 'live') { - const totalSupply = Math.pow(10, 18) * 1000000000; - return Bluebird.each( - tokens.map((token: Token) => DummyToken.new(token.name, token.symbol, token.decimals, totalSupply)), - _.noop, - ).then((dummyTokens: ContractInstance[]) => { - const weth = { - address: EtherToken.address, - name: 'Ether Token', - symbol: 'WETH', - url: '', - decimals: 18, - ipfsHash: constants.NULL_BYTES, - swarmHash: constants.NULL_BYTES, - }; - return Bluebird.each( - dummyTokens - .map((tokenContract: ContractInstance, i: number) => { - const token = tokens[i]; - return tokenRegistry.addToken( - tokenContract.address, - token.name, - token.symbol, - token.decimals, - token.ipfsHash, - token.swarmHash, - ); - }) - .concat( - tokenRegistry.addToken( - weth.address, - weth.name, - weth.symbol, - weth.decimals, - weth.ipfsHash, - weth.swarmHash, - ), - ), - _.noop, - ); - }); - } else { - const zrx = { - address: ZRXToken.address, - name: '0x Protocol Token', - symbol: 'ZRX', - url: 'https://www.0xproject.com/', - decimals: 18, - ipfsHash: constants.NULL_BYTES, - swarmHash: constants.NULL_BYTES, - }; - return Bluebird.each( - tokens - .map((token: Token) => { - return tokenRegistry.addToken( - token.address, - token.name, - token.symbol, - token.decimals, - token.ipfsHash, - token.swarmHash, - ); - }) - .concat( - tokenRegistry.addToken( - zrx.address, - zrx.name, - zrx.symbol, - zrx.decimals, - zrx.ipfsHash, - zrx.swarmHash, - ), - ), - _.noop, - ); - } - }); -}; diff --git a/packages/contracts/migrations/4_configure_proxy.ts b/packages/contracts/migrations/4_configure_proxy.ts deleted file mode 100644 index ff3b844d6..000000000 --- a/packages/contracts/migrations/4_configure_proxy.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Artifacts } from '../util/artifacts'; -import { ContractInstance } from '../util/types'; -const { TokenTransferProxy, Exchange, TokenRegistry } = new Artifacts(artifacts); - -let tokenTransferProxy: ContractInstance; -module.exports = (deployer: any) => { - deployer - .then(async () => { - return Promise.all([TokenTransferProxy.deployed(), TokenRegistry.deployed()]); - }) - .then((instances: ContractInstance[]) => { - let tokenRegistry: ContractInstance; - [tokenTransferProxy, tokenRegistry] = instances; - return tokenRegistry.getTokenAddressBySymbol('ZRX'); - }) - .then((ptAddress: string) => { - return deployer.deploy(Exchange, ptAddress, tokenTransferProxy.address); - }) - .then(() => { - return tokenTransferProxy.addAuthorizedAddress(Exchange.address); - }); -}; diff --git a/packages/contracts/migrations/5_transfer_ownership.ts b/packages/contracts/migrations/5_transfer_ownership.ts deleted file mode 100644 index a27801de3..000000000 --- a/packages/contracts/migrations/5_transfer_ownership.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Artifacts } from '../util/artifacts'; -import { ContractInstance } from '../util/types'; -const { TokenTransferProxy, MultiSigWalletWithTimeLock, TokenRegistry } = new Artifacts(artifacts); - -let tokenRegistry: ContractInstance; -module.exports = (deployer: any, network: string) => { - if (network !== 'development') { - deployer.then(async () => { - return Promise.all([TokenTransferProxy.deployed(), TokenRegistry.deployed()]) - .then((instances: ContractInstance[]) => { - let tokenTransferProxy: ContractInstance; - [tokenTransferProxy, tokenRegistry] = instances; - return tokenTransferProxy.transferOwnership(MultiSigWalletWithTimeLock.address); - }) - .then(() => { - return tokenRegistry.transferOwnership(MultiSigWalletWithTimeLock.address); - }); - }); - } -}; diff --git a/packages/contracts/migrations/config/multisig_sample.ts b/packages/contracts/migrations/config/multisig_sample.ts deleted file mode 100644 index 97cdc2eae..000000000 --- a/packages/contracts/migrations/config/multisig_sample.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { MultiSigConfigByNetwork } from '../../util/types'; - -// Make a copy of this file named `multisig.js` and input custom params as needed -export const multiSig: MultiSigConfigByNetwork = { - kovan: { - owners: [], - confirmationsRequired: 0, - secondsRequired: 0, - }, -}; diff --git a/packages/contracts/migrations/config/token_info.ts b/packages/contracts/migrations/config/token_info.ts deleted file mode 100644 index 6ae67175e..000000000 --- a/packages/contracts/migrations/config/token_info.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { constants } from '../../util/constants'; -import { TokenInfoByNetwork } from '../../util/types'; - -export const tokenInfo: TokenInfoByNetwork = { - development: [ - { - name: '0x Protocol Token', - symbol: 'ZRX', - decimals: 18, - ipfsHash: constants.NULL_BYTES, - swarmHash: constants.NULL_BYTES, - }, - { - name: 'Augur Reputation Token', - symbol: 'REP', - decimals: 18, - ipfsHash: constants.NULL_BYTES, - swarmHash: constants.NULL_BYTES, - }, - { - name: 'Digix DAO Token', - symbol: 'DGD', - decimals: 18, - ipfsHash: constants.NULL_BYTES, - swarmHash: constants.NULL_BYTES, - }, - { - name: 'Golem Network Token', - symbol: 'GNT', - decimals: 18, - ipfsHash: constants.NULL_BYTES, - swarmHash: constants.NULL_BYTES, - }, - { - name: 'MakerDAO', - symbol: 'MKR', - decimals: 18, - ipfsHash: constants.NULL_BYTES, - swarmHash: constants.NULL_BYTES, - }, - { - name: 'Melon Token', - symbol: 'MLN', - decimals: 18, - ipfsHash: constants.NULL_BYTES, - swarmHash: constants.NULL_BYTES, - }, - ], - live: [ - { - address: '0xecf8f87f810ecf450940c9f60066b4a7a501d6a7', - name: 'ETH Wrapper Token', - symbol: 'WETH', - decimals: 18, - ipfsHash: constants.NULL_BYTES, - swarmHash: constants.NULL_BYTES, - }, - { - address: '0x48c80f1f4d53d5951e5d5438b54cba84f29f32a5', - name: 'Augur Reputation Token', - symbol: 'REP', - decimals: 18, - ipfsHash: constants.NULL_BYTES, - swarmHash: constants.NULL_BYTES, - }, - { - address: '0xe0b7927c4af23765cb51314a0e0521a9645f0e2a', - name: 'Digix DAO Token', - symbol: 'DGD', - decimals: 18, - ipfsHash: constants.NULL_BYTES, - swarmHash: constants.NULL_BYTES, - }, - { - address: '0xa74476443119a942de498590fe1f2454d7d4ac0d', - name: 'Golem Network Token', - symbol: 'GNT', - decimals: 18, - ipfsHash: constants.NULL_BYTES, - swarmHash: constants.NULL_BYTES, - }, - { - address: '0xc66ea802717bfb9833400264dd12c2bceaa34a6d', - name: 'MakerDAO', - symbol: 'MKR', - decimals: 18, - ipfsHash: constants.NULL_BYTES, - swarmHash: constants.NULL_BYTES, - }, - { - address: '0xbeb9ef514a379b997e0798fdcc901ee474b6d9a1', - name: 'Melon Token', - symbol: 'MLN', - decimals: 18, - ipfsHash: constants.NULL_BYTES, - swarmHash: constants.NULL_BYTES, - }, - ], -}; diff --git a/packages/contracts/package.json b/packages/contracts/package.json index 23a45218f..d2f4df0b4 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -1,16 +1,18 @@ { "private": true, "name": "contracts", - "version": "2.1.5", + "version": "2.1.7", "description": "Smart contract components of 0x protocol", "main": "index.js", "directories": { "test": "test" }, "scripts": { - "build": - "rm -rf ./lib; copyfiles ./build/**/* ./deploy/solc/solc_bin/* ./deploy/test/fixtures/contracts/**/* ./deploy/test/fixtures/contracts/* ./lib; tsc;", - "test": "npm run build; truffle test", + "prebuild": "run-s clean copy_artifacts", + "copy_artifacts": "copyfiles './build/**/*' './deploy/solc/solc_bin/*' './deploy/test/fixtures/contracts/**/*' './deploy/test/fixtures/contracts/*' ./lib", + "build": "tsc", + "test": "run-s compile build run_mocha", + "run_mocha": "mocha 'lib/test/**/*.js' --timeout 10000 --bail --exit", "compile:comment": "Yarn workspaces do not link binaries correctly so we need to reference them directly https://github.com/yarnpkg/yarn/issues/3846", "compile": "node ../deployer/lib/src/cli.js compile", @@ -30,9 +32,9 @@ }, "homepage": "https://github.com/0xProject/0x.js/packages/contracts/README.md", "devDependencies": { - "@0xproject/dev-utils": "^0.0.6", - "@0xproject/tslint-config": "^0.4.3", - "@0xproject/types": "^0.1.5", + "@0xproject/dev-utils": "^0.0.8", + "@0xproject/tslint-config": "^0.4.5", + "@0xproject/types": "^0.1.7", "@types/bluebird": "^3.5.3", "@types/lodash": "^4.14.86", "@types/node": "^8.0.53", @@ -40,27 +42,27 @@ "@types/yargs": "^10.0.0", "chai": "^4.0.1", "chai-as-promised": "^7.1.0", - "chai-as-promised-typescript-typings": "^0.0.5", + "chai-as-promised-typescript-typings": "^0.0.7", "chai-bignumber": "^2.0.1", "chai-typescript-typings": "^0.0.2", "copyfiles": "^1.2.0", "dirty-chai": "^2.0.1", "mocha": "^4.0.1", + "npm-run-all": "^4.1.2", "solc": "^0.4.18", - "truffle": "^4.0.1", "tslint": "5.8.0", "types-bn": "^0.0.1", - "types-ethereumjs-util": "0xProject/types-ethereumjs-util", + "types-ethereumjs-util": "0xproject/types-ethereumjs-util", "typescript": "~2.6.1", - "web3-typescript-typings": "^0.9.5", + "web3-typescript-typings": "^0.9.7", "yargs": "^10.0.3" }, "dependencies": { - "0x.js": "^0.30.1", - "@0xproject/deployer": "^0.0.2", - "@0xproject/json-schemas": "^0.7.4", - "@0xproject/utils": "^0.2.1", - "@0xproject/web3-wrapper": "^0.1.6", + "0x.js": "^0.31.0", + "@0xproject/deployer": "^0.0.4", + "@0xproject/json-schemas": "^0.7.6", + "@0xproject/utils": "^0.2.3", + "@0xproject/web3-wrapper": "^0.1.8", "bluebird": "^3.5.0", "bn.js": "^4.11.8", "ethereumjs-abi": "^0.6.4", diff --git a/packages/contracts/test/ts/ether_token.ts b/packages/contracts/test/ether_token.ts index f807cdaa3..4c70534ee 100644 --- a/packages/contracts/test/ts/ether_token.ts +++ b/packages/contracts/test/ether_token.ts @@ -1,46 +1,46 @@ import { ZeroEx, ZeroExError } from '0x.js'; +import { BlockchainLifecycle, devConstants, web3Factory } from '@0xproject/dev-utils'; import { BigNumber, promisify } from '@0xproject/utils'; +import { Web3Wrapper } from '@0xproject/web3-wrapper'; import * as chai from 'chai'; -import Web3 = require('web3'); -import { Artifacts } from '../../util/artifacts'; -import { constants } from '../../util/constants'; +import { constants } from '../util/constants'; +import { ContractName } from '../util/types'; import { chaiSetup } from './utils/chai_setup'; - -const { EtherToken } = new Artifacts(artifacts); +import { deployer } from './utils/deployer'; chaiSetup.configure(); const expect = chai.expect; +const web3 = web3Factory.create(); +const web3Wrapper = new Web3Wrapper(web3.currentProvider); +const blockchainLifecycle = new BlockchainLifecycle(); -// In order to benefit from type-safety, we re-assign the global web3 instance injected by Truffle -// with type `any` to a variable of type `Web3`. -const web3: Web3 = (global as any).web3; - -contract('EtherToken', (accounts: string[]) => { - const account = accounts[0]; +describe('EtherToken', () => { + let account: string; const gasPrice = ZeroEx.toBaseUnitAmount(new BigNumber(20), 9); let zeroEx: ZeroEx; let etherTokenAddress: string; - before(async () => { - etherTokenAddress = EtherToken.address; + const accounts = await web3Wrapper.getAvailableAddressesAsync(); + account = accounts[0]; + + const etherToken = await deployer.deployAsync(ContractName.EtherToken); + etherTokenAddress = etherToken.address; zeroEx = new ZeroEx(web3.currentProvider, { gasPrice, networkId: constants.TESTRPC_NETWORK_ID, }); }); - - const sendTransactionAsync = promisify<string>(web3.eth.sendTransaction); - const getEthBalanceAsync = async (owner: string) => { - const balanceStr = await promisify<string>(web3.eth.getBalance)(owner); - const balance = new BigNumber(balanceStr); - return balance; - }; - + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + }); describe('deposit', () => { it('should throw if caller attempts to deposit more Ether than caller balance', async () => { - const initEthBalance = await getEthBalanceAsync(account); + const initEthBalance = await web3Wrapper.getBalanceInWeiAsync(account); const ethToDeposit = initEthBalance.plus(1); return expect(zeroEx.etherToken.depositAsync(etherTokenAddress, ethToDeposit, account)).to.be.rejectedWith( @@ -49,7 +49,7 @@ contract('EtherToken', (accounts: string[]) => { }); it('should convert deposited Ether to wrapped Ether tokens', async () => { - const initEthBalance = await getEthBalanceAsync(account); + const initEthBalance = await web3Wrapper.getBalanceInWeiAsync(account); const initEthTokenBalance = await zeroEx.token.getBalanceAsync(etherTokenAddress, account); const ethToDeposit = new BigNumber(web3.toWei(1, 'ether')); @@ -58,7 +58,7 @@ contract('EtherToken', (accounts: string[]) => { const receipt = await zeroEx.awaitTransactionMinedAsync(txHash); const ethSpentOnGas = gasPrice.times(receipt.gasUsed); - const finalEthBalance = await getEthBalanceAsync(account); + const finalEthBalance = await web3Wrapper.getBalanceInWeiAsync(account); const finalEthTokenBalance = await zeroEx.token.getBalanceAsync(etherTokenAddress, account); expect(finalEthBalance).to.be.bignumber.equal(initEthBalance.minus(ethToDeposit.plus(ethSpentOnGas))); @@ -77,8 +77,10 @@ contract('EtherToken', (accounts: string[]) => { }); it('should convert ether tokens to ether with sufficient balance', async () => { + const ethToDeposit = new BigNumber(web3.toWei(1, 'ether')); + await zeroEx.etherToken.depositAsync(etherTokenAddress, ethToDeposit, account); const initEthTokenBalance = await zeroEx.token.getBalanceAsync(etherTokenAddress, account); - const initEthBalance = await getEthBalanceAsync(account); + const initEthBalance = await web3Wrapper.getBalanceInWeiAsync(account); const ethTokensToWithdraw = initEthTokenBalance; expect(ethTokensToWithdraw).to.not.be.bignumber.equal(0); const txHash = await zeroEx.etherToken.withdrawAsync(etherTokenAddress, ethTokensToWithdraw, account, { @@ -87,7 +89,7 @@ contract('EtherToken', (accounts: string[]) => { const receipt = await zeroEx.awaitTransactionMinedAsync(txHash); const ethSpentOnGas = gasPrice.times(receipt.gasUsed); - const finalEthBalance = await getEthBalanceAsync(account); + const finalEthBalance = await web3Wrapper.getBalanceInWeiAsync(account); const finalEthTokenBalance = await zeroEx.token.getBalanceAsync(etherTokenAddress, account); expect(finalEthBalance).to.be.bignumber.equal( @@ -99,12 +101,12 @@ contract('EtherToken', (accounts: string[]) => { describe('fallback', () => { it('should convert sent ether to ether tokens', async () => { - const initEthBalance = await getEthBalanceAsync(account); + const initEthBalance = await web3Wrapper.getBalanceInWeiAsync(account); const initEthTokenBalance = await zeroEx.token.getBalanceAsync(etherTokenAddress, account); const ethToDeposit = ZeroEx.toBaseUnitAmount(new BigNumber(1), 18); - const txHash = await sendTransactionAsync({ + const txHash = await web3Wrapper.sendTransactionAsync({ from: account, to: etherTokenAddress, value: ethToDeposit, @@ -114,7 +116,7 @@ contract('EtherToken', (accounts: string[]) => { const receipt = await zeroEx.awaitTransactionMinedAsync(txHash); const ethSpentOnGas = gasPrice.times(receipt.gasUsed); - const finalEthBalance = await getEthBalanceAsync(account); + const finalEthBalance = await web3Wrapper.getBalanceInWeiAsync(account); const finalEthTokenBalance = await zeroEx.token.getBalanceAsync(etherTokenAddress, account); expect(finalEthBalance).to.be.bignumber.equal(initEthBalance.minus(ethToDeposit.plus(ethSpentOnGas))); diff --git a/packages/contracts/test/ts/exchange/core.ts b/packages/contracts/test/exchange/core.ts index 770ef0c43..e9182dd1b 100644 --- a/packages/contracts/test/ts/exchange/core.ts +++ b/packages/contracts/test/exchange/core.ts @@ -1,41 +1,47 @@ -import { ZeroEx } from '0x.js'; +import { + LogCancelContractEventArgs, + LogErrorContractEventArgs, + LogFillContractEventArgs, + LogWithDecodedArgs, + TransactionReceiptWithDecodedLogs, + ZeroEx, +} from '0x.js'; +import { BlockchainLifecycle, devConstants, web3Factory } from '@0xproject/dev-utils'; import { BigNumber } from '@0xproject/utils'; +import { Web3Wrapper } from '@0xproject/web3-wrapper'; import * as chai from 'chai'; import ethUtil = require('ethereumjs-util'); import * as Web3 from 'web3'; -import { Artifacts } from '../../../util/artifacts'; -import { Balances } from '../../../util/balances'; -import { constants } from '../../../util/constants'; -import { crypto } from '../../../util/crypto'; -import { ExchangeWrapper } from '../../../util/exchange_wrapper'; -import { Order } from '../../../util/order'; -import { OrderFactory } from '../../../util/order_factory'; -import { BalancesByOwner, ContractInstance, ExchangeContractErrs } from '../../../util/types'; +import { Balances } from '../../util/balances'; +import { constants } from '../../util/constants'; +import { crypto } from '../../util/crypto'; +import { ExchangeWrapper } from '../../util/exchange_wrapper'; +import { Order } from '../../util/order'; +import { OrderFactory } from '../../util/order_factory'; +import { BalancesByOwner, ContractName, ExchangeContractErrs } from '../../util/types'; import { chaiSetup } from '../utils/chai_setup'; +import { deployer } from '../utils/deployer'; chaiSetup.configure(); const expect = chai.expect; -const { Exchange, TokenTransferProxy, DummyToken, TokenRegistry, MaliciousToken } = new Artifacts(artifacts); - -// In order to benefit from type-safety, we re-assign the global web3 instance injected by Truffle -// with type `any` to a variable of type `Web3`. -const web3: Web3 = (global as any).web3; - -contract('Exchange', (accounts: string[]) => { - const maker = accounts[0]; - const tokenOwner = accounts[0]; - const taker = accounts[1] || accounts[accounts.length - 1]; - const feeRecipient = accounts[2] || accounts[accounts.length - 1]; - +const web3 = web3Factory.create(); +const web3Wrapper = new Web3Wrapper(web3.currentProvider); +const blockchainLifecycle = new BlockchainLifecycle(); + +describe('Exchange', () => { + let maker: string; + let tokenOwner: string; + let taker: string; + let feeRecipient: string; const INITIAL_BALANCE = ZeroEx.toBaseUnitAmount(new BigNumber(10000), 18); const INITIAL_ALLOWANCE = ZeroEx.toBaseUnitAmount(new BigNumber(10000), 18); - let rep: ContractInstance; - let dgd: ContractInstance; - let zrx: ContractInstance; - let exchange: ContractInstance; - let tokenRegistry: ContractInstance; + let rep: Web3.ContractInstance; + let dgd: Web3.ContractInstance; + let zrx: Web3.ContractInstance; + let exchange: Web3.ContractInstance; + let tokenTransferProxy: Web3.ContractInstance; let order: Order; let balances: BalancesByOwner; @@ -46,66 +52,69 @@ contract('Exchange', (accounts: string[]) => { let zeroEx: ZeroEx; before(async () => { - [tokenRegistry, exchange] = await Promise.all([TokenRegistry.deployed(), Exchange.deployed()]); - exWrapper = new ExchangeWrapper(exchange); + const accounts = await web3Wrapper.getAvailableAddressesAsync(); + maker = accounts[0]; + [tokenOwner, taker, feeRecipient] = accounts; + [rep, dgd, zrx] = await Promise.all([ + deployer.deployAsync(ContractName.DummyToken), + deployer.deployAsync(ContractName.DummyToken), + deployer.deployAsync(ContractName.DummyToken), + ]); + tokenTransferProxy = await deployer.deployAsync(ContractName.TokenTransferProxy); + exchange = await deployer.deployAsync(ContractName.Exchange, [zrx.address, tokenTransferProxy.address]); + await tokenTransferProxy.addAuthorizedAddress(exchange.address, { from: accounts[0] }); zeroEx = new ZeroEx(web3.currentProvider, { exchangeContractAddress: exchange.address, networkId: constants.TESTRPC_NETWORK_ID, }); - - const [repAddress, dgdAddress, zrxAddress] = await Promise.all([ - tokenRegistry.getTokenAddressBySymbol('REP'), - tokenRegistry.getTokenAddressBySymbol('DGD'), - tokenRegistry.getTokenAddressBySymbol('ZRX'), - ]); + exWrapper = new ExchangeWrapper(exchange, zeroEx); const defaultOrderParams = { - exchangeContractAddress: Exchange.address, + exchangeContractAddress: exchange.address, maker, feeRecipient, - makerToken: repAddress, - takerToken: dgdAddress, + makerToken: rep.address, + takerToken: dgd.address, makerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18), takerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(200), 18), makerFee: ZeroEx.toBaseUnitAmount(new BigNumber(1), 18), takerFee: ZeroEx.toBaseUnitAmount(new BigNumber(1), 18), }; - orderFactory = new OrderFactory(defaultOrderParams); - - [rep, dgd, zrx] = await Promise.all([ - DummyToken.at(repAddress), - DummyToken.at(dgdAddress), - DummyToken.at(zrxAddress), - ]); + orderFactory = new OrderFactory(web3Wrapper, defaultOrderParams); dmyBalances = new Balances([rep, dgd, zrx], [maker, taker, feeRecipient]); await Promise.all([ - rep.approve(TokenTransferProxy.address, INITIAL_ALLOWANCE, { + rep.approve(tokenTransferProxy.address, INITIAL_ALLOWANCE, { from: maker, }), - rep.approve(TokenTransferProxy.address, INITIAL_ALLOWANCE, { + rep.approve(tokenTransferProxy.address, INITIAL_ALLOWANCE, { from: taker, }), rep.setBalance(maker, INITIAL_BALANCE, { from: tokenOwner }), rep.setBalance(taker, INITIAL_BALANCE, { from: tokenOwner }), - dgd.approve(TokenTransferProxy.address, INITIAL_ALLOWANCE, { + dgd.approve(tokenTransferProxy.address, INITIAL_ALLOWANCE, { from: maker, }), - dgd.approve(TokenTransferProxy.address, INITIAL_ALLOWANCE, { + dgd.approve(tokenTransferProxy.address, INITIAL_ALLOWANCE, { from: taker, }), dgd.setBalance(maker, INITIAL_BALANCE, { from: tokenOwner }), dgd.setBalance(taker, INITIAL_BALANCE, { from: tokenOwner }), - zrx.approve(TokenTransferProxy.address, INITIAL_ALLOWANCE, { + zrx.approve(tokenTransferProxy.address, INITIAL_ALLOWANCE, { from: maker, }), - zrx.approve(TokenTransferProxy.address, INITIAL_ALLOWANCE, { + zrx.approve(tokenTransferProxy.address, INITIAL_ALLOWANCE, { from: taker, }), zrx.setBalance(maker, INITIAL_BALANCE, { from: tokenOwner }), zrx.setBalance(taker, INITIAL_BALANCE, { from: tokenOwner }), ]); }); - + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + }); describe('internal functions', () => { it('should include transferViaTokenTransferProxy', () => { expect(exchange.transferViaTokenTransferProxy).to.be.undefined(); @@ -136,9 +145,8 @@ contract('Exchange', (accounts: string[]) => { takerTokenAmount: new BigNumber(3), }); - const filledTakerTokenAmountBefore = await zeroEx.exchange.getFilledTakerAmountAsync( - order.params.orderHashHex, - ); + const filledTakerTokenAmountBefore = await zeroEx.exchange.getFilledTakerAmountAsync(order.params + .orderHashHex as string); expect(filledTakerTokenAmountBefore).to.be.bignumber.equal(0); const fillTakerTokenAmount1 = new BigNumber(2); @@ -146,9 +154,8 @@ contract('Exchange', (accounts: string[]) => { fillTakerTokenAmount: fillTakerTokenAmount1, }); - const filledTakerTokenAmountAfter1 = await zeroEx.exchange.getFilledTakerAmountAsync( - order.params.orderHashHex, - ); + const filledTakerTokenAmountAfter1 = await zeroEx.exchange.getFilledTakerAmountAsync(order.params + .orderHashHex as string); expect(filledTakerTokenAmountAfter1).to.be.bignumber.equal(fillTakerTokenAmount1); const fillTakerTokenAmount2 = new BigNumber(1); @@ -156,9 +163,8 @@ contract('Exchange', (accounts: string[]) => { fillTakerTokenAmount: fillTakerTokenAmount2, }); - const filledTakerTokenAmountAfter2 = await zeroEx.exchange.getFilledTakerAmountAsync( - order.params.orderHashHex, - ); + const filledTakerTokenAmountAfter2 = await zeroEx.exchange.getFilledTakerAmountAsync(order.params + .orderHashHex as string); expect(filledTakerTokenAmountAfter2).to.be.bignumber.equal(filledTakerTokenAmountAfter1); }); @@ -168,17 +174,15 @@ contract('Exchange', (accounts: string[]) => { takerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18), }); - const filledTakerTokenAmountBefore = await zeroEx.exchange.getFilledTakerAmountAsync( - order.params.orderHashHex, - ); + const filledTakerTokenAmountBefore = await zeroEx.exchange.getFilledTakerAmountAsync(order.params + .orderHashHex as string); expect(filledTakerTokenAmountBefore).to.be.bignumber.equal(0); const fillTakerTokenAmount = order.params.takerTokenAmount.div(2); await exWrapper.fillOrderAsync(order, taker, { fillTakerTokenAmount }); - const filledTakerTokenAmountAfter = await zeroEx.exchange.getFilledTakerAmountAsync( - order.params.orderHashHex, - ); + const filledTakerTokenAmountAfter = await zeroEx.exchange.getFilledTakerAmountAsync(order.params + .orderHashHex as string); expect(filledTakerTokenAmountAfter).to.be.bignumber.equal(fillTakerTokenAmount); const newBalances = await dmyBalances.getAsync(); @@ -221,17 +225,15 @@ contract('Exchange', (accounts: string[]) => { takerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18), }); - const filledTakerTokenAmountBefore = await zeroEx.exchange.getFilledTakerAmountAsync( - order.params.orderHashHex, - ); + const filledTakerTokenAmountBefore = await zeroEx.exchange.getFilledTakerAmountAsync(order.params + .orderHashHex as string); expect(filledTakerTokenAmountBefore).to.be.bignumber.equal(0); const fillTakerTokenAmount = order.params.takerTokenAmount.div(2); await exWrapper.fillOrderAsync(order, taker, { fillTakerTokenAmount }); - const filledTakerTokenAmountAfter = await zeroEx.exchange.getFilledTakerAmountAsync( - order.params.orderHashHex, - ); + const filledTakerTokenAmountAfter = await zeroEx.exchange.getFilledTakerAmountAsync(order.params + .orderHashHex as string); expect(filledTakerTokenAmountAfter).to.be.bignumber.equal(fillTakerTokenAmount); const newBalances = await dmyBalances.getAsync(); @@ -274,17 +276,15 @@ contract('Exchange', (accounts: string[]) => { takerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(200), 18), }); - const filledTakerTokenAmountBefore = await zeroEx.exchange.getFilledTakerAmountAsync( - order.params.orderHashHex, - ); + const filledTakerTokenAmountBefore = await zeroEx.exchange.getFilledTakerAmountAsync(order.params + .orderHashHex as string); expect(filledTakerTokenAmountBefore).to.be.bignumber.equal(0); const fillTakerTokenAmount = order.params.takerTokenAmount.div(2); await exWrapper.fillOrderAsync(order, taker, { fillTakerTokenAmount }); - const filledTakerTokenAmountAfter = await zeroEx.exchange.getFilledTakerAmountAsync( - order.params.orderHashHex, - ); + const filledTakerTokenAmountAfter = await zeroEx.exchange.getFilledTakerAmountAsync(order.params + .orderHashHex as string); expect(filledTakerTokenAmountAfter).to.be.bignumber.equal(fillTakerTokenAmount); const newBalances = await dmyBalances.getAsync(); @@ -328,17 +328,15 @@ contract('Exchange', (accounts: string[]) => { takerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(200), 18), }); - const filledTakerTokenAmountBefore = await zeroEx.exchange.getFilledTakerAmountAsync( - order.params.orderHashHex, - ); + const filledTakerTokenAmountBefore = await zeroEx.exchange.getFilledTakerAmountAsync(order.params + .orderHashHex as string); expect(filledTakerTokenAmountBefore).to.be.bignumber.equal(0); const fillTakerTokenAmount = order.params.takerTokenAmount.div(2); await exWrapper.fillOrderAsync(order, taker, { fillTakerTokenAmount }); - const filledTakerTokenAmountAfter = await zeroEx.exchange.getFilledTakerAmountAsync( - order.params.orderHashHex, - ); + const filledTakerTokenAmountAfter = await zeroEx.exchange.getFilledTakerAmountAsync(order.params + .orderHashHex as string); const expectedFillAmountTAfter = fillTakerTokenAmount.add(filledTakerTokenAmountBefore); expect(filledTakerTokenAmountAfter).to.be.bignumber.equal(expectedFillAmountTAfter); @@ -383,8 +381,8 @@ contract('Exchange', (accounts: string[]) => { const res = await exWrapper.fillOrderAsync(order, taker, { fillTakerTokenAmount: order.params.takerTokenAmount, }); - - expect(res.logs[0].args.filledTakerTokenAmount).to.be.bignumber.equal( + const log = res.logs[0] as LogWithDecodedArgs<LogFillContractEventArgs>; + expect(log.args.filledTakerTokenAmount).to.be.bignumber.equal( order.params.takerTokenAmount.minus(fillTakerTokenAmount), ); const newBalances = await dmyBalances.getAsync(); @@ -419,7 +417,7 @@ contract('Exchange', (accounts: string[]) => { }); expect(res.logs).to.have.length(1); - const logArgs = res.logs[0].args; + const logArgs = (res.logs[0] as LogWithDecodedArgs<LogFillContractEventArgs>).args; const expectedFilledMakerTokenAmount = order.params.makerTokenAmount.div(divisor); const expectedFilledTakerTokenAmount = order.params.takerTokenAmount.div(divisor); const expectedFeeMPaid = order.params.makerFee.div(divisor); @@ -450,7 +448,7 @@ contract('Exchange', (accounts: string[]) => { }); expect(res.logs).to.have.length(1); - const logArgs = res.logs[0].args; + const logArgs = (res.logs[0] as LogWithDecodedArgs<LogFillContractEventArgs>).args; const expectedFilledMakerTokenAmount = order.params.makerTokenAmount.div(divisor); const expectedFilledTakerTokenAmount = order.params.takerTokenAmount.div(divisor); const expectedFeeMPaid = new BigNumber(0); @@ -567,9 +565,9 @@ contract('Exchange', (accounts: string[]) => { it('should not change balances if maker allowances are too low to fill order and \ shouldThrowOnInsufficientBalanceOrAllowance = false', async () => { - await rep.approve(TokenTransferProxy.address, 0, { from: maker }); + await rep.approve(tokenTransferProxy.address, 0, { from: maker }); await exWrapper.fillOrderAsync(order, taker); - await rep.approve(TokenTransferProxy.address, INITIAL_ALLOWANCE, { + await rep.approve(tokenTransferProxy.address, INITIAL_ALLOWANCE, { from: maker, }); @@ -579,22 +577,22 @@ contract('Exchange', (accounts: string[]) => { it('should throw if maker allowances are too low to fill order and \ shouldThrowOnInsufficientBalanceOrAllowance = true', async () => { - await rep.approve(TokenTransferProxy.address, 0, { from: maker }); + await rep.approve(tokenTransferProxy.address, 0, { from: maker }); expect( exWrapper.fillOrderAsync(order, taker, { shouldThrowOnInsufficientBalanceOrAllowance: true, }), ).to.be.rejectedWith(constants.REVERT); - await rep.approve(TokenTransferProxy.address, INITIAL_ALLOWANCE, { + await rep.approve(tokenTransferProxy.address, INITIAL_ALLOWANCE, { from: maker, }); }); it('should not change balances if taker allowances are too low to fill order and \ shouldThrowOnInsufficientBalanceOrAllowance = false', async () => { - await dgd.approve(TokenTransferProxy.address, 0, { from: taker }); + await dgd.approve(tokenTransferProxy.address, 0, { from: taker }); await exWrapper.fillOrderAsync(order, taker); - await dgd.approve(TokenTransferProxy.address, INITIAL_ALLOWANCE, { + await dgd.approve(tokenTransferProxy.address, INITIAL_ALLOWANCE, { from: taker, }); @@ -604,13 +602,13 @@ contract('Exchange', (accounts: string[]) => { it('should throw if taker allowances are too low to fill order and \ shouldThrowOnInsufficientBalanceOrAllowance = true', async () => { - await dgd.approve(TokenTransferProxy.address, 0, { from: taker }); + await dgd.approve(tokenTransferProxy.address, 0, { from: taker }); expect( exWrapper.fillOrderAsync(order, taker, { shouldThrowOnInsufficientBalanceOrAllowance: true, }), ).to.be.rejectedWith(constants.REVERT); - await dgd.approve(TokenTransferProxy.address, INITIAL_ALLOWANCE, { + await dgd.approve(tokenTransferProxy.address, INITIAL_ALLOWANCE, { from: taker, }); }); @@ -630,7 +628,7 @@ contract('Exchange', (accounts: string[]) => { it('should not change balances if makerToken is ZRX, makerTokenAmount + makerFee > maker allowance, \ and shouldThrowOnInsufficientBalanceOrAllowance = false', async () => { - const makerZRXAllowance = await zrx.allowance(maker, TokenTransferProxy.address); + const makerZRXAllowance = await zrx.allowance(maker, tokenTransferProxy.address); order = await orderFactory.newSignedOrderAsync({ makerToken: zrx.address, makerTokenAmount: new BigNumber(makerZRXAllowance), @@ -656,7 +654,7 @@ contract('Exchange', (accounts: string[]) => { it('should not change balances if takerToken is ZRX, takerTokenAmount + takerFee > taker allowance, \ and shouldThrowOnInsufficientBalanceOrAllowance = false', async () => { - const takerZRXAllowance = await zrx.allowance(taker, TokenTransferProxy.address); + const takerZRXAllowance = await zrx.allowance(taker, tokenTransferProxy.address); order = await orderFactory.newSignedOrderAsync({ takerToken: zrx.address, takerTokenAmount: new BigNumber(takerZRXAllowance), @@ -669,8 +667,8 @@ contract('Exchange', (accounts: string[]) => { it('should throw if getBalance or getAllowance attempts to change state and \ shouldThrowOnInsufficientBalanceOrAllowance = false', async () => { - const maliciousToken = await MaliciousToken.new(); - await maliciousToken.approve(TokenTransferProxy.address, INITIAL_ALLOWANCE, { from: taker }); + const maliciousToken = await deployer.deployAsync(ContractName.MaliciousToken); + await maliciousToken.approve(tokenTransferProxy.address, INITIAL_ALLOWANCE, { from: taker }); order = await orderFactory.newSignedOrderAsync({ takerToken: maliciousToken.address, @@ -680,7 +678,7 @@ contract('Exchange', (accounts: string[]) => { exWrapper.fillOrderAsync(order, taker, { shouldThrowOnInsufficientBalanceOrAllowance: false, }), - ).to.be.rejectedWith(constants.REVERT); + ).to.be.rejectedWith(constants.INVALID_OPCODE); }); it('should not change balances if an order is expired', async () => { @@ -700,16 +698,19 @@ contract('Exchange', (accounts: string[]) => { const res = await exWrapper.fillOrderAsync(order, taker); expect(res.logs).to.have.length(1); - const errCode = res.logs[0].args.errorId.toNumber(); + const log = res.logs[0] as LogWithDecodedArgs<LogErrorContractEventArgs>; + const errCode = log.args.errorId.toNumber(); expect(errCode).to.be.equal(ExchangeContractErrs.ERROR_ORDER_EXPIRED); }); it('should log an error event if no value is filled', async () => { + order = await orderFactory.newSignedOrderAsync({}); await exWrapper.fillOrderAsync(order, taker); const res = await exWrapper.fillOrderAsync(order, taker); expect(res.logs).to.have.length(1); - const errCode = res.logs[0].args.errorId.toNumber(); + const log = res.logs[0] as LogWithDecodedArgs<LogErrorContractEventArgs>; + const errCode = log.args.errorId.toNumber(); expect(errCode).to.be.equal(ExchangeContractErrs.ERROR_ORDER_FULLY_FILLED_OR_CANCELLED); }); }); @@ -769,7 +770,8 @@ contract('Exchange', (accounts: string[]) => { const res = await exWrapper.fillOrderAsync(order, taker, { fillTakerTokenAmount: order.params.takerTokenAmount, }); - expect(res.logs[0].args.filledTakerTokenAmount).to.be.bignumber.equal( + const log = res.logs[0] as LogWithDecodedArgs<LogFillContractEventArgs>; + expect(log.args.filledTakerTokenAmount).to.be.bignumber.equal( order.params.takerTokenAmount.minus(cancelTakerTokenAmount), ); @@ -813,7 +815,8 @@ contract('Exchange', (accounts: string[]) => { }); expect(res.logs).to.have.length(1); - const logArgs = res.logs[0].args; + const log = res.logs[0] as LogWithDecodedArgs<LogCancelContractEventArgs>; + const logArgs = log.args; const expectedCancelledMakerTokenAmount = order.params.makerTokenAmount.div(divisor); const expectedCancelledTakerTokenAmount = order.params.takerTokenAmount.div(divisor); const tokensHashBuff = crypto.solSHA3([order.params.makerToken, order.params.takerToken]); @@ -834,7 +837,8 @@ contract('Exchange', (accounts: string[]) => { const res = await exWrapper.cancelOrderAsync(order, maker); expect(res.logs).to.have.length(1); - const errCode = res.logs[0].args.errorId.toNumber(); + const log = res.logs[0] as LogWithDecodedArgs<LogErrorContractEventArgs>; + const errCode = log.args.errorId.toNumber(); expect(errCode).to.be.equal(ExchangeContractErrs.ERROR_ORDER_FULLY_FILLED_OR_CANCELLED); }); @@ -845,7 +849,8 @@ contract('Exchange', (accounts: string[]) => { const res = await exWrapper.cancelOrderAsync(order, maker); expect(res.logs).to.have.length(1); - const errCode = res.logs[0].args.errorId.toNumber(); + const log = res.logs[0] as LogWithDecodedArgs<LogErrorContractEventArgs>; + const errCode = log.args.errorId.toNumber(); expect(errCode).to.be.equal(ExchangeContractErrs.ERROR_ORDER_EXPIRED); }); }); diff --git a/packages/contracts/test/ts/exchange/helpers.ts b/packages/contracts/test/exchange/helpers.ts index 95f68e419..5efce41a4 100644 --- a/packages/contracts/test/ts/exchange/helpers.ts +++ b/packages/contracts/test/exchange/helpers.ts @@ -1,52 +1,68 @@ import { ZeroEx } from '0x.js'; +import { BlockchainLifecycle, devConstants, web3Factory } from '@0xproject/dev-utils'; import { BigNumber } from '@0xproject/utils'; +import { Web3Wrapper } from '@0xproject/web3-wrapper'; import * as chai from 'chai'; import ethUtil = require('ethereumjs-util'); -import { Artifacts } from '../../../util/artifacts'; -import { ExchangeWrapper } from '../../../util/exchange_wrapper'; -import { Order } from '../../../util/order'; -import { OrderFactory } from '../../../util/order_factory'; +import { constants } from '../../util/constants'; +import { ExchangeWrapper } from '../../util/exchange_wrapper'; +import { Order } from '../../util/order'; +import { OrderFactory } from '../../util/order_factory'; +import { ContractName } from '../../util/types'; import { chaiSetup } from '../utils/chai_setup'; +import { deployer } from '../utils/deployer'; chaiSetup.configure(); const expect = chai.expect; -const { Exchange, TokenRegistry } = new Artifacts(artifacts); +const web3 = web3Factory.create(); +const web3Wrapper = new Web3Wrapper(web3.currentProvider); +const blockchainLifecycle = new BlockchainLifecycle(); -contract('Exchange', (accounts: string[]) => { - const maker = accounts[0]; - const feeRecipient = accounts[1] || accounts[accounts.length - 1]; +describe('Exchange', () => { + let maker: string; + let feeRecipient: string; let order: Order; let exchangeWrapper: ExchangeWrapper; let orderFactory: OrderFactory; before(async () => { - const [tokenRegistry, exchange] = await Promise.all([TokenRegistry.deployed(), Exchange.deployed()]); - exchangeWrapper = new ExchangeWrapper(exchange); - const [repAddress, dgdAddress] = await Promise.all([ - tokenRegistry.getTokenAddressBySymbol('REP'), - tokenRegistry.getTokenAddressBySymbol('DGD'), + const accounts = await web3Wrapper.getAvailableAddressesAsync(); + [maker, feeRecipient] = accounts; + const tokenRegistry = await deployer.deployAsync(ContractName.TokenRegistry); + const tokenTransferProxy = await deployer.deployAsync(ContractName.TokenTransferProxy); + const [rep, dgd, zrx] = await Promise.all([ + deployer.deployAsync(ContractName.DummyToken), + deployer.deployAsync(ContractName.DummyToken), + deployer.deployAsync(ContractName.DummyToken), ]); + const exchange = await deployer.deployAsync(ContractName.Exchange, [zrx.address, tokenTransferProxy.address]); + await tokenTransferProxy.addAuthorizedAddress(exchange.address, { from: accounts[0] }); + const zeroEx = new ZeroEx(web3.currentProvider, { networkId: constants.TESTRPC_NETWORK_ID }); + exchangeWrapper = new ExchangeWrapper(exchange, zeroEx); const defaultOrderParams = { - exchangeContractAddress: Exchange.address, + exchangeContractAddress: exchange.address, maker, feeRecipient, - makerToken: repAddress, - takerToken: dgdAddress, + makerToken: rep.address, + takerToken: dgd.address, makerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18), takerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(200), 18), makerFee: ZeroEx.toBaseUnitAmount(new BigNumber(1), 18), takerFee: ZeroEx.toBaseUnitAmount(new BigNumber(1), 18), }; - orderFactory = new OrderFactory(defaultOrderParams); + orderFactory = new OrderFactory(web3Wrapper, defaultOrderParams); + order = await orderFactory.newSignedOrderAsync(); }); beforeEach(async () => { - order = await orderFactory.newSignedOrderAsync(); + await blockchainLifecycle.startAsync(); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); }); - describe('getOrderHash', () => { it('should output the correct orderHash', async () => { const orderHashHex = await exchangeWrapper.getOrderHashAsync(order); diff --git a/packages/contracts/test/ts/exchange/wrapper.ts b/packages/contracts/test/exchange/wrapper.ts index e69e08bcf..acdf481a9 100644 --- a/packages/contracts/test/ts/exchange/wrapper.ts +++ b/packages/contracts/test/exchange/wrapper.ts @@ -1,35 +1,41 @@ import { ZeroEx } from '0x.js'; +import { BlockchainLifecycle, devConstants, web3Factory } from '@0xproject/dev-utils'; import { BigNumber } from '@0xproject/utils'; +import { Web3Wrapper } from '@0xproject/web3-wrapper'; import * as chai from 'chai'; import * as _ from 'lodash'; - -import { Artifacts } from '../../../util/artifacts'; -import { Balances } from '../../../util/balances'; -import { constants } from '../../../util/constants'; -import { ExchangeWrapper } from '../../../util/exchange_wrapper'; -import { Order } from '../../../util/order'; -import { OrderFactory } from '../../../util/order_factory'; -import { BalancesByOwner, ContractInstance } from '../../../util/types'; +import * as Web3 from 'web3'; + +import { Balances } from '../../util/balances'; +import { constants } from '../../util/constants'; +import { ExchangeWrapper } from '../../util/exchange_wrapper'; +import { Order } from '../../util/order'; +import { OrderFactory } from '../../util/order_factory'; +import { BalancesByOwner, ContractName } from '../../util/types'; import { chaiSetup } from '../utils/chai_setup'; +import { deployer } from '../utils/deployer'; chaiSetup.configure(); const expect = chai.expect; -const { Exchange, TokenTransferProxy, DummyToken, TokenRegistry } = new Artifacts(artifacts); +const web3 = web3Factory.create(); +const web3Wrapper = new Web3Wrapper(web3.currentProvider); +const blockchainLifecycle = new BlockchainLifecycle(); -contract('Exchange', (accounts: string[]) => { - const maker = accounts[0]; - const tokenOwner = accounts[0]; - const taker = accounts[1] || accounts[accounts.length - 1]; - const feeRecipient = accounts[2] || accounts[accounts.length - 1]; +describe('Exchange', () => { + let maker: string; + let tokenOwner: string; + let taker: string; + let feeRecipient: string; const INIT_BAL = ZeroEx.toBaseUnitAmount(new BigNumber(10000), 18); const INIT_ALLOW = ZeroEx.toBaseUnitAmount(new BigNumber(10000), 18); - let rep: ContractInstance; - let dgd: ContractInstance; - let zrx: ContractInstance; - let exchange: ContractInstance; - let tokenRegistry: ContractInstance; + let rep: Web3.ContractInstance; + let dgd: Web3.ContractInstance; + let zrx: Web3.ContractInstance; + let exchange: Web3.ContractInstance; + let tokenRegistry: Web3.ContractInstance; + let tokenTransferProxy: Web3.ContractInstance; let balances: BalancesByOwner; @@ -38,49 +44,56 @@ contract('Exchange', (accounts: string[]) => { let orderFactory: OrderFactory; before(async () => { - [tokenRegistry, exchange] = await Promise.all([TokenRegistry.deployed(), Exchange.deployed()]); - exWrapper = new ExchangeWrapper(exchange); - const [repAddress, dgdAddress, zrxAddress] = await Promise.all([ - tokenRegistry.getTokenAddressBySymbol('REP'), - tokenRegistry.getTokenAddressBySymbol('DGD'), - tokenRegistry.getTokenAddressBySymbol('ZRX'), + const accounts = await web3Wrapper.getAvailableAddressesAsync(); + tokenOwner = accounts[0]; + [maker, taker, feeRecipient] = accounts; + [rep, dgd, zrx] = await Promise.all([ + deployer.deployAsync(ContractName.DummyToken), + deployer.deployAsync(ContractName.DummyToken), + deployer.deployAsync(ContractName.DummyToken), ]); + tokenRegistry = await deployer.deployAsync(ContractName.TokenRegistry); + tokenTransferProxy = await deployer.deployAsync(ContractName.TokenTransferProxy); + exchange = await deployer.deployAsync(ContractName.Exchange, [zrx.address, tokenTransferProxy.address]); + await tokenTransferProxy.addAuthorizedAddress(exchange.address, { from: accounts[0] }); + const zeroEx = new ZeroEx(web3.currentProvider, { networkId: constants.TESTRPC_NETWORK_ID }); + exWrapper = new ExchangeWrapper(exchange, zeroEx); const defaultOrderParams = { - exchangeContractAddress: Exchange.address, + exchangeContractAddress: exchange.address, maker, feeRecipient, - makerToken: repAddress, - takerToken: dgdAddress, + makerToken: rep.address, + takerToken: dgd.address, makerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18), takerTokenAmount: ZeroEx.toBaseUnitAmount(new BigNumber(200), 18), makerFee: ZeroEx.toBaseUnitAmount(new BigNumber(1), 18), takerFee: ZeroEx.toBaseUnitAmount(new BigNumber(1), 18), }; - orderFactory = new OrderFactory(defaultOrderParams); - [rep, dgd, zrx] = await Promise.all([ - DummyToken.at(repAddress), - DummyToken.at(dgdAddress), - DummyToken.at(zrxAddress), - ]); + orderFactory = new OrderFactory(web3Wrapper, defaultOrderParams); dmyBalances = new Balances([rep, dgd, zrx], [maker, taker, feeRecipient]); await Promise.all([ - rep.approve(TokenTransferProxy.address, INIT_ALLOW, { from: maker }), - rep.approve(TokenTransferProxy.address, INIT_ALLOW, { from: taker }), + rep.approve(tokenTransferProxy.address, INIT_ALLOW, { from: maker }), + rep.approve(tokenTransferProxy.address, INIT_ALLOW, { from: taker }), rep.setBalance(maker, INIT_BAL, { from: tokenOwner }), rep.setBalance(taker, INIT_BAL, { from: tokenOwner }), - dgd.approve(TokenTransferProxy.address, INIT_ALLOW, { from: maker }), - dgd.approve(TokenTransferProxy.address, INIT_ALLOW, { from: taker }), + dgd.approve(tokenTransferProxy.address, INIT_ALLOW, { from: maker }), + dgd.approve(tokenTransferProxy.address, INIT_ALLOW, { from: taker }), dgd.setBalance(maker, INIT_BAL, { from: tokenOwner }), dgd.setBalance(taker, INIT_BAL, { from: tokenOwner }), - zrx.approve(TokenTransferProxy.address, INIT_ALLOW, { from: maker }), - zrx.approve(TokenTransferProxy.address, INIT_ALLOW, { from: taker }), + zrx.approve(tokenTransferProxy.address, INIT_ALLOW, { from: maker }), + zrx.approve(tokenTransferProxy.address, INIT_ALLOW, { from: taker }), zrx.setBalance(maker, INIT_BAL, { from: tokenOwner }), zrx.setBalance(taker, INIT_BAL, { from: tokenOwner }), ]); }); - + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + }); describe('fillOrKillOrder', () => { beforeEach(async () => { balances = await dmyBalances.getAsync(); diff --git a/packages/contracts/test/multi_sig_with_time_lock.ts b/packages/contracts/test/multi_sig_with_time_lock.ts new file mode 100644 index 000000000..bd64be1ba --- /dev/null +++ b/packages/contracts/test/multi_sig_with_time_lock.ts @@ -0,0 +1,191 @@ +import { LogWithDecodedArgs, ZeroEx } from '0x.js'; +import { BlockchainLifecycle, RPC, web3Factory } from '@0xproject/dev-utils'; +import { AbiDecoder, BigNumber } from '@0xproject/utils'; +import { Web3Wrapper } from '@0xproject/web3-wrapper'; +import * as chai from 'chai'; +import * as Web3 from 'web3'; + +import * as multiSigWalletJSON from '../../build/contracts/MultiSigWalletWithTimeLock.json'; +import { artifacts } from '../util/artifacts'; +import { constants } from '../util/constants'; +import { MultiSigWrapper } from '../util/multi_sig_wrapper'; +import { ContractName, SubmissionContractEventArgs } from '../util/types'; + +import { chaiSetup } from './utils/chai_setup'; +import { deployer } from './utils/deployer'; + +const MULTI_SIG_ABI = artifacts.MultiSigWalletWithTimeLockArtifact.networks[constants.TESTRPC_NETWORK_ID].abi; +chaiSetup.configure(); +const expect = chai.expect; + +const web3 = web3Factory.create(); +const web3Wrapper = new Web3Wrapper(web3.currentProvider); +const blockchainLifecycle = new BlockchainLifecycle(); +const zeroEx = new ZeroEx(web3.currentProvider, { networkId: constants.TESTRPC_NETWORK_ID }); +const abiDecoder = new AbiDecoder([MULTI_SIG_ABI]); + +describe('MultiSigWalletWithTimeLock', () => { + let owners: string[]; + before(async () => { + const accounts = await web3Wrapper.getAvailableAddressesAsync(); + owners = [accounts[0], accounts[1]]; + }); + const SIGNATURES_REQUIRED = 2; + const SECONDS_TIME_LOCKED = 10000; + + let multiSig: Web3.ContractInstance; + let multiSigWrapper: MultiSigWrapper; + let txId: number; + let initialSecondsTimeLocked: number; + let rpc: RPC; + + before(async () => { + rpc = new RPC(); + }); + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + }); + + describe('changeTimeLock', () => { + describe('initially non-time-locked', async () => { + before('deploy a walet', async () => { + multiSig = await deployer.deployAsync(ContractName.MultiSigWalletWithTimeLock, [ + owners, + SIGNATURES_REQUIRED, + 0, + ]); + multiSigWrapper = new MultiSigWrapper(multiSig); + + const secondsTimeLocked = await multiSig.secondsTimeLocked(); + initialSecondsTimeLocked = secondsTimeLocked.toNumber(); + }); + it('should throw when not called by wallet', async () => { + return expect(multiSig.changeTimeLock(SECONDS_TIME_LOCKED, { from: owners[0] })).to.be.rejectedWith( + constants.REVERT, + ); + }); + + it('should throw without enough confirmations', async () => { + const destination = multiSig.address; + const from = owners[0]; + const dataParams = { + name: 'changeTimeLock', + abi: MULTI_SIG_ABI, + args: [SECONDS_TIME_LOCKED], + }; + const txHash = await multiSigWrapper.submitTransactionAsync(destination, from, dataParams); + const subRes = await zeroEx.awaitTransactionMinedAsync(txHash); + const log = abiDecoder.tryToDecodeLogOrNoop(subRes.logs[0]) as LogWithDecodedArgs< + SubmissionContractEventArgs + >; + + txId = log.args.transactionId.toNumber(); + return expect(multiSig.executeTransaction(txId, { from: owners[0] })).to.be.rejectedWith( + constants.REVERT, + ); + }); + + it('should set confirmation time with enough confirmations', async () => { + const destination = multiSig.address; + const from = owners[0]; + const dataParams = { + name: 'changeTimeLock', + abi: MULTI_SIG_ABI, + args: [SECONDS_TIME_LOCKED], + }; + let txHash = await multiSigWrapper.submitTransactionAsync(destination, from, dataParams); + const subRes = await zeroEx.awaitTransactionMinedAsync(txHash); + const log = abiDecoder.tryToDecodeLogOrNoop(subRes.logs[0]) as LogWithDecodedArgs< + SubmissionContractEventArgs + >; + + txId = log.args.transactionId.toNumber(); + txHash = await multiSig.confirmTransaction(txId, { from: owners[1] }); + const res = await zeroEx.awaitTransactionMinedAsync(txHash); + expect(res.logs).to.have.length(2); + + const blockNum = await web3Wrapper.getBlockNumberAsync(); + const blockInfo = await web3Wrapper.getBlockAsync(blockNum); + const timestamp = new BigNumber(blockInfo.timestamp); + const confirmationTimeBigNum = new BigNumber(await multiSig.confirmationTimes.call(txId)); + + expect(timestamp).to.be.bignumber.equal(confirmationTimeBigNum); + }); + + it('should be executable with enough confirmations and secondsTimeLocked of 0', async () => { + const destination = multiSig.address; + const from = owners[0]; + const dataParams = { + name: 'changeTimeLock', + abi: MULTI_SIG_ABI, + args: [SECONDS_TIME_LOCKED], + }; + let txHash = await multiSigWrapper.submitTransactionAsync(destination, from, dataParams); + const subRes = await zeroEx.awaitTransactionMinedAsync(txHash); + const log = abiDecoder.tryToDecodeLogOrNoop(subRes.logs[0]) as LogWithDecodedArgs< + SubmissionContractEventArgs + >; + + txId = log.args.transactionId.toNumber(); + txHash = await multiSig.confirmTransaction(txId, { from: owners[1] }); + + expect(initialSecondsTimeLocked).to.be.equal(0); + + txHash = await multiSig.executeTransaction(txId, { from: owners[0] }); + const res = await zeroEx.awaitTransactionMinedAsync(txHash); + expect(res.logs).to.have.length(2); + + const secondsTimeLocked = new BigNumber(await multiSig.secondsTimeLocked.call()); + expect(secondsTimeLocked).to.be.bignumber.equal(SECONDS_TIME_LOCKED); + }); + }); + describe('initially time-locked', async () => { + before('deploy a walet', async () => { + multiSig = await deployer.deployAsync(ContractName.MultiSigWalletWithTimeLock, [ + owners, + SIGNATURES_REQUIRED, + SECONDS_TIME_LOCKED, + ]); + multiSigWrapper = new MultiSigWrapper(multiSig); + + const secondsTimeLocked = await multiSig.secondsTimeLocked(); + initialSecondsTimeLocked = secondsTimeLocked.toNumber(); + const destination = multiSig.address; + const from = owners[0]; + const dataParams = { + name: 'changeTimeLock', + abi: MULTI_SIG_ABI, + args: [newSecondsTimeLocked], + }; + let txHash = await multiSigWrapper.submitTransactionAsync(destination, from, dataParams); + const subRes = await zeroEx.awaitTransactionMinedAsync(txHash); + const log = abiDecoder.tryToDecodeLogOrNoop(subRes.logs[0]) as LogWithDecodedArgs< + SubmissionContractEventArgs + >; + txId = log.args.transactionId.toNumber(); + txHash = await multiSig.confirmTransaction(txId, { + from: owners[1], + }); + const confRes = await zeroEx.awaitTransactionMinedAsync(txHash); + expect(confRes.logs).to.have.length(2); + }); + const newSecondsTimeLocked = 0; + it('should throw if it has enough confirmations but is not past the time lock', async () => { + return expect(multiSig.executeTransaction(txId, { from: owners[0] })).to.be.rejectedWith( + constants.REVERT, + ); + }); + + it('should execute if it has enough confirmations and is past the time lock', async () => { + await rpc.increaseTimeAsync(SECONDS_TIME_LOCKED); + await multiSig.executeTransaction(txId, { from: owners[0] }); + + const secondsTimeLocked = new BigNumber(await multiSig.secondsTimeLocked.call()); + expect(secondsTimeLocked).to.be.bignumber.equal(newSecondsTimeLocked); + }); + }); + }); +}); diff --git a/packages/contracts/test/multi_sig_with_time_lock_except_remove_auth_addr.ts b/packages/contracts/test/multi_sig_with_time_lock_except_remove_auth_addr.ts new file mode 100644 index 000000000..f6b7c1d53 --- /dev/null +++ b/packages/contracts/test/multi_sig_with_time_lock_except_remove_auth_addr.ts @@ -0,0 +1,181 @@ +import { LogWithDecodedArgs, ZeroEx } from '0x.js'; +import { BlockchainLifecycle, devConstants, web3Factory } from '@0xproject/dev-utils'; +import { AbiDecoder } from '@0xproject/utils'; +import { Web3Wrapper } from '@0xproject/web3-wrapper'; +import * as chai from 'chai'; +import * as Web3 from 'web3'; + +import { artifacts } from '../util/artifacts'; +import { constants } from '../util/constants'; +import { crypto } from '../util/crypto'; +import { MultiSigWrapper } from '../util/multi_sig_wrapper'; +import { ContractName, SubmissionContractEventArgs, TransactionDataParams } from '../util/types'; + +import { chaiSetup } from './utils/chai_setup'; +import { deployer } from './utils/deployer'; +const PROXY_ABI = artifacts.TokenTransferProxyArtifact.networks[constants.TESTRPC_NETWORK_ID].abi; +const MUTISIG_WALLET_WITH_TIME_LOCK_EXCEPT_REMOVE_AUTHORIZED_ADDRESS_ABI = + artifacts.MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddressArtifact.networks[constants.TESTRPC_NETWORK_ID] + .abi; + +chaiSetup.configure(); +const expect = chai.expect; +const web3 = web3Factory.create(); +const web3Wrapper = new Web3Wrapper(web3.currentProvider); +const blockchainLifecycle = new BlockchainLifecycle(); +const abiDecoder = new AbiDecoder([MUTISIG_WALLET_WITH_TIME_LOCK_EXCEPT_REMOVE_AUTHORIZED_ADDRESS_ABI]); + +describe('MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress', () => { + const zeroEx = new ZeroEx(web3.currentProvider, { networkId: constants.TESTRPC_NETWORK_ID }); + let owners: string[]; + const requiredApprovals = 2; + const SECONDS_TIME_LOCKED = 1000000; + + // initialize fake addresses + let authorizedAddress: string; + let unauthorizedAddress: string; + + let tokenTransferProxy: Web3.ContractInstance; + let multiSig: Web3.ContractInstance; + let multiSigWrapper: MultiSigWrapper; + + let validDestination: string; + before(async () => { + const accounts = await web3Wrapper.getAvailableAddressesAsync(); + owners = [accounts[0], accounts[1]]; + [authorizedAddress, unauthorizedAddress] = accounts; + const initialOwner = accounts[0]; + tokenTransferProxy = await deployer.deployAsync(ContractName.TokenTransferProxy); + await tokenTransferProxy.addAuthorizedAddress(authorizedAddress, { + from: initialOwner, + }); + multiSig = await deployer.deployAsync(ContractName.MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress, [ + owners, + requiredApprovals, + SECONDS_TIME_LOCKED, + tokenTransferProxy.address, + ]); + await tokenTransferProxy.transferOwnership(multiSig.address, { + from: initialOwner, + }); + multiSigWrapper = new MultiSigWrapper(multiSig); + validDestination = tokenTransferProxy.address; + }); + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + }); + + describe('isFunctionRemoveAuthorizedAddress', () => { + it('should throw if data is not for removeAuthorizedAddress', async () => { + const data = MultiSigWrapper.encodeFnArgs('addAuthorizedAddress', PROXY_ABI, [owners[0]]); + return expect(multiSig.isFunctionRemoveAuthorizedAddress.call(data)).to.be.rejectedWith(constants.REVERT); + }); + + it('should return true if data is for removeAuthorizedAddress', async () => { + const data = MultiSigWrapper.encodeFnArgs('removeAuthorizedAddress', PROXY_ABI, [owners[0]]); + const isFunctionRemoveAuthorizedAddress = await multiSig.isFunctionRemoveAuthorizedAddress.call(data); + expect(isFunctionRemoveAuthorizedAddress).to.be.true(); + }); + }); + + describe('executeRemoveAuthorizedAddress', () => { + it('should throw without the required confirmations', async () => { + const dataParams: TransactionDataParams = { + name: 'removeAuthorizedAddress', + abi: PROXY_ABI, + args: [authorizedAddress], + }; + const txHash = await multiSigWrapper.submitTransactionAsync(validDestination, owners[0], dataParams); + const res = await zeroEx.awaitTransactionMinedAsync(txHash); + const log = abiDecoder.tryToDecodeLogOrNoop(res.logs[0]) as LogWithDecodedArgs<SubmissionContractEventArgs>; + const txId = log.args.transactionId.toString(); + + return expect(multiSig.executeRemoveAuthorizedAddress(txId, { from: owners[1] })).to.be.rejectedWith( + constants.REVERT, + ); + }); + + it('should throw if tx destination is not the tokenTransferProxy', async () => { + const invalidTokenTransferProxy = await deployer.deployAsync(ContractName.TokenTransferProxy); + const invalidDestination = invalidTokenTransferProxy.address; + const dataParams: TransactionDataParams = { + name: 'removeAuthorizedAddress', + abi: PROXY_ABI, + args: [authorizedAddress], + }; + const txHash = await multiSigWrapper.submitTransactionAsync(invalidDestination, owners[0], dataParams); + const res = await zeroEx.awaitTransactionMinedAsync(txHash); + const log = abiDecoder.tryToDecodeLogOrNoop(res.logs[0]) as LogWithDecodedArgs<SubmissionContractEventArgs>; + const txId = log.args.transactionId.toString(); + await multiSig.confirmTransaction(txId, { from: owners[1] }); + const isConfirmed = await multiSig.isConfirmed.call(txId); + expect(isConfirmed).to.be.true(); + + return expect(multiSig.executeRemoveAuthorizedAddress(txId, { from: owners[1] })).to.be.rejectedWith( + constants.REVERT, + ); + }); + + it('should throw if tx data is not for removeAuthorizedAddress', async () => { + const dataParams: TransactionDataParams = { + name: 'addAuthorizedAddress', + abi: PROXY_ABI, + args: [unauthorizedAddress], + }; + const txHash = await multiSigWrapper.submitTransactionAsync(validDestination, owners[0], dataParams); + const res = await zeroEx.awaitTransactionMinedAsync(txHash); + const log = abiDecoder.tryToDecodeLogOrNoop(res.logs[0]) as LogWithDecodedArgs<SubmissionContractEventArgs>; + const txId = log.args.transactionId.toString(); + await multiSig.confirmTransaction(txId, { from: owners[1] }); + const isConfirmed = await multiSig.isConfirmed.call(txId); + expect(isConfirmed).to.be.true(); + + return expect(multiSig.executeRemoveAuthorizedAddress(txId, { from: owners[1] })).to.be.rejectedWith( + constants.REVERT, + ); + }); + + it('should execute removeAuthorizedAddress for valid tokenTransferProxy if fully confirmed', async () => { + const dataParams: TransactionDataParams = { + name: 'removeAuthorizedAddress', + abi: PROXY_ABI, + args: [authorizedAddress], + }; + const txHash = await multiSigWrapper.submitTransactionAsync(validDestination, owners[0], dataParams); + const res = await zeroEx.awaitTransactionMinedAsync(txHash); + const log = abiDecoder.tryToDecodeLogOrNoop(res.logs[0]) as LogWithDecodedArgs<SubmissionContractEventArgs>; + const txId = log.args.transactionId.toString(); + await multiSig.confirmTransaction(txId, { from: owners[1] }); + const isConfirmed = await multiSig.isConfirmed.call(txId); + expect(isConfirmed).to.be.true(); + await multiSig.executeRemoveAuthorizedAddress(txId, { from: owners[1] }); + const isAuthorized = await tokenTransferProxy.authorized.call(authorizedAddress); + expect(isAuthorized).to.be.false(); + }); + + it('should throw if already executed', async () => { + const dataParams: TransactionDataParams = { + name: 'removeAuthorizedAddress', + abi: PROXY_ABI, + args: [authorizedAddress], + }; + const txHash = await multiSigWrapper.submitTransactionAsync(validDestination, owners[0], dataParams); + const res = await zeroEx.awaitTransactionMinedAsync(txHash); + const log = abiDecoder.tryToDecodeLogOrNoop(res.logs[0]) as LogWithDecodedArgs<SubmissionContractEventArgs>; + const txId = log.args.transactionId.toString(); + await multiSig.confirmTransaction(txId, { from: owners[1] }); + const isConfirmed = await multiSig.isConfirmed(txId); + expect(isConfirmed).to.be.true(); + await multiSig.executeRemoveAuthorizedAddress(txId, { from: owners[1] }); + const tx = await multiSig.transactions(txId); + const isExecuted = tx[3]; + expect(isExecuted).to.be.true(); + return expect(multiSig.executeRemoveAuthorizedAddress(txId, { from: owners[1] })).to.be.rejectedWith( + constants.REVERT, + ); + }); + }); +}); diff --git a/packages/contracts/test/ts/token_registry.ts b/packages/contracts/test/token_registry.ts index d1c551565..2a270dc2f 100644 --- a/packages/contracts/test/ts/token_registry.ts +++ b/packages/contracts/test/token_registry.ts @@ -1,22 +1,42 @@ import { ZeroEx } from '0x.js'; +import { BlockchainLifecycle, devConstants, web3Factory } from '@0xproject/dev-utils'; +import { Web3Wrapper } from '@0xproject/web3-wrapper'; import * as chai from 'chai'; import ethUtil = require('ethereumjs-util'); import * as _ from 'lodash'; +import * as Web3 from 'web3'; -import { Artifacts } from '../../util/artifacts'; -import { constants } from '../../util/constants'; -import { TokenRegWrapper } from '../../util/token_registry_wrapper'; -import { ContractInstance } from '../../util/types'; +import { constants } from '../util/constants'; +import { TokenRegWrapper } from '../util/token_registry_wrapper'; +import { ContractName } from '../util/types'; import { chaiSetup } from './utils/chai_setup'; +import { deployer } from './utils/deployer'; -const { TokenRegistry } = new Artifacts(artifacts); chaiSetup.configure(); const expect = chai.expect; - -contract('TokenRegistry', (accounts: string[]) => { - const owner = accounts[0]; - const notOwner = accounts[1]; +const web3 = web3Factory.create(); +const web3Wrapper = new Web3Wrapper(web3.currentProvider); +const blockchainLifecycle = new BlockchainLifecycle(); + +describe('TokenRegistry', () => { + let owner: string; + let notOwner: string; + let tokenReg: Web3.ContractInstance; + let tokenRegWrapper: TokenRegWrapper; + before(async () => { + const accounts = await web3Wrapper.getAvailableAddressesAsync(); + owner = accounts[0]; + notOwner = accounts[1]; + tokenReg = await deployer.deployAsync(ContractName.TokenRegistry); + tokenRegWrapper = new TokenRegWrapper(tokenReg); + }); + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + }); const tokenAddress1 = `0x${ethUtil.setLength(ethUtil.toBuffer('0x1'), 20, false).toString('hex')}`; const tokenAddress2 = `0x${ethUtil.setLength(ethUtil.toBuffer('0x2'), 20, false).toString('hex')}`; @@ -48,14 +68,6 @@ contract('TokenRegistry', (accounts: string[]) => { swarmHash: constants.NULL_BYTES, }; - let tokenReg: ContractInstance; - let tokenRegWrapper: TokenRegWrapper; - - beforeEach(async () => { - tokenReg = await TokenRegistry.new(); - tokenRegWrapper = new TokenRegWrapper(tokenReg); - }); - describe('addToken', () => { it('should throw when not called by owner', async () => { return expect(tokenRegWrapper.addTokenAsync(token1, notOwner)).to.be.rejectedWith(constants.REVERT); @@ -125,10 +137,9 @@ contract('TokenRegistry', (accounts: string[]) => { }); it('should change the token name when called by owner', async () => { - const res = await tokenReg.setTokenName(token1.address, token2.name, { + await tokenReg.setTokenName(token1.address, token2.name, { from: owner, }); - expect(res.logs).to.have.length(1); const [newData, oldData] = await Promise.all([ tokenRegWrapper.getTokenByNameAsync(token2.name), tokenRegWrapper.getTokenByNameAsync(token1.name), @@ -165,8 +176,7 @@ contract('TokenRegistry', (accounts: string[]) => { }); it('should change the token symbol when called by owner', async () => { - const res = await tokenReg.setTokenSymbol(token1.address, token2.symbol, { from: owner }); - expect(res.logs).to.have.length(1); + await tokenReg.setTokenSymbol(token1.address, token2.symbol, { from: owner }); const [newData, oldData] = await Promise.all([ tokenRegWrapper.getTokenBySymbolAsync(token2.symbol), tokenRegWrapper.getTokenBySymbolAsync(token1.symbol), @@ -207,10 +217,9 @@ contract('TokenRegistry', (accounts: string[]) => { it('should remove token metadata when called by owner', async () => { const index = 0; - const res = await tokenReg.removeToken(token1.address, index, { + await tokenReg.removeToken(token1.address, index, { from: owner, }); - expect(res.logs).to.have.length(1); const tokenData = await tokenRegWrapper.getTokenMetaDataAsync(token1.address); expect(tokenData).to.be.deep.equal(nullToken); }); diff --git a/packages/contracts/test/ts/token_transfer_proxy/auth.ts b/packages/contracts/test/token_transfer_proxy/auth.ts index 9ae0a8fc3..b2bfc6b65 100644 --- a/packages/contracts/test/ts/token_transfer_proxy/auth.ts +++ b/packages/contracts/test/token_transfer_proxy/auth.ts @@ -1,44 +1,50 @@ +import { BlockchainLifecycle, devConstants, web3Factory } from '@0xproject/dev-utils'; +import { Web3Wrapper } from '@0xproject/web3-wrapper'; import * as chai from 'chai'; +import * as Web3 from 'web3'; -import { constants } from '../../../util/constants'; -import { ContractInstance } from '../../../util/types'; +import { constants } from '../../util/constants'; +import { ContractName } from '../../util/types'; import { chaiSetup } from '../utils/chai_setup'; +import { deployer } from '../utils/deployer'; chaiSetup.configure(); const expect = chai.expect; -const TokenTransferProxy = artifacts.require('./db/TokenTransferProxy.sol'); - -contract('TokenTransferProxy', (accounts: string[]) => { - const owner = accounts[0]; - const notOwner = accounts[1]; - - let tokenTransferProxy: ContractInstance; - let authorized: string; - let notAuthorized = owner; +const web3 = web3Factory.create(); +const web3Wrapper = new Web3Wrapper(web3.currentProvider); +const blockchainLifecycle = new BlockchainLifecycle(); +describe('TokenTransferProxy', () => { + let owner: string; + let notOwner: string; + let address: string; + let tokenTransferProxy: Web3.ContractInstance; before(async () => { - tokenTransferProxy = await TokenTransferProxy.deployed(); + const accounts = await web3Wrapper.getAvailableAddressesAsync(); + owner = address = accounts[0]; + notOwner = accounts[1]; + tokenTransferProxy = await deployer.deployAsync(ContractName.TokenTransferProxy); + }); + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); }); - describe('addAuthorizedAddress', () => { it('should throw if not called by owner', async () => { return expect(tokenTransferProxy.addAuthorizedAddress(notOwner, { from: notOwner })).to.be.rejectedWith( constants.REVERT, ); }); - it('should allow owner to add an authorized address', async () => { - await tokenTransferProxy.addAuthorizedAddress(notAuthorized, { - from: owner, - }); - authorized = notAuthorized; - notAuthorized = null; - const isAuthorized = await tokenTransferProxy.authorized.call(authorized); + await tokenTransferProxy.addAuthorizedAddress(address, { from: owner }); + const isAuthorized = await tokenTransferProxy.authorized(address); expect(isAuthorized).to.be.true(); }); - it('should throw if owner attempts to authorize a duplicate address', async () => { - return expect(tokenTransferProxy.addAuthorizedAddress(authorized, { from: owner })).to.be.rejectedWith( + await tokenTransferProxy.addAuthorizedAddress(address, { from: owner }); + return expect(tokenTransferProxy.addAuthorizedAddress(address, { from: owner })).to.be.rejectedWith( constants.REVERT, ); }); @@ -46,27 +52,26 @@ contract('TokenTransferProxy', (accounts: string[]) => { describe('removeAuthorizedAddress', () => { it('should throw if not called by owner', async () => { + await tokenTransferProxy.addAuthorizedAddress(address, { from: owner }); return expect( - tokenTransferProxy.removeAuthorizedAddress(authorized, { + tokenTransferProxy.removeAuthorizedAddress(address, { from: notOwner, }), ).to.be.rejectedWith(constants.REVERT); }); it('should allow owner to remove an authorized address', async () => { - await tokenTransferProxy.removeAuthorizedAddress(authorized, { + await tokenTransferProxy.addAuthorizedAddress(address, { from: owner }); + await tokenTransferProxy.removeAuthorizedAddress(address, { from: owner, }); - notAuthorized = authorized; - authorized = null; - - const isAuthorized = await tokenTransferProxy.authorized.call(notAuthorized); + const isAuthorized = await tokenTransferProxy.authorized(address); expect(isAuthorized).to.be.false(); }); it('should throw if owner attempts to remove an address that is not authorized', async () => { return expect( - tokenTransferProxy.removeAuthorizedAddress(notAuthorized, { + tokenTransferProxy.removeAuthorizedAddress(address, { from: owner, }), ).to.be.rejectedWith(constants.REVERT); @@ -76,24 +81,19 @@ contract('TokenTransferProxy', (accounts: string[]) => { describe('getAuthorizedAddresses', () => { it('should return all authorized addresses', async () => { const initial = await tokenTransferProxy.getAuthorizedAddresses(); - expect(initial).to.have.length(1); - await tokenTransferProxy.addAuthorizedAddress(notAuthorized, { + expect(initial).to.have.length(0); + await tokenTransferProxy.addAuthorizedAddress(address, { from: owner, }); - - authorized = notAuthorized; - notAuthorized = null; const afterAdd = await tokenTransferProxy.getAuthorizedAddresses(); - expect(afterAdd).to.have.length(2); - expect(afterAdd).to.include(authorized); + expect(afterAdd).to.have.length(1); + expect(afterAdd).to.include(address); - await tokenTransferProxy.removeAuthorizedAddress(authorized, { + await tokenTransferProxy.removeAuthorizedAddress(address, { from: owner, }); - notAuthorized = authorized; - authorized = null; const afterRemove = await tokenTransferProxy.getAuthorizedAddresses(); - expect(afterRemove).to.have.length(1); + expect(afterRemove).to.have.length(0); }); }); }); diff --git a/packages/contracts/test/ts/token_transfer_proxy/transfer_from.ts b/packages/contracts/test/token_transfer_proxy/transfer_from.ts index e1aff6dae..bd7adcfae 100644 --- a/packages/contracts/test/ts/token_transfer_proxy/transfer_from.ts +++ b/packages/contracts/test/token_transfer_proxy/transfer_from.ts @@ -1,47 +1,55 @@ +import { BlockchainLifecycle, devConstants, web3Factory } from '@0xproject/dev-utils'; +import { Web3Wrapper } from '@0xproject/web3-wrapper'; import * as chai from 'chai'; +import * as Web3 from 'web3'; -import { Artifacts } from '../../../util/artifacts'; -import { Balances } from '../../../util/balances'; -import { constants } from '../../../util/constants'; -import { ContractInstance } from '../../../util/types'; +import { Balances } from '../../util/balances'; +import { constants } from '../../util/constants'; +import { ContractName } from '../../util/types'; import { chaiSetup } from '../utils/chai_setup'; +import { deployer } from '../utils/deployer'; chaiSetup.configure(); const expect = chai.expect; -const { TokenTransferProxy, DummyToken, TokenRegistry } = new Artifacts(artifacts); +const web3 = web3Factory.create(); +const web3Wrapper = new Web3Wrapper(web3.currentProvider); +const blockchainLifecycle = new BlockchainLifecycle(); -contract('TokenTransferProxy', (accounts: string[]) => { +describe('TokenTransferProxy', () => { + let accounts: string[]; + let owner: string; + let notAuthorized: string; const INIT_BAL = 100000000; const INIT_ALLOW = 100000000; - const owner = accounts[0]; - const notAuthorized = owner; - - let tokenTransferProxy: ContractInstance; - let tokenRegistry: ContractInstance; - let rep: ContractInstance; + let tokenTransferProxy: Web3.ContractInstance; + let rep: Web3.ContractInstance; let dmyBalances: Balances; before(async () => { - [tokenTransferProxy, tokenRegistry] = await Promise.all([ - TokenTransferProxy.deployed(), - TokenRegistry.deployed(), - ]); - const repAddress = await tokenRegistry.getTokenAddressBySymbol('REP'); - rep = DummyToken.at(repAddress); + accounts = await web3Wrapper.getAvailableAddressesAsync(); + owner = notAuthorized = accounts[0]; + tokenTransferProxy = await deployer.deployAsync(ContractName.TokenTransferProxy); + rep = await deployer.deployAsync(ContractName.DummyToken); dmyBalances = new Balances([rep], [accounts[0], accounts[1]]); await Promise.all([ - rep.approve(TokenTransferProxy.address, INIT_ALLOW, { + rep.approve(tokenTransferProxy.address, INIT_ALLOW, { from: accounts[0], }), rep.setBalance(accounts[0], INIT_BAL, { from: owner }), - rep.approve(TokenTransferProxy.address, INIT_ALLOW, { + rep.approve(tokenTransferProxy.address, INIT_ALLOW, { from: accounts[1], }), rep.setBalance(accounts[1], INIT_BAL, { from: owner }), ]); }); + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + }); describe('transferFrom', () => { it('should throw when called by an unauthorized address', async () => { diff --git a/packages/contracts/test/ts/multi_sig_with_time_lock.ts b/packages/contracts/test/ts/multi_sig_with_time_lock.ts deleted file mode 100644 index ea939a758..000000000 --- a/packages/contracts/test/ts/multi_sig_with_time_lock.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { RPC } from '@0xproject/dev-utils'; -import { BigNumber, promisify } from '@0xproject/utils'; -import * as chai from 'chai'; -import Web3 = require('web3'); - -import * as multiSigWalletJSON from '../../build/contracts/MultiSigWalletWithTimeLock.json'; -import * as truffleConf from '../../truffle.js'; -import { Artifacts } from '../../util/artifacts'; -import { constants } from '../../util/constants'; -import { MultiSigWrapper } from '../../util/multi_sig_wrapper'; -import { ContractInstance } from '../../util/types'; - -import { chaiSetup } from './utils/chai_setup'; - -const { MultiSigWalletWithTimeLock } = new Artifacts(artifacts); - -const MULTI_SIG_ABI = (multiSigWalletJSON as any).abi; -chaiSetup.configure(); -const expect = chai.expect; - -// In order to benefit from type-safety, we re-assign the global web3 instance injected by Truffle -// with type `any` to a variable of type `Web3`. -const web3: Web3 = (global as any).web3; - -contract('MultiSigWalletWithTimeLock', (accounts: string[]) => { - const owners = [accounts[0], accounts[1]]; - const SECONDS_TIME_LOCKED = 10000; - - let multiSig: ContractInstance; - let multiSigWrapper: MultiSigWrapper; - let txId: number; - let initialSecondsTimeLocked: number; - let rpc: RPC; - - before(async () => { - multiSig = await MultiSigWalletWithTimeLock.deployed(); - multiSigWrapper = new MultiSigWrapper(multiSig); - - const secondsTimeLocked = await multiSig.secondsTimeLocked.call(); - initialSecondsTimeLocked = secondsTimeLocked.toNumber(); - const rpcUrl = `http://${truffleConf.networks.development.host}:${truffleConf.networks.development.port}`; - rpc = new RPC(rpcUrl); - }); - - describe('changeTimeLock', () => { - it('should throw when not called by wallet', async () => { - return expect(multiSig.changeTimeLock(SECONDS_TIME_LOCKED, { from: owners[0] })).to.be.rejectedWith( - constants.REVERT, - ); - }); - - it('should throw without enough confirmations', async () => { - const destination = multiSig.address; - const from = owners[0]; - const dataParams = { - name: 'changeTimeLock', - abi: MULTI_SIG_ABI, - args: [SECONDS_TIME_LOCKED], - }; - const subRes = await multiSigWrapper.submitTransactionAsync(destination, from, dataParams); - - txId = subRes.logs[0].args.transactionId.toNumber(); - return expect(multiSig.executeTransaction(txId)).to.be.rejectedWith(constants.REVERT); - }); - - it('should set confirmation time with enough confirmations', async () => { - const res = await multiSig.confirmTransaction(txId, { from: owners[1] }); - expect(res.logs).to.have.length(2); - const blockNum = await promisify<number>(web3.eth.getBlockNumber)(); - const blockInfo = await promisify<Web3.BlockWithoutTransactionData>(web3.eth.getBlock)(blockNum); - const timestamp = new BigNumber(blockInfo.timestamp); - const confirmationTimeBigNum = new BigNumber(await multiSig.confirmationTimes.call(txId)); - - expect(timestamp).to.be.bignumber.equal(confirmationTimeBigNum); - }); - - it('should be executable with enough confirmations and secondsTimeLocked of 0', async () => { - expect(initialSecondsTimeLocked).to.be.equal(0); - - const res = await multiSig.executeTransaction(txId); - expect(res.logs).to.have.length(2); - - const secondsTimeLocked = new BigNumber(await multiSig.secondsTimeLocked.call()); - expect(secondsTimeLocked).to.be.bignumber.equal(SECONDS_TIME_LOCKED); - }); - - const newSecondsTimeLocked = 0; - it('should throw if it has enough confirmations but is not past the time lock', async () => { - const destination = multiSig.address; - const from = owners[0]; - const dataParams = { - name: 'changeTimeLock', - abi: MULTI_SIG_ABI, - args: [newSecondsTimeLocked], - }; - const subRes = await multiSigWrapper.submitTransactionAsync(destination, from, dataParams); - - txId = subRes.logs[0].args.transactionId.toNumber(); - const confRes = await multiSig.confirmTransaction(txId, { - from: owners[1], - }); - expect(confRes.logs).to.have.length(2); - - return expect(multiSig.executeTransaction(txId)).to.be.rejectedWith(constants.REVERT); - }); - - it('should execute if it has enough confirmations and is past the time lock', async () => { - await rpc.increaseTimeAsync(SECONDS_TIME_LOCKED); - await multiSig.executeTransaction(txId); - - const secondsTimeLocked = new BigNumber(await multiSig.secondsTimeLocked.call()); - expect(secondsTimeLocked).to.be.bignumber.equal(newSecondsTimeLocked); - }); - }); -}); diff --git a/packages/contracts/test/ts/multi_sig_with_time_lock_except_remove_auth_addr.ts b/packages/contracts/test/ts/multi_sig_with_time_lock_except_remove_auth_addr.ts deleted file mode 100644 index 62aa625fe..000000000 --- a/packages/contracts/test/ts/multi_sig_with_time_lock_except_remove_auth_addr.ts +++ /dev/null @@ -1,150 +0,0 @@ -import * as chai from 'chai'; - -import * as tokenTransferProxyJSON from '../../build/contracts/TokenTransferProxy.json'; -import { Artifacts } from '../../util/artifacts'; -import { constants } from '../../util/constants'; -import { crypto } from '../../util/crypto'; -import { MultiSigWrapper } from '../../util/multi_sig_wrapper'; -import { ContractInstance, TransactionDataParams } from '../../util/types'; - -import { chaiSetup } from './utils/chai_setup'; -const { TokenTransferProxy, MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress } = new Artifacts(artifacts); -const PROXY_ABI = (tokenTransferProxyJSON as any).abi; - -chaiSetup.configure(); -const expect = chai.expect; - -contract('MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress', (accounts: string[]) => { - const owners = [accounts[0], accounts[1]]; - const requiredApprovals = 2; - const SECONDS_TIME_LOCKED = 1000000; - - // initialize fake addresses - const authorizedAddress = `0x${crypto - .solSHA3([accounts[0]]) - .slice(0, 20) - .toString('hex')}`; - const unauthorizedAddress = `0x${crypto - .solSHA3([accounts[1]]) - .slice(0, 20) - .toString('hex')}`; - - let tokenTransferProxy: ContractInstance; - let multiSig: ContractInstance; - let multiSigWrapper: MultiSigWrapper; - - let validDestination: string; - - beforeEach(async () => { - const initialOwner = accounts[0]; - tokenTransferProxy = await TokenTransferProxy.new({ from: initialOwner }); - await tokenTransferProxy.addAuthorizedAddress(authorizedAddress, { - from: initialOwner, - }); - multiSig = await MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress.new( - owners, - requiredApprovals, - SECONDS_TIME_LOCKED, - tokenTransferProxy.address, - ); - await tokenTransferProxy.transferOwnership(multiSig.address, { - from: initialOwner, - }); - multiSigWrapper = new MultiSigWrapper(multiSig); - validDestination = tokenTransferProxy.address; - }); - - describe('isFunctionRemoveAuthorizedAddress', () => { - it('should throw if data is not for removeAuthorizedAddress', async () => { - const data = MultiSigWrapper.encodeFnArgs('addAuthorizedAddress', PROXY_ABI, [owners[0]]); - return expect(multiSig.isFunctionRemoveAuthorizedAddress.call(data)).to.be.rejectedWith(constants.REVERT); - }); - - it('should return true if data is for removeAuthorizedAddress', async () => { - const data = MultiSigWrapper.encodeFnArgs('removeAuthorizedAddress', PROXY_ABI, [owners[0]]); - const isFunctionRemoveAuthorizedAddress = await multiSig.isFunctionRemoveAuthorizedAddress.call(data); - expect(isFunctionRemoveAuthorizedAddress).to.be.true(); - }); - }); - - describe('executeRemoveAuthorizedAddress', () => { - it('should throw without the required confirmations', async () => { - const dataParams: TransactionDataParams = { - name: 'removeAuthorizedAddress', - abi: PROXY_ABI, - args: [authorizedAddress], - }; - const res = await multiSigWrapper.submitTransactionAsync(validDestination, owners[0], dataParams); - const txId = res.logs[0].args.transactionId.toString(); - - return expect(multiSig.executeRemoveAuthorizedAddress(txId)).to.be.rejectedWith(constants.REVERT); - }); - - it('should throw if tx destination is not the tokenTransferProxy', async () => { - const invalidTokenTransferProxy = await TokenTransferProxy.new(); - const invalidDestination = invalidTokenTransferProxy.address; - const dataParams: TransactionDataParams = { - name: 'removeAuthorizedAddress', - abi: PROXY_ABI, - args: [authorizedAddress], - }; - const res = await multiSigWrapper.submitTransactionAsync(invalidDestination, owners[0], dataParams); - const txId = res.logs[0].args.transactionId.toString(); - await multiSig.confirmTransaction(txId, { from: owners[1] }); - const isConfirmed = await multiSig.isConfirmed.call(txId); - expect(isConfirmed).to.be.true(); - - return expect(multiSig.executeRemoveAuthorizedAddress(txId)).to.be.rejectedWith(constants.REVERT); - }); - - it('should throw if tx data is not for removeAuthorizedAddress', async () => { - const dataParams: TransactionDataParams = { - name: 'addAuthorizedAddress', - abi: PROXY_ABI, - args: [unauthorizedAddress], - }; - const res = await multiSigWrapper.submitTransactionAsync(validDestination, owners[0], dataParams); - const txId = res.logs[0].args.transactionId.toString(); - await multiSig.confirmTransaction(txId, { from: owners[1] }); - const isConfirmed = await multiSig.isConfirmed.call(txId); - expect(isConfirmed).to.be.true(); - - return expect(multiSig.executeRemoveAuthorizedAddress(txId)).to.be.rejectedWith(constants.REVERT); - }); - - it('should execute removeAuthorizedAddress for valid tokenTransferProxy if fully confirmed', async () => { - const dataParams: TransactionDataParams = { - name: 'removeAuthorizedAddress', - abi: PROXY_ABI, - args: [authorizedAddress], - }; - const res = await multiSigWrapper.submitTransactionAsync(validDestination, owners[0], dataParams); - const txId = res.logs[0].args.transactionId.toString(); - await multiSig.confirmTransaction(txId, { from: owners[1] }); - const isConfirmed = await multiSig.isConfirmed.call(txId); - expect(isConfirmed).to.be.true(); - await multiSig.executeRemoveAuthorizedAddress(txId); - - const isAuthorized = await tokenTransferProxy.authorized.call(authorizedAddress); - expect(isAuthorized).to.be.false(); - }); - - it('should throw if already executed', async () => { - const dataParams: TransactionDataParams = { - name: 'removeAuthorizedAddress', - abi: PROXY_ABI, - args: [authorizedAddress], - }; - const res = await multiSigWrapper.submitTransactionAsync(validDestination, owners[0], dataParams); - const txId = res.logs[0].args.transactionId.toString(); - await multiSig.confirmTransaction(txId, { from: owners[1] }); - const isConfirmed = await multiSig.isConfirmed.call(txId); - expect(isConfirmed).to.be.true(); - await multiSig.executeRemoveAuthorizedAddress(txId); - const tx = await multiSig.transactions.call(txId); - const isExecuted = tx[3]; - expect(isExecuted).to.be.true(); - return expect(multiSig.executeRemoveAuthorizedAddress(txId)).to.be.rejectedWith(constants.REVERT); - }); - }); -}); diff --git a/packages/contracts/test/ts/unlimited_allowance_token.ts b/packages/contracts/test/unlimited_allowance_token.ts index c90a52095..7f4fef987 100644 --- a/packages/contracts/test/ts/unlimited_allowance_token.ts +++ b/packages/contracts/test/unlimited_allowance_token.ts @@ -1,37 +1,48 @@ import { ZeroEx } from '0x.js'; +import { BlockchainLifecycle, devConstants, web3Factory } from '@0xproject/dev-utils'; import { BigNumber } from '@0xproject/utils'; +import { Web3Wrapper } from '@0xproject/web3-wrapper'; import * as chai from 'chai'; import * as Web3 from 'web3'; -import { Artifacts } from '../../util/artifacts'; -import { constants } from '../../util/constants'; -import { ContractInstance } from '../../util/types'; +import { constants } from '../util/constants'; +import { ContractName } from '../util/types'; import { chaiSetup } from './utils/chai_setup'; +import { deployer } from './utils/deployer'; -const { DummyToken } = new Artifacts(artifacts); -const web3: Web3 = (global as any).web3; +const web3 = web3Factory.create(); +const web3Wrapper = new Web3Wrapper(web3.currentProvider); chaiSetup.configure(); const expect = chai.expect; +const blockchainLifecycle = new BlockchainLifecycle(); -contract('UnlimitedAllowanceToken', (accounts: string[]) => { +describe('UnlimitedAllowanceToken', () => { + let owner: string; + let spender: string; const config = { networkId: constants.TESTRPC_NETWORK_ID, }; const zeroEx = new ZeroEx(web3.currentProvider, config); - const owner = accounts[0]; - const spender = accounts[1]; const MAX_MINT_VALUE = new BigNumber(100000000000000000000); let tokenAddress: string; - let token: ContractInstance; + let token: Web3.ContractInstance; - beforeEach(async () => { - token = await DummyToken.new({ from: owner }); + before(async () => { + const accounts = await web3Wrapper.getAvailableAddressesAsync(); + owner = accounts[0]; + spender = accounts[1]; + token = await deployer.deployAsync(ContractName.DummyToken); await token.mint(MAX_MINT_VALUE, { from: owner }); tokenAddress = token.address; }); - + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + }); describe('transfer', () => { it('should transfer balance from sender to receiver', async () => { const receiver = spender; diff --git a/packages/contracts/test/ts/unlimited_allowance_token_v2.ts b/packages/contracts/test/unlimited_allowance_token_v2.ts index 1b29a02ba..440cc11ac 100644 --- a/packages/contracts/test/ts/unlimited_allowance_token_v2.ts +++ b/packages/contracts/test/unlimited_allowance_token_v2.ts @@ -1,37 +1,48 @@ import { ZeroEx } from '0x.js'; +import { BlockchainLifecycle, devConstants, web3Factory } from '@0xproject/dev-utils'; import { BigNumber } from '@0xproject/utils'; +import { Web3Wrapper } from '@0xproject/web3-wrapper'; import * as chai from 'chai'; import * as Web3 from 'web3'; -import { Artifacts } from '../../util/artifacts'; -import { constants } from '../../util/constants'; -import { ContractInstance } from '../../util/types'; +import { constants } from '../util/constants'; +import { ContractName } from '../util/types'; import { chaiSetup } from './utils/chai_setup'; +import { deployer } from './utils/deployer'; -const { DummyTokenV2 } = new Artifacts(artifacts); -const web3: Web3 = (global as any).web3; chaiSetup.configure(); const expect = chai.expect; +const web3 = web3Factory.create(); +const web3Wrapper = new Web3Wrapper(web3.currentProvider); +const blockchainLifecycle = new BlockchainLifecycle(); -contract('UnlimitedAllowanceTokenV2', (accounts: string[]) => { +describe('UnlimitedAllowanceTokenV2', () => { const config = { networkId: constants.TESTRPC_NETWORK_ID, }; const zeroEx = new ZeroEx(web3.currentProvider, config); - const owner = accounts[0]; - const spender = accounts[1]; + let owner: string; + let spender: string; const MAX_MINT_VALUE = new BigNumber(100000000000000000000); let tokenAddress: string; - let token: ContractInstance; + let token: Web3.ContractInstance; - beforeEach(async () => { - token = await DummyTokenV2.new({ from: owner }); + before(async () => { + const accounts = await web3Wrapper.getAvailableAddressesAsync(); + owner = accounts[0]; + spender = accounts[1]; + token = await deployer.deployAsync(ContractName.DummyToken_v2); await token.mint(MAX_MINT_VALUE, { from: owner }); tokenAddress = token.address; }); - + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + }); describe('transfer', () => { it('should throw if owner has insufficient balance', async () => { const ownerBalance = await zeroEx.token.getBalanceAsync(tokenAddress, owner); diff --git a/packages/contracts/test/ts/utils/chai_setup.ts b/packages/contracts/test/utils/chai_setup.ts index 078edd309..078edd309 100644 --- a/packages/contracts/test/ts/utils/chai_setup.ts +++ b/packages/contracts/test/utils/chai_setup.ts diff --git a/packages/contracts/test/utils/deployer.ts b/packages/contracts/test/utils/deployer.ts new file mode 100644 index 000000000..4c6eeff2b --- /dev/null +++ b/packages/contracts/test/utils/deployer.ts @@ -0,0 +1,16 @@ +import { Deployer } from '@0xproject/deployer'; +import { devConstants } from '@0xproject/dev-utils'; +import * as path from 'path'; + +import { constants } from '../../util/constants'; + +const deployerOpts = { + artifactsDir: `${path.resolve('build')}/artifacts`, + jsonrpcPort: devConstants.RPC_PORT, + networkId: constants.TESTRPC_NETWORK_ID, + defaults: { + gas: devConstants.GAS_ESTIMATE, + }, +}; + +export const deployer = new Deployer(deployerOpts); diff --git a/packages/contracts/test/ts/zrx_token.ts b/packages/contracts/test/zrx_token.ts index 766c94c2a..1844a67af 100644 --- a/packages/contracts/test/ts/zrx_token.ts +++ b/packages/contracts/test/zrx_token.ts @@ -1,60 +1,70 @@ import { ZeroEx } from '0x.js'; +import { BlockchainLifecycle, devConstants, web3Factory } from '@0xproject/dev-utils'; import { BigNumber } from '@0xproject/utils'; +import { Web3Wrapper } from '@0xproject/web3-wrapper'; import * as chai from 'chai'; -import Web3 = require('web3'); +import * as Web3 from 'web3'; -import { Artifacts } from '../../util/artifacts'; -import { constants } from '../../util/constants'; -import { ContractInstance } from '../../util/types'; +import { constants } from '../util/constants'; +import { ContractName } from '../util/types'; import { chaiSetup } from './utils/chai_setup'; +import { deployer } from './utils/deployer'; chaiSetup.configure(); const expect = chai.expect; -const { Exchange, ZRXToken } = new Artifacts(artifacts); -const web3: Web3 = (global as any).web3; +const web3 = web3Factory.create(); +const web3Wrapper = new Web3Wrapper(web3.currentProvider); +const blockchainLifecycle = new BlockchainLifecycle(); -contract('ZRXToken', (accounts: string[]) => { - const owner = accounts[0]; - const spender = accounts[1]; +describe('ZRXToken', () => { + let owner: string; + let spender: string; let zeroEx: ZeroEx; let MAX_UINT: BigNumber; - let zrx: ContractInstance; + let zrx: Web3.ContractInstance; let zrxAddress: string; - beforeEach(async () => { + before(async () => { + const accounts = await web3Wrapper.getAvailableAddressesAsync(); + owner = accounts[0]; + spender = accounts[1]; zeroEx = new ZeroEx(web3.currentProvider, { - exchangeContractAddress: Exchange.address, networkId: constants.TESTRPC_NETWORK_ID, }); - zrx = await ZRXToken.new(); + zrx = await deployer.deployAsync(ContractName.ZRXToken); zrxAddress = zrx.address; MAX_UINT = zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS; }); - + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + }); describe('constants', () => { it('should have 18 decimals', async () => { - const decimals = new BigNumber(await zrx.decimals.call()); + const decimals = new BigNumber(await zrx.decimals()); const expectedDecimals = 18; expect(decimals).to.be.bignumber.equal(expectedDecimals); }); it('should have a total supply of 1 billion tokens', async () => { - const totalSupply = new BigNumber(await zrx.totalSupply.call()); + const totalSupply = new BigNumber(await zrx.totalSupply()); const expectedTotalSupply = 1000000000; expect(ZeroEx.toUnitAmount(totalSupply, 18)).to.be.bignumber.equal(expectedTotalSupply); }); it('should be named 0x Protocol Token', async () => { - const name = await zrx.name.call(); + const name = await zrx.name(); const expectedName = '0x Protocol Token'; expect(name).to.be.equal(expectedName); }); it('should have the symbol ZRX', async () => { - const symbol = await zrx.symbol.call(); + const symbol = await zrx.symbol(); const expectedSymbol = 'ZRX'; expect(symbol).to.be.equal(expectedSymbol); }); @@ -63,7 +73,7 @@ contract('ZRXToken', (accounts: string[]) => { describe('constructor', () => { it('should initialize owner balance to totalSupply', async () => { const ownerBalance = await zeroEx.token.getBalanceAsync(zrxAddress, owner); - const totalSupply = new BigNumber(await zrx.totalSupply.call()); + const totalSupply = new BigNumber(await zrx.totalSupply()); expect(totalSupply).to.be.bignumber.equal(ownerBalance); }); }); @@ -73,8 +83,7 @@ contract('ZRXToken', (accounts: string[]) => { const receiver = spender; const initOwnerBalance = await zeroEx.token.getBalanceAsync(zrxAddress, owner); const amountToTransfer = new BigNumber(1); - const txHash = await zeroEx.token.transferAsync(zrxAddress, owner, receiver, amountToTransfer); - await zeroEx.awaitTransactionMinedAsync(txHash); + await zeroEx.token.transferAsync(zrxAddress, owner, receiver, amountToTransfer); const finalOwnerBalance = await zeroEx.token.getBalanceAsync(zrxAddress, owner); const finalReceiverBalance = await zeroEx.token.getBalanceAsync(zrxAddress, receiver); @@ -96,10 +105,9 @@ contract('ZRXToken', (accounts: string[]) => { it('should return false if owner has insufficient balance', async () => { const ownerBalance = await zeroEx.token.getBalanceAsync(zrxAddress, owner); const amountToTransfer = ownerBalance.plus(1); - const txHash = await zeroEx.token.setAllowanceAsync(zrxAddress, owner, spender, amountToTransfer, { + await zeroEx.token.setAllowanceAsync(zrxAddress, owner, spender, amountToTransfer, { gasLimit: constants.MAX_TOKEN_APPROVE_GAS, }); - await zeroEx.awaitTransactionMinedAsync(txHash); const didReturnTrue = await zrx.transferFrom.call(owner, spender, amountToTransfer, { from: spender }); expect(didReturnTrue).to.be.false(); }); @@ -126,14 +134,12 @@ contract('ZRXToken', (accounts: string[]) => { const initOwnerBalance = await zeroEx.token.getBalanceAsync(zrxAddress, owner); const amountToTransfer = initOwnerBalance; const initSpenderAllowance = MAX_UINT; - let txHash = await zeroEx.token.setAllowanceAsync(zrxAddress, owner, spender, initSpenderAllowance, { + await zeroEx.token.setAllowanceAsync(zrxAddress, owner, spender, initSpenderAllowance, { gasLimit: constants.MAX_TOKEN_APPROVE_GAS, }); - await zeroEx.awaitTransactionMinedAsync(txHash); - txHash = await zeroEx.token.transferFromAsync(zrxAddress, owner, spender, spender, amountToTransfer, { + await zeroEx.token.transferFromAsync(zrxAddress, owner, spender, spender, amountToTransfer, { gasLimit: constants.MAX_TOKEN_TRANSFERFROM_GAS, }); - await zeroEx.awaitTransactionMinedAsync(txHash); const newSpenderAllowance = await zeroEx.token.getAllowanceAsync(zrxAddress, owner, spender); expect(initSpenderAllowance).to.be.bignumber.equal(newSpenderAllowance); @@ -144,12 +150,10 @@ contract('ZRXToken', (accounts: string[]) => { const initSpenderBalance = await zeroEx.token.getBalanceAsync(zrxAddress, spender); const amountToTransfer = initOwnerBalance; const initSpenderAllowance = initOwnerBalance; - let txHash = await zeroEx.token.setAllowanceAsync(zrxAddress, owner, spender, initSpenderAllowance); - await zeroEx.awaitTransactionMinedAsync(txHash); - txHash = await zeroEx.token.transferFromAsync(zrxAddress, owner, spender, spender, amountToTransfer, { + await zeroEx.token.setAllowanceAsync(zrxAddress, owner, spender, initSpenderAllowance); + await zeroEx.token.transferFromAsync(zrxAddress, owner, spender, spender, amountToTransfer, { gasLimit: constants.MAX_TOKEN_TRANSFERFROM_GAS, }); - await zeroEx.awaitTransactionMinedAsync(txHash); const newOwnerBalance = await zeroEx.token.getBalanceAsync(zrxAddress, owner); const newSpenderBalance = await zeroEx.token.getBalanceAsync(zrxAddress, spender); @@ -161,12 +165,10 @@ contract('ZRXToken', (accounts: string[]) => { it('should modify allowance if spender has sufficient allowance less than 2^256 - 1', async () => { const initOwnerBalance = await zeroEx.token.getBalanceAsync(zrxAddress, owner); const amountToTransfer = initOwnerBalance; - let txHash = await zeroEx.token.setAllowanceAsync(zrxAddress, owner, spender, amountToTransfer); - await zeroEx.awaitTransactionMinedAsync(txHash); - txHash = await zeroEx.token.transferFromAsync(zrxAddress, owner, spender, spender, amountToTransfer, { + await zeroEx.token.setAllowanceAsync(zrxAddress, owner, spender, amountToTransfer); + await zeroEx.token.transferFromAsync(zrxAddress, owner, spender, spender, amountToTransfer, { gasLimit: constants.MAX_TOKEN_TRANSFERFROM_GAS, }); - await zeroEx.awaitTransactionMinedAsync(txHash); const newSpenderAllowance = await zeroEx.token.getAllowanceAsync(zrxAddress, owner, spender); expect(newSpenderAllowance).to.be.bignumber.equal(0); diff --git a/packages/contracts/truffle.js b/packages/contracts/truffle.js deleted file mode 100644 index 630f4cf8b..000000000 --- a/packages/contracts/truffle.js +++ /dev/null @@ -1,17 +0,0 @@ -module.exports = { - networks: { - development: { - host: "localhost", - port: 8545, - network_id: "*", // Match any network id - }, - kovan: { - host: "localhost", - port: 8546, - network_id: "42", - gas: 4612388, - }, - }, - test_directory: "lib/test", - migrations_directory: "lib/migrations", -}; diff --git a/packages/contracts/tsconfig.json b/packages/contracts/tsconfig.json index 38008a542..0c1b3bc16 100644 --- a/packages/contracts/tsconfig.json +++ b/packages/contracts/tsconfig.json @@ -4,8 +4,6 @@ "outDir": "lib", "baseUrl": ".", "declaration": false, - "strictNullChecks": false, - "strictFunctionTypes": false, "allowJs": true }, "include": [ diff --git a/packages/contracts/util/artifacts.ts b/packages/contracts/util/artifacts.ts index ecb18cbce..145b1db3b 100644 --- a/packages/contracts/util/artifacts.ts +++ b/packages/contracts/util/artifacts.ts @@ -1,28 +1,27 @@ -export class Artifacts { - public Migrations: any; - public TokenTransferProxy: any; - public TokenRegistry: any; - public MultiSigWalletWithTimeLock: any; - public Exchange: any; - public ZRXToken: any; - public DummyToken: any; - public DummyTokenV2: any; - public EtherToken: any; - public MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress: any; - public MaliciousToken: any; - constructor(artifacts: any) { - this.Migrations = artifacts.require('Migrations'); - this.TokenTransferProxy = artifacts.require('TokenTransferProxy'); - this.TokenRegistry = artifacts.require('TokenRegistry'); - this.MultiSigWalletWithTimeLock = artifacts.require('MultiSigWalletWithTimeLock'); - this.Exchange = artifacts.require('Exchange'); - this.ZRXToken = artifacts.require('ZRXToken'); - this.DummyToken = artifacts.require('DummyToken'); - this.DummyTokenV2 = artifacts.require('DummyToken_v2'); - this.EtherToken = artifacts.require('WETH9'); - this.MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress = artifacts.require( - 'MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress', - ); - this.MaliciousToken = artifacts.require('MaliciousToken'); - } -} +import * as DummyTokenArtifact from '../build/artifacts/DummyToken.json'; +import * as DummyTokenV2Artifact from '../build/artifacts/DummyToken_v2.json'; +import * as ExchangeArtifact from '../build/artifacts/Exchange.json'; +import * as MaliciousTokenArtifact from '../build/artifacts/MaliciousToken.json'; +import * as MultiSigWalletWithTimeLockArtifact from '../build/artifacts/MultiSigWalletWithTimeLock.json'; +import * as MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddressArtifact from '../build/artifacts/MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress.json'; +import * as TokenArtifact from '../build/artifacts/Token.json'; +import * as TokenRegistryArtifact from '../build/artifacts/TokenRegistry.json'; +import * as TokenTransferProxyArtifact from '../build/artifacts/TokenTransferProxy.json'; +import * as EtherTokenArtifact from '../build/artifacts/WETH9.json'; +import * as ZRXArtifact from '../build/artifacts/ZRXToken.json'; + +import { Artifact } from './types'; + +export const artifacts = { + ZRXArtifact: (ZRXArtifact as any) as Artifact, + DummyTokenArtifact: (DummyTokenArtifact as any) as Artifact, + DummyTokenV2Artifact: (DummyTokenV2Artifact as any) as Artifact, + TokenArtifact: (TokenArtifact as any) as Artifact, + ExchangeArtifact: (ExchangeArtifact as any) as Artifact, + EtherTokenArtifact: (EtherTokenArtifact as any) as Artifact, + TokenRegistryArtifact: (TokenRegistryArtifact as any) as Artifact, + MaliciousTokenArtifact: (MaliciousTokenArtifact as any) as Artifact, + TokenTransferProxyArtifact: (TokenTransferProxyArtifact as any) as Artifact, + MultiSigWalletWithTimeLockArtifact: (MultiSigWalletWithTimeLockArtifact as any) as Artifact, + MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddressArtifact: (MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddressArtifact as any) as Artifact, +}; diff --git a/packages/contracts/util/balances.ts b/packages/contracts/util/balances.ts index 6a1659ab1..5bcb5145f 100644 --- a/packages/contracts/util/balances.ts +++ b/packages/contracts/util/balances.ts @@ -1,12 +1,13 @@ import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; +import * as Web3 from 'web3'; -import { BalancesByOwner, ContractInstance } from './types'; +import { BalancesByOwner } from './types'; export class Balances { - private _tokenContractInstances: ContractInstance[]; + private _tokenContractInstances: Web3.ContractInstance[]; private _ownerAddresses: string[]; - constructor(tokenContractInstances: ContractInstance[], ownerAddresses: string[]) { + constructor(tokenContractInstances: Web3.ContractInstance[], ownerAddresses: string[]) { this._tokenContractInstances = tokenContractInstances; this._ownerAddresses = ownerAddresses; } diff --git a/packages/contracts/util/exchange_wrapper.ts b/packages/contracts/util/exchange_wrapper.ts index ca79f92c4..7a07239f5 100644 --- a/packages/contracts/util/exchange_wrapper.ts +++ b/packages/contracts/util/exchange_wrapper.ts @@ -1,14 +1,17 @@ +import { TransactionReceiptWithDecodedLogs, ZeroEx } from '0x.js'; import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; +import * as Web3 from 'web3'; import { formatters } from './formatters'; import { Order } from './order'; -import { ContractInstance } from './types'; export class ExchangeWrapper { - private _exchange: ContractInstance; - constructor(exchangeContractInstance: ContractInstance) { + private _exchange: Web3.ContractInstance; + private _zeroEx: ZeroEx; + constructor(exchangeContractInstance: Web3.ContractInstance, zeroEx: ZeroEx) { this._exchange = exchangeContractInstance; + this._zeroEx = zeroEx; } public async fillOrderAsync( order: Order, @@ -17,10 +20,10 @@ export class ExchangeWrapper { fillTakerTokenAmount?: BigNumber; shouldThrowOnInsufficientBalanceOrAllowance?: boolean; } = {}, - ) { + ): Promise<TransactionReceiptWithDecodedLogs> { const shouldThrowOnInsufficientBalanceOrAllowance = !!opts.shouldThrowOnInsufficientBalanceOrAllowance; const params = order.createFill(shouldThrowOnInsufficientBalanceOrAllowance, opts.fillTakerTokenAmount); - const tx = await this._exchange.fillOrder( + const txHash = await this._exchange.fillOrder( params.orderAddresses, params.orderValues, params.fillTakerTokenAmount, @@ -30,24 +33,36 @@ export class ExchangeWrapper { params.s, { from }, ); + const tx = await this._zeroEx.awaitTransactionMinedAsync(txHash); + tx.logs = _.filter(tx.logs, log => log.address === this._exchange.address); _.each(tx.logs, log => wrapLogBigNumbers(log)); return tx; } - public async cancelOrderAsync(order: Order, from: string, opts: { cancelTakerTokenAmount?: BigNumber } = {}) { + public async cancelOrderAsync( + order: Order, + from: string, + opts: { cancelTakerTokenAmount?: BigNumber } = {}, + ): Promise<TransactionReceiptWithDecodedLogs> { const params = order.createCancel(opts.cancelTakerTokenAmount); - const tx = await this._exchange.cancelOrder( + const txHash = await this._exchange.cancelOrder( params.orderAddresses, params.orderValues, params.cancelTakerTokenAmount, { from }, ); + const tx = await this._zeroEx.awaitTransactionMinedAsync(txHash); + tx.logs = _.filter(tx.logs, log => log.address === this._exchange.address); _.each(tx.logs, log => wrapLogBigNumbers(log)); return tx; } - public async fillOrKillOrderAsync(order: Order, from: string, opts: { fillTakerTokenAmount?: BigNumber } = {}) { + public async fillOrKillOrderAsync( + order: Order, + from: string, + opts: { fillTakerTokenAmount?: BigNumber } = {}, + ): Promise<TransactionReceiptWithDecodedLogs> { const shouldThrowOnInsufficientBalanceOrAllowance = true; const params = order.createFill(shouldThrowOnInsufficientBalanceOrAllowance, opts.fillTakerTokenAmount); - const tx = await this._exchange.fillOrKillOrder( + const txHash = await this._exchange.fillOrKillOrder( params.orderAddresses, params.orderValues, params.fillTakerTokenAmount, @@ -56,6 +71,8 @@ export class ExchangeWrapper { params.s, { from }, ); + const tx = await this._zeroEx.awaitTransactionMinedAsync(txHash); + tx.logs = _.filter(tx.logs, log => log.address === this._exchange.address); _.each(tx.logs, log => wrapLogBigNumbers(log)); return tx; } @@ -66,14 +83,14 @@ export class ExchangeWrapper { fillTakerTokenAmounts?: BigNumber[]; shouldThrowOnInsufficientBalanceOrAllowance?: boolean; } = {}, - ) { + ): Promise<TransactionReceiptWithDecodedLogs> { const shouldThrowOnInsufficientBalanceOrAllowance = !!opts.shouldThrowOnInsufficientBalanceOrAllowance; const params = formatters.createBatchFill( orders, shouldThrowOnInsufficientBalanceOrAllowance, opts.fillTakerTokenAmounts, ); - const tx = await this._exchange.batchFillOrders( + const txHash = await this._exchange.batchFillOrders( params.orderAddresses, params.orderValues, params.fillTakerTokenAmounts, @@ -83,16 +100,23 @@ export class ExchangeWrapper { params.s, { from }, ); + const tx = await this._zeroEx.awaitTransactionMinedAsync(txHash); + tx.logs = _.filter(tx.logs, log => log.address === this._exchange.address); _.each(tx.logs, log => wrapLogBigNumbers(log)); return tx; } public async batchFillOrKillOrdersAsync( orders: Order[], from: string, - opts: { fillTakerTokenAmounts?: BigNumber[] } = {}, - ) { - const params = formatters.createBatchFill(orders, undefined, opts.fillTakerTokenAmounts); - const tx = await this._exchange.batchFillOrKillOrders( + opts: { fillTakerTokenAmounts?: BigNumber[]; shouldThrowOnInsufficientBalanceOrAllowance?: boolean } = {}, + ): Promise<TransactionReceiptWithDecodedLogs> { + const shouldThrowOnInsufficientBalanceOrAllowance = !!opts.shouldThrowOnInsufficientBalanceOrAllowance; + const params = formatters.createBatchFill( + orders, + shouldThrowOnInsufficientBalanceOrAllowance, + opts.fillTakerTokenAmounts, + ); + const txHash = await this._exchange.batchFillOrKillOrders( params.orderAddresses, params.orderValues, params.fillTakerTokenAmounts, @@ -101,24 +125,23 @@ export class ExchangeWrapper { params.s, { from }, ); + const tx = await this._zeroEx.awaitTransactionMinedAsync(txHash); + tx.logs = _.filter(tx.logs, log => log.address === this._exchange.address); _.each(tx.logs, log => wrapLogBigNumbers(log)); return tx; } public async fillOrdersUpToAsync( orders: Order[], from: string, - opts: { - fillTakerTokenAmount?: BigNumber; - shouldThrowOnInsufficientBalanceOrAllowance?: boolean; - } = {}, - ) { + opts: { fillTakerTokenAmount: BigNumber; shouldThrowOnInsufficientBalanceOrAllowance?: boolean }, + ): Promise<TransactionReceiptWithDecodedLogs> { const shouldThrowOnInsufficientBalanceOrAllowance = !!opts.shouldThrowOnInsufficientBalanceOrAllowance; const params = formatters.createFillUpTo( orders, shouldThrowOnInsufficientBalanceOrAllowance, opts.fillTakerTokenAmount, ); - const tx = await this._exchange.fillOrdersUpTo( + const txHash = await this._exchange.fillOrdersUpTo( params.orderAddresses, params.orderValues, params.fillTakerTokenAmount, @@ -128,6 +151,8 @@ export class ExchangeWrapper { params.s, { from }, ); + const tx = await this._zeroEx.awaitTransactionMinedAsync(txHash); + tx.logs = _.filter(tx.logs, log => log.address === this._exchange.address); _.each(tx.logs, log => wrapLogBigNumbers(log)); return tx; } @@ -135,14 +160,16 @@ export class ExchangeWrapper { orders: Order[], from: string, opts: { cancelTakerTokenAmounts?: BigNumber[] } = {}, - ) { + ): Promise<TransactionReceiptWithDecodedLogs> { const params = formatters.createBatchCancel(orders, opts.cancelTakerTokenAmounts); - const tx = await this._exchange.batchCancelOrders( + const txHash = await this._exchange.batchCancelOrders( params.orderAddresses, params.orderValues, params.cancelTakerTokenAmounts, { from }, ); + const tx = await this._zeroEx.awaitTransactionMinedAsync(txHash); + tx.logs = _.filter(tx.logs, log => log.address === this._exchange.address); _.each(tx.logs, log => wrapLogBigNumbers(log)); return tx; } diff --git a/packages/contracts/util/formatters.ts b/packages/contracts/util/formatters.ts index 0d0ef6df4..e16fe8d45 100644 --- a/packages/contracts/util/formatters.ts +++ b/packages/contracts/util/formatters.ts @@ -35,9 +35,9 @@ export const formatters = { order.params.expirationTimestampInSec, order.params.salt, ]); - batchFill.v.push(order.params.v); - batchFill.r.push(order.params.r); - batchFill.s.push(order.params.s); + batchFill.v.push(order.params.v as number); + batchFill.r.push(order.params.r as string); + batchFill.s.push(order.params.s as string); if (fillTakerTokenAmounts.length < orders.length) { batchFill.fillTakerTokenAmounts.push(order.params.takerTokenAmount); } @@ -74,9 +74,9 @@ export const formatters = { order.params.expirationTimestampInSec, order.params.salt, ]); - fillUpTo.v.push(order.params.v); - fillUpTo.r.push(order.params.r); - fillUpTo.s.push(order.params.s); + fillUpTo.v.push(order.params.v as number); + fillUpTo.r.push(order.params.r as string); + fillUpTo.s.push(order.params.s as string); }); return fillUpTo; }, diff --git a/packages/contracts/util/multi_sig_wrapper.ts b/packages/contracts/util/multi_sig_wrapper.ts index 0e2e671ec..0a066df53 100644 --- a/packages/contracts/util/multi_sig_wrapper.ts +++ b/packages/contracts/util/multi_sig_wrapper.ts @@ -3,10 +3,10 @@ import ethUtil = require('ethereumjs-util'); import * as _ from 'lodash'; import * as Web3 from 'web3'; -import { ContractInstance, TransactionDataParams } from './types'; +import { TransactionDataParams } from './types'; export class MultiSigWrapper { - private _multiSig: ContractInstance; + private _multiSig: Web3.ContractInstance; public static encodeFnArgs(name: string, abi: Web3.AbiDefinition[], args: any[]) { const abiEntity = _.find(abi, { name }) as Web3.MethodAbi; if (_.isUndefined(abiEntity)) { @@ -21,7 +21,7 @@ export class MultiSigWrapper { }); return funcSig + argsData.join(''); } - constructor(multiSigContractInstance: ContractInstance) { + constructor(multiSigContractInstance: Web3.ContractInstance) { this._multiSig = multiSigContractInstance; } public async submitTransactionAsync( diff --git a/packages/contracts/util/order.ts b/packages/contracts/util/order.ts index e202d485b..57bb2bcbf 100644 --- a/packages/contracts/util/order.ts +++ b/packages/contracts/util/order.ts @@ -1,19 +1,17 @@ -import { BigNumber, promisify } from '@0xproject/utils'; +import { BigNumber } from '@0xproject/utils'; +import { Web3Wrapper } from '@0xproject/web3-wrapper'; import ethUtil = require('ethereumjs-util'); import * as _ from 'lodash'; -import Web3 = require('web3'); import { crypto } from './crypto'; import { OrderParams } from './types'; -// In order to benefit from type-safety, we re-assign the global web3 instance injected by Truffle -// with type `any` to a variable of type `Web3`. -const web3: Web3 = (global as any).web3; - export class Order { public params: OrderParams; - constructor(params: OrderParams) { + private _web3Wrapper: Web3Wrapper; + constructor(web3Wrapper: Web3Wrapper, params: OrderParams) { this.params = params; + this._web3Wrapper = web3Wrapper; } public isValidSignature() { const { v, r, s } = this.params; @@ -32,7 +30,7 @@ export class Order { } public async signAsync() { const orderHash = this._getOrderHash(); - const signature = await promisify<string>(web3.eth.sign)(this.params.maker, orderHash); + const signature = await this._web3Wrapper.signTransactionAsync(this.params.maker, orderHash); const { v, r, s } = ethUtil.fromRpcSig(signature); this.params = _.assign(this.params, { orderHashHex: orderHash, diff --git a/packages/contracts/util/order_factory.ts b/packages/contracts/util/order_factory.ts index a45877de0..2b50f13e8 100644 --- a/packages/contracts/util/order_factory.ts +++ b/packages/contracts/util/order_factory.ts @@ -1,5 +1,6 @@ import { ZeroEx } from '0x.js'; import { BigNumber } from '@0xproject/utils'; +import { Web3Wrapper } from '@0xproject/web3-wrapper'; import * as _ from 'lodash'; import { Order } from './order'; @@ -7,10 +8,12 @@ import { DefaultOrderParams, OptionalOrderParams, OrderParams } from './types'; export class OrderFactory { private _defaultOrderParams: DefaultOrderParams; - constructor(defaultOrderParams: DefaultOrderParams) { + private _web3Wrapper: Web3Wrapper; + constructor(web3Wrapper: Web3Wrapper, defaultOrderParams: DefaultOrderParams) { this._defaultOrderParams = defaultOrderParams; + this._web3Wrapper = web3Wrapper; } - public async newSignedOrderAsync(customOrderParams: OptionalOrderParams = {}) { + public async newSignedOrderAsync(customOrderParams: OptionalOrderParams = {}): Promise<Order> { const randomExpiration = new BigNumber(Math.floor((Date.now() + Math.random() * 100000000000) / 1000)); const orderParams: OrderParams = _.assign( {}, @@ -22,7 +25,7 @@ export class OrderFactory { this._defaultOrderParams, customOrderParams, ); - const order = new Order(orderParams); + const order = new Order(this._web3Wrapper, orderParams); await order.signAsync(); return order; } diff --git a/packages/contracts/util/token_registry_wrapper.ts b/packages/contracts/util/token_registry_wrapper.ts index 07a577dea..033b72d10 100644 --- a/packages/contracts/util/token_registry_wrapper.ts +++ b/packages/contracts/util/token_registry_wrapper.ts @@ -1,8 +1,10 @@ -import { ContractInstance, Token } from './types'; +import * as Web3 from 'web3'; + +import { Token } from './types'; export class TokenRegWrapper { - private _tokenReg: ContractInstance; - constructor(tokenRegContractInstance: ContractInstance) { + private _tokenReg: Web3.ContractInstance; + constructor(tokenRegContractInstance: Web3.ContractInstance) { this._tokenReg = tokenRegContractInstance; } public addTokenAsync(token: Token, from: string) { diff --git a/packages/contracts/util/types.ts b/packages/contracts/util/types.ts index e511ca9f4..0db04cd76 100644 --- a/packages/contracts/util/types.ts +++ b/packages/contracts/util/types.ts @@ -7,6 +7,10 @@ export interface BalancesByOwner { }; } +export interface SubmissionContractEventArgs { + transactionId: BigNumber; +} + export interface BatchFillOrders { orderAddresses: string[][]; orderValues: BigNumber[][]; @@ -108,12 +112,38 @@ export interface TokenInfoByNetwork { live: Token[]; } -// Named type aliases to improve readability -export type ContractInstance = any; - export enum ExchangeContractErrs { ERROR_ORDER_EXPIRED, ERROR_ORDER_FULLY_FILLED_OR_CANCELLED, ERROR_ROUNDING_ERROR_TOO_LARGE, ERROR_INSUFFICIENT_BALANCE_OR_ALLOWANCE, } + +export enum ContractName { + TokenTransferProxy = 'TokenTransferProxy', + TokenRegistry = 'TokenRegistry', + MultiSigWalletWithTimeLock = 'MultiSigWalletWithTimeLock', + Exchange = 'Exchange', + ZRXToken = 'ZRXToken', + DummyToken = 'DummyToken', + DummyToken_v2 = 'DummyToken_v2', + EtherToken = 'WETH9', + MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress = 'MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress', + MaliciousToken = 'MaliciousToken', +} + +export interface Artifact { + contract_name: ContractName; + networks: { + [networkId: number]: { + abi: Web3.ContractAbi; + solc_version: string; + keccak256: string; + optimizer_enabled: number; + unlinked_binary: string; + updated_at: number; + address: string; + constructor_args: string; + }; + }; +} diff --git a/packages/deployer/package.json b/packages/deployer/package.json index 1ae0ca227..8bff8445c 100644 --- a/packages/deployer/package.json +++ b/packages/deployer/package.json @@ -1,8 +1,9 @@ { "name": "@0xproject/deployer", - "version": "0.0.2", + "version": "0.0.4", "description": "Smart contract deployer of 0x protocol", - "main": "lib/src/cli.js", + "main": "lib/src/index.js", + "types": "lib/src/index.d.ts", "scripts": { "build": "yarn clean && copyfiles 'test/fixtures/contracts/**/*' src/solc/solc_bin/* ./lib && tsc", "test": "npm run build; mocha lib/test/*_test.js", @@ -27,13 +28,14 @@ "homepage": "https://github.com/0xProject/0x.js/packages/deployer/README.md", "devDependencies": { "copyfiles": "^1.2.0", + "web3-typescript-typings": "^0.9.4", "types-bn": "^0.0.1", "typescript": "~2.6.1", - "web3-typescript-typings": "^0.9.5" + "web3-typescript-typings": "^0.9.7" }, "dependencies": { - "@0xproject/utils": "^0.1.3", - "@0xproject/web3-wrapper": "^0.1.6", + "@0xproject/utils": "^0.2.1", + "@0xproject/web3-wrapper": "^0.1.8", "lodash": "^4.17.4", "solc": "^0.4.18", "web3": "^0.20.0", diff --git a/packages/deployer/src/index.ts b/packages/deployer/src/index.ts new file mode 100644 index 000000000..06dc9e596 --- /dev/null +++ b/packages/deployer/src/index.ts @@ -0,0 +1 @@ +export { Deployer } from './deployer'; diff --git a/packages/deployer/src/utils/contract.ts b/packages/deployer/src/utils/contract.ts index 546e82dfb..9c57751ff 100644 --- a/packages/deployer/src/utils/contract.ts +++ b/packages/deployer/src/utils/contract.ts @@ -28,16 +28,16 @@ export class Contract implements Web3.ContractInstance { _.forEach(functionsAbi, (functionAbi: Web3.MethodAbi) => { if (functionAbi.constant) { const cbStyleCallFunction = this._contract[functionAbi.name].call; - this[functionAbi.name] = { - callAsync: promisify(cbStyleCallFunction, this._contract), - }; + this[functionAbi.name] = promisify(cbStyleCallFunction, this._contract); + this[functionAbi.name].call = promisify(cbStyleCallFunction, this._contract); } else { const cbStyleFunction = this._contract[functionAbi.name]; + const cbStyleCallFunction = this._contract[functionAbi.name].call; const cbStyleEstimateGasFunction = this._contract[functionAbi.name].estimateGas; - this[functionAbi.name] = { - estimateGasAsync: promisify(cbStyleEstimateGasFunction, this._contract), - sendTransactionAsync: this._promisifyWithDefaultParams(cbStyleFunction), - }; + this[functionAbi.name] = this._promisifyWithDefaultParams(cbStyleFunction); + this[functionAbi.name].estimateGasAsync = promisify(cbStyleEstimateGasFunction); + this[functionAbi.name].sendTransactionAsync = this._promisifyWithDefaultParams(cbStyleFunction); + this[functionAbi.name].call = promisify(cbStyleCallFunction, this._contract); } }); } diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json index 1cd54f15f..ec0872a46 100644 --- a/packages/dev-utils/package.json +++ b/packages/dev-utils/package.json @@ -1,6 +1,6 @@ { "name": "@0xproject/dev-utils", - "version": "0.0.6", + "version": "0.0.8", "description": "0x dev TS utils", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -19,19 +19,22 @@ }, "homepage": "https://github.com/0xProject/0x.js/packages/dev-utils/README.md", "devDependencies": { - "@0xproject/tslint-config": "^0.4.3", + "@0xproject/tslint-config": "^0.4.5", "@types/lodash": "^4.14.86", + "@0xproject/types": "^0.1.5", "npm-run-all": "^4.1.2", "shx": "^0.2.2", "tslint": "5.8.0", "types-bn": "^0.0.1", - "types-ethereumjs-util": "0xProject/types-ethereumjs-util", + "types-ethereumjs-util": "0xproject/types-ethereumjs-util", "typescript": "~2.6.1" }, "dependencies": { - "@0xproject/utils": "^0.2.1", + "@0xproject/utils": "^0.2.3", "ethereumjs-util": "^5.1.2", "lodash": "^4.17.4", - "request-promise-native": "^1.0.5" + "request-promise-native": "^1.0.5", + "web3": "^0.20.0", + "web3-provider-engine": "^13.0.1" } } diff --git a/packages/dev-utils/src/blockchain_lifecycle.ts b/packages/dev-utils/src/blockchain_lifecycle.ts index 18f5d5c61..c46902f76 100644 --- a/packages/dev-utils/src/blockchain_lifecycle.ts +++ b/packages/dev-utils/src/blockchain_lifecycle.ts @@ -3,8 +3,8 @@ import { RPC } from './rpc'; export class BlockchainLifecycle { private _rpc: RPC; private _snapshotIdsStack: number[]; - constructor(url: string) { - this._rpc = new RPC(url); + constructor() { + this._rpc = new RPC(); this._snapshotIdsStack = []; } // TODO: In order to run these tests on an actual node, we should check if we are running against diff --git a/packages/dev-utils/src/constants.ts b/packages/dev-utils/src/constants.ts new file mode 100644 index 000000000..4f7b4202a --- /dev/null +++ b/packages/dev-utils/src/constants.ts @@ -0,0 +1,5 @@ +export const constants = { + RPC_URL: 'http://localhost:8545', + RPC_PORT: 8545, + GAS_ESTIMATE: 1000000, +}; diff --git a/packages/dev-utils/src/globals.d.ts b/packages/dev-utils/src/globals.d.ts new file mode 100644 index 000000000..7b132ee28 --- /dev/null +++ b/packages/dev-utils/src/globals.d.ts @@ -0,0 +1,2 @@ +declare module 'web3-provider-engine'; +declare module 'web3-provider-engine/subproviders/rpc'; diff --git a/packages/dev-utils/src/index.ts b/packages/dev-utils/src/index.ts index 9ba0cb5cf..e899ac206 100644 --- a/packages/dev-utils/src/index.ts +++ b/packages/dev-utils/src/index.ts @@ -1,2 +1,4 @@ export { RPC } from './rpc'; export { BlockchainLifecycle } from './blockchain_lifecycle'; +export { web3Factory } from './web3_factory'; +export { constants as devConstants } from './constants'; diff --git a/packages/dev-utils/src/rpc.ts b/packages/dev-utils/src/rpc.ts index 36f8b1ef9..47a359263 100644 --- a/packages/dev-utils/src/rpc.ts +++ b/packages/dev-utils/src/rpc.ts @@ -1,11 +1,13 @@ import * as ethUtil from 'ethereumjs-util'; import * as request from 'request-promise-native'; +import { constants } from './constants'; + export class RPC { private _url: string; private _id: number; - constructor(url: string) { - this._url = url; + constructor() { + this._url = constants.RPC_URL; this._id = 0; } public async takeSnapshotAsync(): Promise<number> { diff --git a/packages/0x.js/test/utils/subproviders/empty_wallet_subprovider.ts b/packages/dev-utils/src/subproviders/empty_wallet_subprovider.ts index 53f2be83d..8c1fdfdb2 100644 --- a/packages/0x.js/test/utils/subproviders/empty_wallet_subprovider.ts +++ b/packages/dev-utils/src/subproviders/empty_wallet_subprovider.ts @@ -1,4 +1,4 @@ -import { JSONRPCPayload } from '../../../src/types'; +import { JSONRPCPayload } from '@0xproject/types'; /* * This class implements the web3-provider-engine subprovider interface and returns diff --git a/packages/0x.js/test/utils/subproviders/fake_gas_estimate_subprovider.ts b/packages/dev-utils/src/subproviders/fake_gas_estimate_subprovider.ts index e1113a851..b455a0ed7 100644 --- a/packages/0x.js/test/utils/subproviders/fake_gas_estimate_subprovider.ts +++ b/packages/dev-utils/src/subproviders/fake_gas_estimate_subprovider.ts @@ -1,4 +1,4 @@ -import { JSONRPCPayload } from '../../../src/types'; +import { JSONRPCPayload } from '@0xproject/types'; /* * This class implements the web3-provider-engine subprovider interface and returns diff --git a/packages/0x.js/test/utils/web3_factory.ts b/packages/dev-utils/src/web3_factory.ts index 26c26e03d..26c26e03d 100644 --- a/packages/0x.js/test/utils/web3_factory.ts +++ b/packages/dev-utils/src/web3_factory.ts diff --git a/packages/dev-utils/tsconfig.json b/packages/dev-utils/tsconfig.json index b28e45170..bdf315d59 100644 --- a/packages/dev-utils/tsconfig.json +++ b/packages/dev-utils/tsconfig.json @@ -6,6 +6,7 @@ "include": [ "./src/**/*", "../../node_modules/types-bn/index.d.ts", + "../../node_modules/web3-typescript-typings/index.d.ts", "../../node_modules/types-ethereumjs-util/index.d.ts" ] } diff --git a/packages/json-schemas/package.json b/packages/json-schemas/package.json index 4b500eab3..4deba09bc 100644 --- a/packages/json-schemas/package.json +++ b/packages/json-schemas/package.json @@ -1,6 +1,6 @@ { "name": "@0xproject/json-schemas", - "version": "0.7.4", + "version": "0.7.6", "description": "0x-related json schemas", "main": "lib/src/index.js", "types": "lib/src/index.d.ts", @@ -27,8 +27,8 @@ "lodash.values": "^4.3.0" }, "devDependencies": { - "@0xproject/tslint-config": "^0.4.3", - "@0xproject/utils": "^0.2.1", + "@0xproject/tslint-config": "^0.4.5", + "@0xproject/utils": "^0.2.3", "@types/lodash.foreach": "^4.5.3", "@types/lodash.values": "^4.3.3", "@types/mocha": "^2.2.42", diff --git a/packages/json-schemas/tsconfig.json b/packages/json-schemas/tsconfig.json index 88a467ccb..10354fa33 100644 --- a/packages/json-schemas/tsconfig.json +++ b/packages/json-schemas/tsconfig.json @@ -3,5 +3,10 @@ "compilerOptions": { "outDir": "lib" }, - "include": ["./src/**/*", "./test/**/*", "../../node_modules/chai-typescript-typings/index.d.ts"] + "include": [ + "./src/**/*", + "./test/**/*", + "../../node_modules/web3-typescript-typings/index.d.ts", + "../../node_modules/chai-typescript-typings/index.d.ts" + ] } diff --git a/packages/monorepo-scripts/package.json b/packages/monorepo-scripts/package.json index 57d0d6f5f..1e177836a 100644 --- a/packages/monorepo-scripts/package.json +++ b/packages/monorepo-scripts/package.json @@ -1,6 +1,6 @@ { "name": "@0xproject/monorepo-scripts", - "version": "0.1.5", + "version": "0.1.7", "private": true, "description": "Helper scripts for the monorepo", "scripts": { @@ -19,7 +19,7 @@ }, "homepage": "https://github.com/0xProject/0x.js/packages/monorepo-scripts/README.md", "devDependencies": { - "@0xproject/tslint-config": "^0.4.3", + "@0xproject/tslint-config": "^0.4.5", "@types/glob": "^5.0.33", "@types/node": "^8.0.53", "shx": "^0.2.2", diff --git a/packages/subproviders/package.json b/packages/subproviders/package.json index 2735f8280..9381689b1 100644 --- a/packages/subproviders/package.json +++ b/packages/subproviders/package.json @@ -1,6 +1,6 @@ { "name": "@0xproject/subproviders", - "version": "0.3.2", + "version": "0.3.4", "main": "lib/src/index.js", "types": "lib/src/index.d.ts", "license": "Apache-2.0", @@ -17,8 +17,8 @@ "test:integration": "run-s clean build run_mocha_integration" }, "dependencies": { - "@0xproject/assert": "^0.0.12", - "@0xproject/utils": "^0.2.1", + "@0xproject/assert": "^0.0.14", + "@0xproject/utils": "^0.2.3", "bn.js": "^4.11.8", "es6-promisify": "^5.0.0", "ethereumjs-tx": "^1.3.3", @@ -31,15 +31,15 @@ "web3-provider-engine": "^13.0.1" }, "devDependencies": { - "@0xproject/tslint-config": "^0.4.3", - "@0xproject/utils": "^0.2.1", + "@0xproject/tslint-config": "^0.4.5", + "@0xproject/utils": "^0.2.3", "@types/lodash": "^4.14.86", "@types/mocha": "^2.2.42", "@types/node": "^8.0.53", "awesome-typescript-loader": "^3.1.3", "chai": "^4.0.1", "chai-as-promised": "^7.1.0", - "chai-as-promised-typescript-typings": "^0.0.5", + "chai-as-promised-typescript-typings": "^0.0.7", "chai-typescript-typings": "^0.0.2", "dirty-chai": "^2.0.1", "mocha": "^4.0.1", @@ -49,7 +49,7 @@ "types-bn": "^0.0.1", "types-ethereumjs-util": "0xproject/types-ethereumjs-util", "typescript": "~2.6.1", - "web3-typescript-typings": "^0.7.2", + "web3-typescript-typings": "^0.9.5", "webpack": "^3.1.0" } } diff --git a/packages/testnet-faucets/package.json b/packages/testnet-faucets/package.json index bb0484a04..3b42ebb3e 100644 --- a/packages/testnet-faucets/package.json +++ b/packages/testnet-faucets/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "@0xproject/testnet-faucets", - "version": "1.0.6", + "version": "1.0.8", "description": "A faucet micro-service that dispenses test ERC20 tokens or Ether", "main": "server.js", "scripts": { @@ -14,8 +14,8 @@ "author": "Fabio Berger", "license": "Apache-2.0", "dependencies": { - "0x.js": "^0.30.1", - "@0xproject/utils": "^0.2.1", + "0x.js": "^0.31.0", + "@0xproject/utils": "^0.2.3", "body-parser": "^1.17.1", "ethereumjs-tx": "^1.3.3", "express": "^4.15.2", @@ -25,7 +25,7 @@ "web3-provider-engine": "^13.0.1" }, "devDependencies": { - "@0xproject/tslint-config": "^0.4.3", + "@0xproject/tslint-config": "^0.4.5", "@types/body-parser": "^1.16.1", "@types/express": "^4.0.35", "@types/lodash": "^4.14.86", @@ -36,7 +36,7 @@ "source-map-loader": "^0.1.6", "tslint": "5.8.0", "typescript": "~2.6.1", - "web3-typescript-typings": "^0.9.5", + "web3-typescript-typings": "^0.9.7", "webpack": "^3.1.0", "webpack-node-externals": "^1.6.0" } diff --git a/packages/tslint-config/package.json b/packages/tslint-config/package.json index cdee9e279..f4497ff5d 100644 --- a/packages/tslint-config/package.json +++ b/packages/tslint-config/package.json @@ -1,6 +1,6 @@ { "name": "@0xproject/tslint-config", - "version": "0.4.3", + "version": "0.4.5", "description": "Lint rules related to 0xProject for TSLint", "main": "tslint.json", "scripts": { diff --git a/packages/tslint-config/tslint.json b/packages/tslint-config/tslint.json index 971588b08..44a8517dc 100644 --- a/packages/tslint-config/tslint.json +++ b/packages/tslint-config/tslint.json @@ -44,7 +44,6 @@ "no-string-throw": true, "no-submodule-imports": false, "no-unnecessary-type-assertion": true, - "no-unused-variable": [true, { "ignore-pattern": "^_\\d*" }], "no-implicit-dependencies": [true, "dev"], "number-literal-format": true, "object-literal-sort-keys": false, diff --git a/packages/types/package.json b/packages/types/package.json index 8a8244950..1abcf79ad 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,6 +1,6 @@ { "name": "@0xproject/types", - "version": "0.1.5", + "version": "0.1.7", "description": "0x types", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -19,13 +19,14 @@ }, "homepage": "https://github.com/0xProject/0x.js/packages/types/README.md", "devDependencies": { - "@0xproject/tslint-config": "^0.4.3", + "@0xproject/tslint-config": "^0.4.5", "shx": "^0.2.2", "tslint": "5.8.0", + "web3-typescript-typings": "^0.9.5", "typescript": "~2.6.1" }, "dependencies": { - "@0xproject/utils": "^0.2.1", + "bignumber.js": "~4.1.0", "web3": "^0.20.0" } } diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 9cf9bc7af..7b53b52c4 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -1,4 +1,4 @@ -import { BigNumber } from '@0xproject/utils'; +import { BigNumber } from 'bignumber.js'; import * as Web3 from 'web3'; export interface TxData { @@ -25,3 +25,34 @@ export interface TransactionReceipt { contractAddress: string | null; logs: Web3.LogEntry[]; } + +export interface JSONRPCPayload { + params: any[]; + method: string; +} + +export enum AbiType { + Function = 'function', + Constructor = 'constructor', + Event = 'event', + Fallback = 'fallback', +} + +export type ContractEventArg = string | BigNumber; + +export interface DecodedLogArgs { + [argName: string]: ContractEventArg; +} + +export interface LogWithDecodedArgs<ArgsType> extends Web3.DecodedLogEntry<ArgsType> {} +export type RawLog = Web3.LogEntry; +export enum SolidityTypes { + Address = 'address', + Uint256 = 'uint256', + Uint8 = 'uint8', + Uint = 'uint', +} + +export interface TransactionReceiptWithDecodedLogs extends TransactionReceipt { + logs: Array<LogWithDecodedArgs<DecodedLogArgs> | Web3.LogEntry>; +} diff --git a/packages/utils/package.json b/packages/utils/package.json index b1f8b3792..db82f8040 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -1,6 +1,6 @@ { "name": "@0xproject/utils", - "version": "0.2.1", + "version": "0.2.3", "description": "0x TS utils", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -19,16 +19,19 @@ }, "homepage": "https://github.com/0xProject/0x.js/packages/utils/README.md", "devDependencies": { - "@0xproject/tslint-config": "^0.4.3", + "@0xproject/tslint-config": "^0.4.5", + "@0xproject/types": "^0.1.5", "@types/lodash": "^4.14.86", "npm-run-all": "^4.1.2", "shx": "^0.2.2", "tslint": "5.8.0", + "web3-typescript-typings": "^0.9.5", "typescript": "~2.6.1" }, "dependencies": { "bignumber.js": "~4.1.0", "js-sha3": "^0.7.0", - "lodash": "^4.17.4" + "lodash": "^4.17.4", + "web3": "^0.20.0" } } diff --git a/packages/0x.js/src/utils/abi_decoder.ts b/packages/utils/src/abi_decoder.ts index bbd2a0b1d..f96ee2edb 100644 --- a/packages/0x.js/src/utils/abi_decoder.ts +++ b/packages/utils/src/abi_decoder.ts @@ -1,9 +1,9 @@ -import { BigNumber } from '@0xproject/utils'; +import { AbiType, DecodedLogArgs, LogWithDecodedArgs, RawLog, SolidityTypes } from '@0xproject/types'; import * as _ from 'lodash'; import * as Web3 from 'web3'; import * as SolidityCoder from 'web3/lib/solidity/coder'; -import { AbiType, ContractEventArgs, DecodedLogArgs, LogWithDecodedArgs, RawLog, SolidityTypes } from '../types'; +import { BigNumber } from './configured_bignumber'; export class AbiDecoder { private _savedABIs: Web3.AbiDefinition[] = []; @@ -21,9 +21,7 @@ export class AbiDecoder { _.map(abiArrays, this._addABI.bind(this)); } // This method can only decode logs from the 0x & ERC20 smart contracts - public tryToDecodeLogOrNoop<ArgsType extends ContractEventArgs>( - log: Web3.LogEntry, - ): LogWithDecodedArgs<ArgsType> | RawLog { + public tryToDecodeLogOrNoop<ArgsType>(log: Web3.LogEntry): LogWithDecodedArgs<ArgsType> | RawLog { const methodId = log.topics[0]; const event = this._methodIds[methodId]; if (_.isUndefined(event)) { @@ -40,7 +38,7 @@ export class AbiDecoder { _.map(event.inputs, (param: Web3.EventParameter) => { // Indexed parameters are stored in topics. Non-indexed ones in decodedData - let value = param.indexed ? log.topics[topicsIndex++] : decodedData[dataIndex++]; + let value: BigNumber | string = param.indexed ? log.topics[topicsIndex++] : decodedData[dataIndex++]; if (param.type === SolidityTypes.Address) { value = AbiDecoder._padZeros(new BigNumber(value).toString(16)); } else if ( diff --git a/packages/utils/src/globals.d.ts b/packages/utils/src/globals.d.ts new file mode 100644 index 000000000..ade9e59db --- /dev/null +++ b/packages/utils/src/globals.d.ts @@ -0,0 +1,3 @@ +declare module 'web3/lib/solidity/coder' { + const decodeParams: (types: string[], data: string) => any[]; +} diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index 2768e49ab..39dede41f 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -3,3 +3,4 @@ export { addressUtils } from './address_utils'; export { classUtils } from './class_utils'; export { intervalUtils } from './interval_utils'; export { BigNumber } from './configured_bignumber'; +export { AbiDecoder } from './abi_decoder'; diff --git a/packages/web3-typescript-typings/CHANGELOG.md b/packages/web3-typescript-typings/CHANGELOG.md index 46f70d864..56bb4ed48 100644 --- a/packages/web3-typescript-typings/CHANGELOG.md +++ b/packages/web3-typescript-typings/CHANGELOG.md @@ -2,5 +2,6 @@ ## v0.9.3 - _January 11, 2018_ +* Fix `getTransactionReceipt` not returning null (#338) * Add type for getData on a contract * Fixed the `defaultAccount` not allowing for `undefined` value (#320) diff --git a/packages/web3-typescript-typings/index.d.ts b/packages/web3-typescript-typings/index.d.ts index ff379cb3d..cd34759ed 100644 --- a/packages/web3-typescript-typings/index.d.ts +++ b/packages/web3-typescript-typings/index.d.ts @@ -257,10 +257,10 @@ declare module 'web3' { sign(address: string, data: string): string; sign(address: string, data: string, callback: (err: Error, signature: string) => void): void; - getTransactionReceipt(txHash: string): Web3.TransactionReceipt; + getTransactionReceipt(txHash: string): Web3.TransactionReceipt | null; getTransactionReceipt( txHash: string, - callback: (err: Error, receipt: Web3.TransactionReceipt) => void, + callback: (err: Error, receipt: Web3.TransactionReceipt | null) => void, ): void; // TODO block param diff --git a/packages/web3-typescript-typings/package.json b/packages/web3-typescript-typings/package.json index 6f7dd0931..4737088b0 100644 --- a/packages/web3-typescript-typings/package.json +++ b/packages/web3-typescript-typings/package.json @@ -1,6 +1,6 @@ { "name": "web3-typescript-typings", - "version": "0.9.5", + "version": "0.9.7", "description": "Typescript type definitions for web3", "main": "index.d.ts", "types": "index.d.ts", @@ -20,7 +20,7 @@ "homepage": "https://github.com/0xProject/0x.js/packages/web3-typescript-typings#readme", "devDependencies": { "@types/bignumber.js": "^4.0.2", - "tslint": "^5.5.0", + "tslint": "5.8.0", "tslint-config-0xproject": "^0.0.2", "typescript": "~2.6.1" }, diff --git a/packages/web3-wrapper/package.json b/packages/web3-wrapper/package.json index a0e15b48d..ec9ff3bb4 100644 --- a/packages/web3-wrapper/package.json +++ b/packages/web3-wrapper/package.json @@ -1,6 +1,6 @@ { "name": "@0xproject/web3-wrapper", - "version": "0.1.6", + "version": "0.1.8", "description": "Wraps around web3 and gives a nicer interface", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -19,17 +19,17 @@ }, "homepage": "https://github.com/0xProject/0x.js/packages/web3-wrapper/README.md", "devDependencies": { - "@0xproject/tslint-config": "^0.4.3", - "@0xproject/types": "^0.1.5", + "@0xproject/tslint-config": "^0.4.5", + "@0xproject/types": "^0.1.7", "@types/lodash": "^4.14.86", "npm-run-all": "^4.1.2", "shx": "^0.2.2", "tslint": "5.8.0", "typescript": "~2.6.1", - "web3-typescript-typings": "^0.9.5" + "web3-typescript-typings": "^0.9.7" }, "dependencies": { - "@0xproject/utils": "^0.2.1", + "@0xproject/utils": "^0.2.3", "lodash": "^4.17.4", "web3": "^0.20.0" } diff --git a/packages/web3-wrapper/src/index.ts b/packages/web3-wrapper/src/index.ts index c4826f2be..a2878fc2a 100644 --- a/packages/web3-wrapper/src/index.ts +++ b/packages/web3-wrapper/src/index.ts @@ -134,6 +134,10 @@ export class Web3Wrapper { const gas = await promisify<number>(this._web3.eth.estimateGas)({ data }); return gas; } + public async sendTransactionAsync(txData: Web3.TxData): Promise<string> { + const txHash = await promisify<string>(this._web3.eth.sendTransaction)(txData); + return txHash; + } private async _sendRawPayloadAsync<A>(payload: Web3.JSONRPCRequestPayload): Promise<A> { const sendAsync = this._web3.currentProvider.sendAsync.bind(this._web3.currentProvider); const response = await promisify<Web3.JSONRPCResponsePayload>(sendAsync)(payload); diff --git a/packages/website/package.json b/packages/website/package.json index 568bca47f..3d6cc24f7 100644 --- a/packages/website/package.json +++ b/packages/website/package.json @@ -1,6 +1,6 @@ { "name": "@0xproject/website", - "version": "0.0.8", + "version": "0.0.10", "private": true, "description": "Website and 0x portal dapp", "scripts": { @@ -21,9 +21,9 @@ "author": "Fabio Berger", "license": "Apache-2.0", "dependencies": { - "0x.js": "^0.30.1", - "@0xproject/subproviders": "^0.3.2", - "@0xproject/utils": "^0.2.1", + "0x.js": "^0.31.0", + "@0xproject/subproviders": "^0.3.4", + "@0xproject/utils": "^0.2.3", "accounting": "^0.4.1", "basscss": "^8.0.3", "blockies": "^0.0.2", @@ -36,7 +36,6 @@ "find-versions": "^2.0.0", "is-mobile": "^0.2.2", "jsonschema": "^1.2.0", - "ledgerco": "0xProject/ledger-node-js-api", "less": "^2.7.2", "lodash": "^4.17.4", "material-ui": "^0.17.1", @@ -101,7 +100,7 @@ "style-loader": "0.13.x", "tslint": "5.8.0", "typescript": "~2.6.1", - "web3-typescript-typings": "^0.9.5", + "web3-typescript-typings": "^0.9.7", "webpack": "^3.1.0", "webpack-dev-middleware": "^1.10.0", "webpack-dev-server": "^2.5.0" diff --git a/packages/website/public/gifs/0xAnimation.gif b/packages/website/public/gifs/0xAnimation.gif Binary files differdeleted file mode 100644 index b3e32a6ad..000000000 --- a/packages/website/public/gifs/0xAnimation.gif +++ /dev/null diff --git a/packages/website/public/images/ledger_icon.png b/packages/website/public/images/ledger_icon.png Binary files differnew file mode 100644 index 000000000..29b8df08f --- /dev/null +++ b/packages/website/public/images/ledger_icon.png diff --git a/packages/website/public/images/loading_poster.png b/packages/website/public/images/loading_poster.png Binary files differdeleted file mode 100644 index e5618f260..000000000 --- a/packages/website/public/images/loading_poster.png +++ /dev/null diff --git a/packages/website/public/images/metamask_or_parity.png b/packages/website/public/images/metamask_or_parity.png Binary files differnew file mode 100644 index 000000000..fda646558 --- /dev/null +++ b/packages/website/public/images/metamask_or_parity.png diff --git a/packages/website/public/images/network_icons/kovan.png b/packages/website/public/images/network_icons/kovan.png Binary files differnew file mode 100644 index 000000000..f47a12e74 --- /dev/null +++ b/packages/website/public/images/network_icons/kovan.png diff --git a/packages/website/public/images/network_icons/mainnet.png b/packages/website/public/images/network_icons/mainnet.png Binary files differnew file mode 100644 index 000000000..6693635d6 --- /dev/null +++ b/packages/website/public/images/network_icons/mainnet.png diff --git a/packages/website/public/images/network_icons/rinkeby.png b/packages/website/public/images/network_icons/rinkeby.png Binary files differnew file mode 100644 index 000000000..f9ba18778 --- /dev/null +++ b/packages/website/public/images/network_icons/rinkeby.png diff --git a/packages/website/public/images/network_icons/ropsten.png b/packages/website/public/images/network_icons/ropsten.png Binary files differnew file mode 100644 index 000000000..894910b34 --- /dev/null +++ b/packages/website/public/images/network_icons/ropsten.png diff --git a/packages/website/public/videos/0xAnimation.mp4 b/packages/website/public/videos/0xAnimation.mp4 Binary files differdeleted file mode 100644 index d78c07d4f..000000000 --- a/packages/website/public/videos/0xAnimation.mp4 +++ /dev/null diff --git a/packages/website/ts/blockchain.ts b/packages/website/ts/blockchain.ts index 5530701c0..d53994c0c 100644 --- a/packages/website/ts/blockchain.ts +++ b/packages/website/ts/blockchain.ts @@ -37,10 +37,10 @@ import { EtherscanLinkSuffixes, ProviderType, Side, + SideToAssetToken, SignatureData, Token, TokenByAddress, - TokenStateByAddress, } from 'ts/types'; import { configs } from 'ts/utils/configs'; import { constants } from 'ts/utils/constants'; @@ -54,6 +54,7 @@ import FilterSubprovider = require('web3-provider-engine/subproviders/filters'); import * as MintableArtifacts from '../contracts/Mintable.json'; const BLOCK_NUMBER_BACK_TRACK = 50; +const GWEI_IN_WEI = 1000000000; export class Blockchain { public networkId: number; @@ -64,8 +65,9 @@ export class Blockchain { private _exchangeAddress: string; private _userAddress: string; private _cachedProvider: Web3.Provider; + private _cachedProviderNetworkId: number; private _ledgerSubprovider: LedgerWalletSubprovider; - private _zrxPollIntervalId: NodeJS.Timer; + private _defaultGasPrice: BigNumber; private static async _onPageLoadAsync(): Promise<void> { if (document.readyState === 'complete') { return; // Already loaded @@ -111,7 +113,7 @@ export class Blockchain { // injected into their browser. provider = new ProviderEngine(); provider.addProvider(new FilterSubprovider()); - const networkId = configs.IS_MAINNET_ENABLED ? constants.NETWORK_ID_MAINNET : constants.NETWORK_ID_TESTNET; + const networkId = configs.IS_MAINNET_ENABLED ? constants.NETWORK_ID_MAINNET : constants.NETWORK_ID_KOVAN; provider.addProvider(new RedundantRPCSubprovider(configs.PUBLIC_NODE_URLS_BY_NETWORK_ID[networkId])); provider.start(); } @@ -121,6 +123,10 @@ export class Blockchain { constructor(dispatcher: Dispatcher, isSalePage: boolean = false) { this._dispatcher = dispatcher; this._userAddress = ''; + const defaultGasPrice = GWEI_IN_WEI * 30; + this._defaultGasPrice = new BigNumber(defaultGasPrice); + // tslint:disable-next-line:no-floating-promises + this._updateDefaultGasPriceAsync(); // tslint:disable-next-line:no-floating-promises this._onPageLoadInitFireAndForgetAsync(); } @@ -133,14 +139,14 @@ export class Blockchain { } else if (this.networkId !== newNetworkId) { this.networkId = newNetworkId; this._dispatcher.encounteredBlockchainError(BlockchainErrs.NoError); - await this._fetchTokenInformationAsync(); + await this.fetchTokenInformationAsync(); await this._rehydrateStoreWithContractEvents(); } } public async userAddressUpdatedFireAndForgetAsync(newUserAddress: string) { if (this._userAddress !== newUserAddress) { this._userAddress = newUserAddress; - await this._fetchTokenInformationAsync(); + await this.fetchTokenInformationAsync(); await this._rehydrateStoreWithContractEvents(); } } @@ -180,84 +186,96 @@ export class Blockchain { } this._ledgerSubprovider.setPathIndex(pathIndex); } - public async providerTypeUpdatedFireAndForgetAsync(providerType: ProviderType) { + public async updateProviderToLedgerAsync(networkId: number) { utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.'); - // Should actually be Web3.Provider|ProviderEngine union type but it causes issues - // later on in the logic. - let provider; - switch (providerType) { - case ProviderType.Ledger: { - const isU2FSupported = await utils.isU2FSupportedAsync(); - if (!isU2FSupported) { - throw new Error('Cannot update providerType to LEDGER without U2F support'); - } - // Cache injected provider so that we can switch the user back to it easily - this._cachedProvider = this._web3Wrapper.getProviderObj(); - - this._dispatcher.updateUserAddress(''); // Clear old userAddress - - provider = new ProviderEngine(); - const ledgerWalletConfigs = { - networkId: this.networkId, - ledgerEthereumClientFactoryAsync: ledgerEthereumBrowserClientFactoryAsync, - }; - this._ledgerSubprovider = new LedgerSubprovider(ledgerWalletConfigs); - provider.addProvider(this._ledgerSubprovider); - provider.addProvider(new FilterSubprovider()); - const networkId = configs.IS_MAINNET_ENABLED - ? constants.NETWORK_ID_MAINNET - : constants.NETWORK_ID_TESTNET; - provider.addProvider(new RedundantRPCSubprovider(configs.PUBLIC_NODE_URLS_BY_NETWORK_ID[networkId])); - provider.start(); - this._web3Wrapper.destroy(); - const shouldPollUserAddress = false; - this._web3Wrapper = new Web3Wrapper(this._dispatcher, provider, this.networkId, shouldPollUserAddress); - this._zeroEx.setProvider(provider, networkId); - await this._postInstantiationOrUpdatingProviderZeroExAsync(); - break; - } + const isU2FSupported = await utils.isU2FSupportedAsync(); + if (!isU2FSupported) { + throw new Error('Cannot update providerType to LEDGER without U2F support'); + } - case ProviderType.Injected: { - if (_.isUndefined(this._cachedProvider)) { - return; // Going from injected to injected, so we noop - } - provider = this._cachedProvider; - const shouldPollUserAddress = true; - this._web3Wrapper = new Web3Wrapper(this._dispatcher, provider, this.networkId, shouldPollUserAddress); - this._zeroEx.setProvider(provider, this.networkId); - await this._postInstantiationOrUpdatingProviderZeroExAsync(); - delete this._ledgerSubprovider; - delete this._cachedProvider; - break; - } + // Cache injected provider so that we can switch the user back to it easily + if (_.isUndefined(this._cachedProvider)) { + this._cachedProvider = this._web3Wrapper.getProviderObj(); + this._cachedProviderNetworkId = this.networkId; + } - default: - throw utils.spawnSwitchErr('providerType', providerType); + this._web3Wrapper.destroy(); + + this._userAddress = ''; + this._dispatcher.updateUserAddress(''); // Clear old userAddress + + const provider = new ProviderEngine(); + const ledgerWalletConfigs = { + networkId, + ledgerEthereumClientFactoryAsync: ledgerEthereumBrowserClientFactoryAsync, + }; + this._ledgerSubprovider = new LedgerSubprovider(ledgerWalletConfigs); + provider.addProvider(this._ledgerSubprovider); + provider.addProvider(new FilterSubprovider()); + provider.addProvider(new RedundantRPCSubprovider(configs.PUBLIC_NODE_URLS_BY_NETWORK_ID[networkId])); + provider.start(); + this.networkId = networkId; + this._dispatcher.updateNetworkId(this.networkId); + const shouldPollUserAddress = false; + this._web3Wrapper = new Web3Wrapper(this._dispatcher, provider, this.networkId, shouldPollUserAddress); + this._zeroEx.setProvider(provider, this.networkId); + await this._postInstantiationOrUpdatingProviderZeroExAsync(); + this._web3Wrapper.startEmittingNetworkConnectionAndUserBalanceState(); + this._dispatcher.updateProviderType(ProviderType.Ledger); + } + public async updateProviderToInjectedAsync() { + utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.'); + + if (_.isUndefined(this._cachedProvider)) { + return; // Going from injected to injected, so we noop } - await this._fetchTokenInformationAsync(); + this._web3Wrapper.destroy(); + + const provider = this._cachedProvider; + this.networkId = this._cachedProviderNetworkId; + + const shouldPollUserAddress = true; + this._web3Wrapper = new Web3Wrapper(this._dispatcher, provider, this.networkId, shouldPollUserAddress); + + this._userAddress = await this._web3Wrapper.getFirstAccountIfExistsAsync(); + + this._zeroEx.setProvider(provider, this.networkId); + await this._postInstantiationOrUpdatingProviderZeroExAsync(); + + await this.fetchTokenInformationAsync(); + this._web3Wrapper.startEmittingNetworkConnectionAndUserBalanceState(); + this._dispatcher.updateProviderType(ProviderType.Injected); + delete this._ledgerSubprovider; + delete this._cachedProvider; } public async setProxyAllowanceAsync(token: Token, amountInBaseUnits: BigNumber): Promise<void> { utils.assert(this.isValidAddress(token.address), BlockchainCallErrs.TokenAddressIsInvalid); utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses); utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.'); + this._showFlashMessageIfLedger(); const txHash = await this._zeroEx.token.setProxyAllowanceAsync( token.address, this._userAddress, amountInBaseUnits, + { + gasPrice: this._defaultGasPrice, + }, ); await this._showEtherScanLinkAndAwaitTransactionMinedAsync(txHash); - const allowance = amountInBaseUnits; - this._dispatcher.replaceTokenAllowanceByAddress(token.address, allowance); } public async transferAsync(token: Token, toAddress: string, amountInBaseUnits: BigNumber): Promise<void> { + this._showFlashMessageIfLedger(); const txHash = await this._zeroEx.token.transferAsync( token.address, this._userAddress, toAddress, amountInBaseUnits, + { + gasPrice: this._defaultGasPrice, + }, ); await this._showEtherScanLinkAndAwaitTransactionMinedAsync(txHash); const etherScanLinkIfExists = utils.getEtherScanLinkIfExists(txHash, this.networkId, EtherscanLinkSuffixes.Tx); @@ -309,11 +327,15 @@ export class Blockchain { const shouldThrowOnInsufficientBalanceOrAllowance = true; + this._showFlashMessageIfLedger(); const txHash = await this._zeroEx.exchange.fillOrderAsync( signedOrder, fillTakerTokenAmount, shouldThrowOnInsufficientBalanceOrAllowance, this._userAddress, + { + gasPrice: this._defaultGasPrice, + }, ); const receipt = await this._showEtherScanLinkAndAwaitTransactionMinedAsync(txHash); const logs: Array<LogWithDecodedArgs<ExchangeContractEventArgs>> = receipt.logs as any; @@ -324,7 +346,10 @@ export class Blockchain { return filledTakerTokenAmount; } public async cancelOrderAsync(signedOrder: SignedOrder, cancelTakerTokenAmount: BigNumber): Promise<BigNumber> { - const txHash = await this._zeroEx.exchange.cancelOrderAsync(signedOrder, cancelTakerTokenAmount); + this._showFlashMessageIfLedger(); + const txHash = await this._zeroEx.exchange.cancelOrderAsync(signedOrder, cancelTakerTokenAmount, { + gasPrice: this._defaultGasPrice, + }); const receipt = await this._showEtherScanLinkAndAwaitTransactionMinedAsync(txHash); const logs: Array<LogWithDecodedArgs<ExchangeContractEventArgs>> = receipt.logs as any; this._zeroEx.exchange.throwLogErrorsAsErrors(logs); @@ -368,22 +393,25 @@ export class Blockchain { const [currBalance] = await this.getTokenBalanceAndAllowanceAsync(this._userAddress, token.address); - this._zrxPollIntervalId = intervalUtils.setAsyncExcludingInterval( - async () => { - const [balance] = await this.getTokenBalanceAndAllowanceAsync(this._userAddress, token.address); - if (!balance.eq(currBalance)) { - this._dispatcher.replaceTokenBalanceByAddress(token.address, balance); - intervalUtils.clearAsyncExcludingInterval(this._zrxPollIntervalId); - delete this._zrxPollIntervalId; - } - }, - 5000, - (err: Error) => { - utils.consoleLog(`Polling tokenBalance failed: ${err}`); - intervalUtils.clearAsyncExcludingInterval(this._zrxPollIntervalId); - delete this._zrxPollIntervalId; - }, - ); + const newTokenBalancePromise = new Promise((resolve: (balance: BigNumber) => void, reject) => { + const tokenPollInterval = intervalUtils.setAsyncExcludingInterval( + async () => { + const [balance] = await this.getTokenBalanceAndAllowanceAsync(this._userAddress, token.address); + if (!balance.eq(currBalance)) { + intervalUtils.clearAsyncExcludingInterval(tokenPollInterval); + resolve(balance); + } + }, + 5000, + (err: Error) => { + utils.consoleLog(`Polling tokenBalance failed: ${err}`); + intervalUtils.clearAsyncExcludingInterval(tokenPollInterval); + reject(err); + }, + ); + }); + + return newTokenBalancePromise; } public async signOrderHashAsync(orderHash: string): Promise<SignatureData> { utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.'); @@ -393,7 +421,21 @@ export class Blockchain { if (_.isUndefined(makerAddress)) { throw new Error('Tried to send a sign request but user has no associated addresses'); } - const ecSignature = await this._zeroEx.signOrderHashAsync(orderHash, makerAddress); + + this._showFlashMessageIfLedger(); + const nodeVersion = await this._web3Wrapper.getNodeVersionAsync(); + const isParityNode = utils.isParityNode(nodeVersion); + const isTestRpc = utils.isTestRpc(nodeVersion); + const isLedgerSigner = !_.isUndefined(this._ledgerSubprovider); + let shouldAddPersonalMessagePrefix = true; + if ((isParityNode && !isLedgerSigner) || isTestRpc || isLedgerSigner) { + shouldAddPersonalMessagePrefix = false; + } + const ecSignature = await this._zeroEx.signOrderHashAsync( + orderHash, + makerAddress, + shouldAddPersonalMessagePrefix, + ); const signatureData = _.extend({}, ecSignature, { hash: orderHash, }); @@ -404,11 +446,11 @@ export class Blockchain { utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses); const mintableContract = await this._instantiateContractIfExistsAsync(MintableArtifacts, token.address); + this._showFlashMessageIfLedger(); await mintableContract.mint(constants.MINT_AMOUNT, { from: this._userAddress, + gasPrice: this._defaultGasPrice, }); - const balanceDelta = constants.MINT_AMOUNT; - this._dispatcher.updateTokenBalanceByAddress(token.address, balanceDelta); } public async getBalanceInEthAsync(owner: string): Promise<BigNumber> { const balance = await this._web3Wrapper.getBalanceInEthAsync(owner); @@ -418,14 +460,20 @@ export class Blockchain { utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.'); utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses); - const txHash = await this._zeroEx.etherToken.depositAsync(etherTokenAddress, amount, this._userAddress); + this._showFlashMessageIfLedger(); + const txHash = await this._zeroEx.etherToken.depositAsync(etherTokenAddress, amount, this._userAddress, { + gasPrice: this._defaultGasPrice, + }); await this._showEtherScanLinkAndAwaitTransactionMinedAsync(txHash); } public async convertWrappedEthTokensToEthAsync(etherTokenAddress: string, amount: BigNumber): Promise<void> { utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.'); utils.assert(this._doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses); - const txHash = await this._zeroEx.etherToken.withdrawAsync(etherTokenAddress, amount, this._userAddress); + this._showFlashMessageIfLedger(); + const txHash = await this._zeroEx.etherToken.withdrawAsync(etherTokenAddress, amount, this._userAddress, { + gasPrice: this._defaultGasPrice, + }); await this._showEtherScanLinkAndAwaitTransactionMinedAsync(txHash); } public async doesContractExistAtAddressAsync(address: string) { @@ -451,22 +499,6 @@ export class Blockchain { } return [balance, allowance]; } - public async updateTokenBalancesAndAllowancesAsync(tokens: Token[]) { - const tokenStateByAddress: TokenStateByAddress = {}; - for (const token of tokens) { - let balance = new BigNumber(0); - let allowance = new BigNumber(0); - if (this._doesUserAddressExist()) { - [balance, allowance] = await this.getTokenBalanceAndAllowanceAsync(this._userAddress, token.address); - } - const tokenState = { - balance, - allowance, - }; - tokenStateByAddress[token.address] = tokenState; - } - this._dispatcher.updateTokenStateByAddress(tokenStateByAddress); - } public async getUserAccountsAsync() { utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.'); const userAccountsIfExists = await this._zeroEx.getAvailableAddressesAsync(); @@ -479,10 +511,59 @@ export class Blockchain { this._web3Wrapper.updatePrevUserAddress(newUserAddress); } public destroy() { - intervalUtils.clearAsyncExcludingInterval(this._zrxPollIntervalId); this._web3Wrapper.destroy(); this._stopWatchingExchangeLogFillEvents(); } + public async fetchTokenInformationAsync() { + utils.assert( + !_.isUndefined(this.networkId), + 'Cannot call fetchTokenInformationAsync if disconnected from Ethereum node', + ); + + this._dispatcher.updateBlockchainIsLoaded(false); + + const tokenRegistryTokensByAddress = await this._getTokenRegistryTokensByAddressAsync(); + + const trackedTokensByAddress = trackedTokenStorage.getTrackedTokensByAddress(this._userAddress, this.networkId); + const tokenRegistryTokens = _.values(tokenRegistryTokensByAddress); + if (_.isEmpty(trackedTokensByAddress)) { + _.each(configs.DEFAULT_TRACKED_TOKEN_SYMBOLS, symbol => { + const token = _.find(tokenRegistryTokens, t => t.symbol === symbol); + token.isTracked = true; + trackedTokensByAddress[token.address] = token; + }); + _.each(trackedTokensByAddress, (token: Token, address: string) => { + trackedTokenStorage.addTrackedTokenToUser(this._userAddress, this.networkId, token); + }); + } else { + // Properly set all tokenRegistry tokens `isTracked` to true if they are in the existing trackedTokens array + _.each(trackedTokensByAddress, (trackedToken: Token, address: string) => { + if (!_.isUndefined(tokenRegistryTokensByAddress[address])) { + tokenRegistryTokensByAddress[address].isTracked = true; + } + }); + } + const allTokensByAddress = { + ...tokenRegistryTokensByAddress, + ...trackedTokensByAddress, + }; + const allTokens = _.values(allTokensByAddress); + const mostPopularTradingPairTokens: Token[] = [ + _.find(allTokens, { symbol: configs.DEFAULT_TRACKED_TOKEN_SYMBOLS[0] }), + _.find(allTokens, { symbol: configs.DEFAULT_TRACKED_TOKEN_SYMBOLS[1] }), + ]; + const sideToAssetToken: SideToAssetToken = { + [Side.Deposit]: { + address: mostPopularTradingPairTokens[0].address, + }, + [Side.Receive]: { + address: mostPopularTradingPairTokens[1].address, + }, + }; + this._dispatcher.batchDispatch(allTokensByAddress, this.networkId, this._userAddress, sideToAssetToken); + + this._dispatcher.updateBlockchainIsLoaded(true); + } private async _showEtherScanLinkAndAwaitTransactionMinedAsync( txHash: string, ): Promise<TransactionReceiptWithDecodedLogs> { @@ -665,17 +746,23 @@ export class Blockchain { } const provider = await Blockchain._getProviderAsync(injectedWeb3, networkIdIfExists); - const networkId = !_.isUndefined(networkIdIfExists) + this.networkId = !_.isUndefined(networkIdIfExists) ? networkIdIfExists - : configs.IS_MAINNET_ENABLED ? constants.NETWORK_ID_MAINNET : constants.NETWORK_ID_TESTNET; + : configs.IS_MAINNET_ENABLED ? constants.NETWORK_ID_MAINNET : constants.NETWORK_ID_KOVAN; + this._dispatcher.updateNetworkId(this.networkId); const zeroExConfigs = { - networkId, + networkId: this.networkId, }; this._zeroEx = new ZeroEx(provider, zeroExConfigs); this._updateProviderName(injectedWeb3); const shouldPollUserAddress = true; - this._web3Wrapper = new Web3Wrapper(this._dispatcher, provider, networkId, shouldPollUserAddress); + this._web3Wrapper = new Web3Wrapper(this._dispatcher, provider, this.networkId, shouldPollUserAddress); await this._postInstantiationOrUpdatingProviderZeroExAsync(); + this._userAddress = await this._web3Wrapper.getFirstAccountIfExistsAsync(); + this._dispatcher.updateUserAddress(this._userAddress); + await this.fetchTokenInformationAsync(); + this._web3Wrapper.startEmittingNetworkConnectionAndUserBalanceState(); + await this._rehydrateStoreWithContractEvents(); } // This method should always be run after instantiating or updating the provider // of the ZeroEx instance. @@ -690,60 +777,6 @@ export class Blockchain { : constants.PROVIDER_NAME_PUBLIC; this._dispatcher.updateInjectedProviderName(providerName); } - private async _fetchTokenInformationAsync() { - utils.assert( - !_.isUndefined(this.networkId), - 'Cannot call fetchTokenInformationAsync if disconnected from Ethereum node', - ); - - this._dispatcher.updateBlockchainIsLoaded(false); - this._dispatcher.clearTokenByAddress(); - - const tokenRegistryTokensByAddress = await this._getTokenRegistryTokensByAddressAsync(); - - // HACK: We need to fetch the userAddress here because otherwise we cannot save the - // tracked tokens in localStorage under the users address nor fetch the token - // balances and allowances and we need to do this in order not to trigger the blockchain - // loading dialog to show up twice. First to load the contracts, and second to load the - // balances and allowances. - this._userAddress = await this._web3Wrapper.getFirstAccountIfExistsAsync(); - if (!_.isEmpty(this._userAddress)) { - this._dispatcher.updateUserAddress(this._userAddress); - } - - let trackedTokensIfExists = trackedTokenStorage.getTrackedTokensIfExists(this._userAddress, this.networkId); - const tokenRegistryTokens = _.values(tokenRegistryTokensByAddress); - if (_.isUndefined(trackedTokensIfExists)) { - trackedTokensIfExists = _.map(configs.DEFAULT_TRACKED_TOKEN_SYMBOLS, symbol => { - const token = _.find(tokenRegistryTokens, t => t.symbol === symbol); - token.isTracked = true; - return token; - }); - _.each(trackedTokensIfExists, token => { - trackedTokenStorage.addTrackedTokenToUser(this._userAddress, this.networkId, token); - }); - } else { - // Properly set all tokenRegistry tokens `isTracked` to true if they are in the existing trackedTokens array - _.each(trackedTokensIfExists, trackedToken => { - if (!_.isUndefined(tokenRegistryTokensByAddress[trackedToken.address])) { - tokenRegistryTokensByAddress[trackedToken.address].isTracked = true; - } - }); - } - const allTokens = _.uniq([...tokenRegistryTokens, ...trackedTokensIfExists]); - this._dispatcher.updateTokenByAddress(allTokens); - - // Get balance/allowance for tracked tokens - await this.updateTokenBalancesAndAllowancesAsync(trackedTokensIfExists); - - const mostPopularTradingPairTokens: Token[] = [ - _.find(allTokens, { symbol: configs.DEFAULT_TRACKED_TOKEN_SYMBOLS[0] }), - _.find(allTokens, { symbol: configs.DEFAULT_TRACKED_TOKEN_SYMBOLS[1] }), - ]; - this._dispatcher.updateChosenAssetTokenAddress(Side.Deposit, mostPopularTradingPairTokens[0].address); - this._dispatcher.updateChosenAssetTokenAddress(Side.Receive, mostPopularTradingPairTokens[1].address); - this._dispatcher.updateBlockchainIsLoaded(true); - } private async _instantiateContractIfExistsAsync(artifact: any, address?: string): Promise<ContractInstance> { const c = await contract(artifact); const providerObj = this._web3Wrapper.getProviderObj(); @@ -779,4 +812,20 @@ export class Blockchain { } } } + private _showFlashMessageIfLedger() { + if (!_.isUndefined(this._ledgerSubprovider)) { + this._dispatcher.showFlashMessage('Confirm the transaction on your Ledger Nano S'); + } + } + private async _updateDefaultGasPriceAsync() { + const endpoint = `${configs.BACKEND_BASE_URL}/eth_gas_station`; + const response = await fetch(endpoint); + if (response.status !== 200) { + return; // noop and we keep hard-coded default + } + const gasInfo = await response.json(); + const gasPriceInGwei = new BigNumber(gasInfo.average / 10); + const gasPriceInWei = gasPriceInGwei.mul(1000000000); + this._defaultGasPrice = gasPriceInWei; + } } // tslint:disable:max-file-line-count diff --git a/packages/website/ts/components/dialogs/blockchain_err_dialog.tsx b/packages/website/ts/components/dialogs/blockchain_err_dialog.tsx index f555ca6b1..278e2bbf5 100644 --- a/packages/website/ts/components/dialogs/blockchain_err_dialog.tsx +++ b/packages/website/ts/components/dialogs/blockchain_err_dialog.tsx @@ -3,7 +3,7 @@ import Dialog from 'material-ui/Dialog'; import FlatButton from 'material-ui/FlatButton'; import * as React from 'react'; import { Blockchain } from 'ts/blockchain'; -import { BlockchainErrs } from 'ts/types'; +import { BlockchainErrs, Networks } from 'ts/types'; import { colors } from 'ts/utils/colors'; import { configs } from 'ts/utils/configs'; import { constants } from 'ts/utils/constants'; @@ -129,7 +129,7 @@ export class BlockchainErrDialog extends React.Component<BlockchainErrDialogProp <div> The 0x smart contracts are not deployed on the Ethereum network you are currently connected to (network Id: {this.props.networkId}). In order to use the 0x portal dApp, please connect to the{' '} - {constants.TESTNET_NAME} testnet (network Id: {constants.NETWORK_ID_TESTNET}) + {Networks.Kovan} testnet (network Id: {constants.NETWORK_ID_KOVAN}) {configs.IS_MAINNET_ENABLED ? ` or ${constants.MAINNET_NAME} (network Id: ${constants.NETWORK_ID_MAINNET}).` : `.`} diff --git a/packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx b/packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx index 661cc1d8c..acd4a7110 100644 --- a/packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx +++ b/packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx @@ -2,38 +2,55 @@ import { BigNumber } from '@0xproject/utils'; import Dialog from 'material-ui/Dialog'; import FlatButton from 'material-ui/FlatButton'; import * as React from 'react'; +import { Blockchain } from 'ts/blockchain'; import { EthAmountInput } from 'ts/components/inputs/eth_amount_input'; import { TokenAmountInput } from 'ts/components/inputs/token_amount_input'; -import { Side, Token, TokenState } from 'ts/types'; +import { Side, Token } from 'ts/types'; import { colors } from 'ts/utils/colors'; interface EthWethConversionDialogProps { + blockchain: Blockchain; + userAddress: string; + networkId: number; direction: Side; onComplete: (direction: Side, value: BigNumber) => void; onCancelled: () => void; isOpen: boolean; token: Token; - tokenState: TokenState; etherBalance: BigNumber; + lastForceTokenStateRefetch: number; } interface EthWethConversionDialogState { value?: BigNumber; shouldShowIncompleteErrs: boolean; hasErrors: boolean; + isEthTokenBalanceLoaded: boolean; + ethTokenBalance: BigNumber; } export class EthWethConversionDialog extends React.Component< EthWethConversionDialogProps, EthWethConversionDialogState > { + private _isUnmounted: boolean; constructor() { super(); + this._isUnmounted = false; this.state = { shouldShowIncompleteErrs: false, hasErrors: false, + isEthTokenBalanceLoaded: false, + ethTokenBalance: new BigNumber(0), }; } + public componentWillMount() { + // tslint:disable-next-line:no-floating-promises + this._fetchEthTokenBalanceAsync(); + } + public componentWillUnmount() { + this._isUnmounted = true; + } public render() { const convertDialogActions = [ <FlatButton key="cancel" label="Cancel" onTouchTap={this._onCancel.bind(this)} />, @@ -72,8 +89,11 @@ export class EthWethConversionDialog extends React.Component< <div className="pt2 mx-auto" style={{ width: 245 }}> {this.props.direction === Side.Receive ? ( <TokenAmountInput + lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch} + blockchain={this.props.blockchain} + userAddress={this.props.userAddress} + networkId={this.props.networkId} token={this.props.token} - tokenState={this.props.tokenState} shouldShowIncompleteErrs={this.state.shouldShowIncompleteErrs} shouldCheckBalance={true} shouldCheckAllowance={false} @@ -93,19 +113,20 @@ export class EthWethConversionDialog extends React.Component< )} <div className="pt1" style={{ fontSize: 12 }}> <div className="left">1 ETH = 1 WETH</div> - {this.props.direction === Side.Receive && ( - <div - className="right" - onClick={this._onMaxClick.bind(this)} - style={{ - color: colors.darkBlue, - textDecoration: 'underline', - cursor: 'pointer', - }} - > - Max - </div> - )} + {this.props.direction === Side.Receive && + this.state.isEthTokenBalanceLoaded && ( + <div + className="right" + onClick={this._onMaxClick.bind(this)} + style={{ + color: colors.darkBlue, + textDecoration: 'underline', + cursor: 'pointer', + }} + > + Max + </div> + )} </div> </div> </div> @@ -132,7 +153,7 @@ export class EthWethConversionDialog extends React.Component< } private _onMaxClick() { this.setState({ - value: this.props.tokenState.balance, + value: this.state.ethTokenBalance, }); } private _onValueChange(isValid: boolean, amount?: BigNumber) { @@ -160,4 +181,16 @@ export class EthWethConversionDialog extends React.Component< }); this.props.onCancelled(); } + private async _fetchEthTokenBalanceAsync() { + const [balance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync( + this.props.userAddress, + this.props.token.address, + ); + if (!this._isUnmounted) { + this.setState({ + isEthTokenBalanceLoaded: true, + ethTokenBalance: balance, + }); + } + } } diff --git a/packages/website/ts/components/dialogs/ledger_config_dialog.tsx b/packages/website/ts/components/dialogs/ledger_config_dialog.tsx index 60db93c52..bc5f05241 100644 --- a/packages/website/ts/components/dialogs/ledger_config_dialog.tsx +++ b/packages/website/ts/components/dialogs/ledger_config_dialog.tsx @@ -7,8 +7,10 @@ import TextField from 'material-ui/TextField'; import * as React from 'react'; import ReactTooltip = require('react-tooltip'); import { Blockchain } from 'ts/blockchain'; +import { NetworkDropDown } from 'ts/components/dropdowns/network_drop_down'; import { LifeCycleRaisedButton } from 'ts/components/ui/lifecycle_raised_button'; import { Dispatcher } from 'ts/redux/dispatcher'; +import { ProviderType } from 'ts/types'; import { colors } from 'ts/utils/colors'; import { configs } from 'ts/utils/configs'; import { constants } from 'ts/utils/constants'; @@ -27,27 +29,33 @@ interface LedgerConfigDialogProps { dispatcher: Dispatcher; blockchain: Blockchain; networkId: number; + providerType: ProviderType; } interface LedgerConfigDialogState { - didConnectFail: boolean; + connectionErrMsg: string; stepIndex: LedgerSteps; userAddresses: string[]; addressBalances: BigNumber[]; derivationPath: string; derivationErrMsg: string; + preferredNetworkId: number; } export class LedgerConfigDialog extends React.Component<LedgerConfigDialogProps, LedgerConfigDialogState> { constructor(props: LedgerConfigDialogProps) { super(props); + const derivationPathIfExists = props.blockchain.getLedgerDerivationPathIfExists(); this.state = { - didConnectFail: false, + connectionErrMsg: '', stepIndex: LedgerSteps.CONNECT, userAddresses: [], addressBalances: [], - derivationPath: configs.DEFAULT_DERIVATION_PATH, + derivationPath: _.isUndefined(derivationPathIfExists) + ? configs.DEFAULT_DERIVATION_PATH + : derivationPathIfExists, derivationErrMsg: '', + preferredNetworkId: props.networkId, }; } public render() { @@ -74,19 +82,28 @@ export class LedgerConfigDialog extends React.Component<LedgerConfigDialogProps, ); } private _renderConnectStep() { + const networkIds = _.values(constants.NETWORK_ID_BY_NAME); return ( <div> <div className="h4 pt3">Follow these instructions before proceeding:</div> - <ol> + <ol className="mb0"> <li className="pb1">Connect your Ledger Nano S & Open the Ethereum application</li> - <li className="pb1">Verify that Browser Support is enabled in Settings</li> + <li className="pb1">Verify that "Browser Support" AND "Contract Data" are enabled in Settings</li> <li className="pb1"> If no Browser Support is found in settings, verify that you have{' '} <a href="https://www.ledgerwallet.com/apps/manager" target="_blank"> Firmware >1.2 </a> </li> + <li>Choose your desired network:</li> </ol> + <div className="pb2"> + <NetworkDropDown + updateSelectedNetwork={this._onSelectedNetworkUpdated.bind(this)} + selectedNetworkId={this.state.preferredNetworkId} + avialableNetworkIds={networkIds} + /> + </div> <div className="center pb3"> <LifeCycleRaisedButton isPrimary={true} @@ -95,9 +112,9 @@ export class LedgerConfigDialog extends React.Component<LedgerConfigDialogProps, labelComplete="Connected!" onClickAsyncFn={this._onConnectLedgerClickAsync.bind(this, true)} /> - {this.state.didConnectFail && ( + {!_.isEmpty(this.state.connectionErrMsg) && ( <div className="pt2 left-align" style={{ color: colors.red200 }}> - Failed to connect. Follow the instructions and try again. + {this.state.connectionErrMsg} </div> )} </div> @@ -172,7 +189,8 @@ export class LedgerConfigDialog extends React.Component<LedgerConfigDialogProps, } private _onClose() { this.setState({ - didConnectFail: false, + connectionErrMsg: '', + stepIndex: LedgerSteps.CONNECT, }); const isOpen = false; this.props.toggleDialogFn(isOpen); @@ -184,6 +202,8 @@ export class LedgerConfigDialog extends React.Component<LedgerConfigDialogProps, const selectAddressBalance = this.state.addressBalances[selectedRowIndex]; this.props.dispatcher.updateUserAddress(selectedAddress); this.props.blockchain.updateWeb3WrapperPrevUserAddress(selectedAddress); + // tslint:disable-next-line:no-floating-promises + this.props.blockchain.fetchTokenInformationAsync(); this.props.dispatcher.updateUserEtherBalance(selectAddressBalance); this.setState({ stepIndex: LedgerSteps.CONNECT, @@ -219,7 +239,7 @@ export class LedgerConfigDialog extends React.Component<LedgerConfigDialogProps, } catch (err) { utils.consoleLog(`Ledger error: ${JSON.stringify(err)}`); this.setState({ - didConnectFail: true, + connectionErrMsg: 'Failed to connect. Follow the instructions and try again.', }); return false; } @@ -241,6 +261,22 @@ export class LedgerConfigDialog extends React.Component<LedgerConfigDialogProps, }); } private async _onConnectLedgerClickAsync() { + const isU2FSupported = await utils.isU2FSupportedAsync(); + if (!isU2FSupported) { + utils.consoleLog(`U2F not supported in this browser`); + this.setState({ + connectionErrMsg: 'U2F not supported by this browser. Try using Chrome.', + }); + return false; + } + + if ( + this.props.providerType !== ProviderType.Ledger || + (this.props.providerType === ProviderType.Ledger && this.props.networkId !== this.state.preferredNetworkId) + ) { + await this.props.blockchain.updateProviderToLedgerAsync(this.state.preferredNetworkId); + } + const didSucceed = await this._fetchAddressesAndBalancesAsync(); if (didSucceed) { this.setState({ @@ -258,4 +294,9 @@ export class LedgerConfigDialog extends React.Component<LedgerConfigDialogProps, } return userAddresses; } + private _onSelectedNetworkUpdated(e: any, index: number, networkId: number) { + this.setState({ + preferredNetworkId: networkId, + }); + } } diff --git a/packages/website/ts/components/dialogs/send_dialog.tsx b/packages/website/ts/components/dialogs/send_dialog.tsx index b3dbce598..d44dd9aab 100644 --- a/packages/website/ts/components/dialogs/send_dialog.tsx +++ b/packages/website/ts/components/dialogs/send_dialog.tsx @@ -3,16 +3,20 @@ import * as _ from 'lodash'; import Dialog from 'material-ui/Dialog'; import FlatButton from 'material-ui/FlatButton'; import * as React from 'react'; +import { Blockchain } from 'ts/blockchain'; import { AddressInput } from 'ts/components/inputs/address_input'; import { TokenAmountInput } from 'ts/components/inputs/token_amount_input'; -import { Token, TokenState } from 'ts/types'; +import { Token } from 'ts/types'; interface SendDialogProps { + blockchain: Blockchain; + userAddress: string; + networkId: number; onComplete: (recipient: string, value: BigNumber) => void; onCancelled: () => void; isOpen: boolean; token: Token; - tokenState: TokenState; + lastForceTokenStateRefetch: number; } interface SendDialogState { @@ -66,15 +70,18 @@ export class SendDialog extends React.Component<SendDialogProps, SendDialogState /> </div> <TokenAmountInput + blockchain={this.props.blockchain} + userAddress={this.props.userAddress} + networkId={this.props.networkId} label="Amount to send" token={this.props.token} - tokenState={this.props.tokenState} shouldShowIncompleteErrs={this.state.shouldShowIncompleteErrs} shouldCheckBalance={true} shouldCheckAllowance={false} onChange={this._onValueChange.bind(this)} amount={this.state.value} onVisitBalancesPageClick={this.props.onCancelled} + lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch} /> </div> ); diff --git a/packages/website/ts/components/dialogs/track_token_confirmation_dialog.tsx b/packages/website/ts/components/dialogs/track_token_confirmation_dialog.tsx index 3f29d46f8..bb7e3ed1a 100644 --- a/packages/website/ts/components/dialogs/track_token_confirmation_dialog.tsx +++ b/packages/website/ts/components/dialogs/track_token_confirmation_dialog.tsx @@ -82,16 +82,6 @@ export class TrackTokenConfirmationDialog extends React.Component< newTokenEntry.isTracked = true; trackedTokenStorage.addTrackedTokenToUser(this.props.userAddress, this.props.networkId, newTokenEntry); this.props.dispatcher.updateTokenByAddress([newTokenEntry]); - - const [balance, allowance] = await this.props.blockchain.getCurrentUserTokenBalanceAndAllowanceAsync( - token.address, - ); - this.props.dispatcher.updateTokenStateByAddress({ - [token.address]: { - balance, - allowance, - }, - }); } this.setState({ diff --git a/packages/website/ts/components/dropdowns/network_drop_down.tsx b/packages/website/ts/components/dropdowns/network_drop_down.tsx new file mode 100644 index 000000000..28ec28ed5 --- /dev/null +++ b/packages/website/ts/components/dropdowns/network_drop_down.tsx @@ -0,0 +1,40 @@ +import * as _ from 'lodash'; +import DropDownMenu from 'material-ui/DropDownMenu'; +import MenuItem from 'material-ui/MenuItem'; +import * as React from 'react'; +import { constants } from 'ts/utils/constants'; + +interface NetworkDropDownProps { + updateSelectedNetwork: (e: any, index: number, value: number) => void; + selectedNetworkId: number; + avialableNetworkIds: number[]; +} + +interface NetworkDropDownState {} + +export class NetworkDropDown extends React.Component<NetworkDropDownProps, NetworkDropDownState> { + public render() { + return ( + <div className="mx-auto" style={{ width: 120 }}> + <DropDownMenu value={this.props.selectedNetworkId} onChange={this.props.updateSelectedNetwork}> + {this._renderDropDownItems()} + </DropDownMenu> + </div> + ); + } + private _renderDropDownItems() { + const items = _.map(this.props.avialableNetworkIds, networkId => { + const networkName = constants.NETWORK_NAME_BY_ID[networkId]; + const primaryText = ( + <div className="flex"> + <div className="pr1" style={{ width: 14, paddingTop: 2 }}> + <img src={`/images/network_icons/${networkName.toLowerCase()}.png`} style={{ width: 14 }} /> + </div> + <div>{networkName}</div> + </div> + ); + return <MenuItem key={networkId} value={networkId} primaryText={primaryText} />; + }); + return items; + } +} diff --git a/packages/website/ts/components/eth_weth_conversion_button.tsx b/packages/website/ts/components/eth_weth_conversion_button.tsx index 300e71f1f..62bafdba7 100644 --- a/packages/website/ts/components/eth_weth_conversion_button.tsx +++ b/packages/website/ts/components/eth_weth_conversion_button.tsx @@ -6,21 +6,24 @@ import * as React from 'react'; import { Blockchain } from 'ts/blockchain'; import { EthWethConversionDialog } from 'ts/components/dialogs/eth_weth_conversion_dialog'; import { Dispatcher } from 'ts/redux/dispatcher'; -import { BlockchainCallErrs, Side, Token, TokenState } from 'ts/types'; +import { BlockchainCallErrs, Side, Token } from 'ts/types'; import { constants } from 'ts/utils/constants'; import { errorReporter } from 'ts/utils/error_reporter'; import { utils } from 'ts/utils/utils'; interface EthWethConversionButtonProps { + userAddress: string; + networkId: number; direction: Side; ethToken: Token; - ethTokenState: TokenState; dispatcher: Dispatcher; blockchain: Blockchain; userEtherBalance: BigNumber; isOutdatedWrappedEther: boolean; onConversionSuccessful?: () => void; isDisabled?: boolean; + lastForceTokenStateRefetch: number; + refetchEthTokenStateAsync: () => Promise<void>; } interface EthWethConversionButtonState { @@ -64,13 +67,16 @@ export class EthWethConversionButton extends React.Component< onClick={this._toggleConversionDialog.bind(this)} /> <EthWethConversionDialog + blockchain={this.props.blockchain} + userAddress={this.props.userAddress} + networkId={this.props.networkId} direction={this.props.direction} isOpen={this.state.isEthConversionDialogVisible} onComplete={this._onConversionAmountSelectedAsync.bind(this)} onCancelled={this._toggleConversionDialog.bind(this)} etherBalance={this.props.userEtherBalance} token={this.props.ethToken} - tokenState={this.props.ethTokenState} + lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch} /> </div> ); @@ -86,29 +92,25 @@ export class EthWethConversionButton extends React.Component< }); this._toggleConversionDialog(); const token = this.props.ethToken; - const tokenState = this.props.ethTokenState; - let balance = tokenState.balance; try { if (direction === Side.Deposit) { await this.props.blockchain.convertEthToWrappedEthTokensAsync(token.address, value); const ethAmount = ZeroEx.toUnitAmount(value, constants.DECIMAL_PLACES_ETH); this.props.dispatcher.showFlashMessage(`Successfully wrapped ${ethAmount.toString()} ETH to WETH`); - balance = balance.plus(value); } else { await this.props.blockchain.convertWrappedEthTokensToEthAsync(token.address, value); const tokenAmount = ZeroEx.toUnitAmount(value, token.decimals); this.props.dispatcher.showFlashMessage(`Successfully unwrapped ${tokenAmount.toString()} WETH to ETH`); - balance = balance.minus(value); } if (!this.props.isOutdatedWrappedEther) { - this.props.dispatcher.replaceTokenBalanceByAddress(token.address, balance); + await this.props.refetchEthTokenStateAsync(); } this.props.onConversionSuccessful(); } catch (err) { const errMsg = `${err}`; if (_.includes(errMsg, BlockchainCallErrs.UserHasNoAssociatedAddresses)) { this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); - } else if (!_.includes(errMsg, 'User denied transaction')) { + } else if (!utils.didUserDenyWeb3Request(errMsg)) { utils.consoleLog(`Unexpected error encountered: ${err}`); utils.consoleLog(err.stack); const errorMsg = diff --git a/packages/website/ts/components/eth_wrappers.tsx b/packages/website/ts/components/eth_wrappers.tsx index d074ec787..c2cdf6751 100644 --- a/packages/website/ts/components/eth_wrappers.tsx +++ b/packages/website/ts/components/eth_wrappers.tsx @@ -16,7 +16,6 @@ import { Token, TokenByAddress, TokenState, - TokenStateByAddress, } from 'ts/types'; import { colors } from 'ts/utils/colors'; import { configs } from 'ts/utils/configs'; @@ -41,19 +40,23 @@ interface EthWrappersProps { blockchain: Blockchain; dispatcher: Dispatcher; tokenByAddress: TokenByAddress; - tokenStateByAddress: TokenStateByAddress; userAddress: string; userEtherBalance: BigNumber; + lastForceTokenStateRefetch: number; } interface EthWrappersState { + ethTokenState: TokenState; + isWethStateLoaded: boolean; outdatedWETHAddressToIsStateLoaded: OutdatedWETHAddressToIsStateLoaded; outdatedWETHStateByAddress: OutdatedWETHStateByAddress; } export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersState> { + private _isUnmounted: boolean; constructor(props: EthWrappersProps) { super(props); + this._isUnmounted = false; const outdatedWETHAddresses = this._getOutdatedWETHAddresses(); const outdatedWETHAddressToIsStateLoaded: OutdatedWETHAddressToIsStateLoaded = {}; const outdatedWETHStateByAddress: OutdatedWETHStateByAddress = {}; @@ -67,18 +70,34 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt this.state = { outdatedWETHAddressToIsStateLoaded, outdatedWETHStateByAddress, + isWethStateLoaded: false, + ethTokenState: { + balance: new BigNumber(0), + allowance: new BigNumber(0), + }, }; } + public componentWillReceiveProps(nextProps: EthWrappersProps) { + if ( + nextProps.userAddress !== this.props.userAddress || + nextProps.networkId !== this.props.networkId || + nextProps.lastForceTokenStateRefetch !== this.props.lastForceTokenStateRefetch + ) { + // tslint:disable-next-line:no-floating-promises + this._fetchWETHStateAsync(); + } + } public componentDidMount() { window.scrollTo(0, 0); // tslint:disable-next-line:no-floating-promises - this._fetchOutdatedWETHStateAsync(); + this._fetchWETHStateAsync(); + } + public componentWillUnmount() { + this._isUnmounted = true; } public render() { - const tokens = _.values(this.props.tokenByAddress); - const etherToken = _.find(tokens, { symbol: 'WETH' }); - const etherTokenState = this.props.tokenStateByAddress[etherToken.address]; - const wethBalance = ZeroEx.toUnitAmount(etherTokenState.balance, constants.DECIMAL_PLACES_ETH); + const etherToken = this._getEthToken(); + const wethBalance = ZeroEx.toUnitAmount(this.state.ethTokenState.balance, constants.DECIMAL_PLACES_ETH); const isBidirectional = true; const etherscanUrl = utils.getEtherScanLinkIfExists( etherToken.address, @@ -136,10 +155,13 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt </TableRowColumn> <TableRowColumn> <EthWethConversionButton + refetchEthTokenStateAsync={this._refetchEthTokenStateAsync.bind(this)} + lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch} + userAddress={this.props.userAddress} + networkId={this.props.networkId} isOutdatedWrappedEther={false} direction={Side.Deposit} ethToken={etherToken} - ethTokenState={etherTokenState} dispatcher={this.props.dispatcher} blockchain={this.props.blockchain} userEtherBalance={this.props.userEtherBalance} @@ -150,13 +172,23 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt <TableRowColumn className="py1"> {this._renderTokenLink(tokenLabel, etherscanUrl)} </TableRowColumn> - <TableRowColumn>{wethBalance.toFixed(PRECISION)} WETH</TableRowColumn> + <TableRowColumn> + {this.state.isWethStateLoaded ? ( + `${wethBalance.toFixed(PRECISION)} WETH` + ) : ( + <i className="zmdi zmdi-spinner zmdi-hc-spin" /> + )} + </TableRowColumn> <TableRowColumn> <EthWethConversionButton + refetchEthTokenStateAsync={this._refetchEthTokenStateAsync.bind(this)} + lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch} + userAddress={this.props.userAddress} + networkId={this.props.networkId} isOutdatedWrappedEther={false} direction={Side.Receive} + isDisabled={!this.state.isWethStateLoaded} ethToken={etherToken} - ethTokenState={etherTokenState} dispatcher={this.props.dispatcher} blockchain={this.props.blockchain} userEtherBalance={this.props.userEtherBalance} @@ -190,7 +222,7 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt </TableRow> </TableHeader> <TableBody displayRowCheckbox={false}> - {this._renderOutdatedWeths(etherToken, etherTokenState)} + {this._renderOutdatedWeths(etherToken, this.state.ethTokenState)} </TableBody> </Table> </div> @@ -269,11 +301,14 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt </TableRowColumn> <TableRowColumn> <EthWethConversionButton + refetchEthTokenStateAsync={this._refetchEthTokenStateAsync.bind(this)} + lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch} + userAddress={this.props.userAddress} + networkId={this.props.networkId} isDisabled={!isStateLoaded} isOutdatedWrappedEther={true} direction={Side.Receive} ethToken={outdatedEtherToken} - ethTokenState={outdatedEtherTokenState} dispatcher={this.props.dispatcher} blockchain={this.props.blockchain} userEtherBalance={this.props.userEtherBalance} @@ -338,7 +373,14 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt }, }); } - private async _fetchOutdatedWETHStateAsync() { + private async _fetchWETHStateAsync() { + const tokens = _.values(this.props.tokenByAddress); + const wethToken = _.find(tokens, token => token.symbol === 'WETH'); + const [wethBalance, wethAllowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync( + this.props.userAddress, + wethToken.address, + ); + const outdatedWETHAddresses = this._getOutdatedWETHAddresses(); const outdatedWETHAddressToIsStateLoaded: OutdatedWETHAddressToIsStateLoaded = {}; const outdatedWETHStateByAddress: OutdatedWETHStateByAddress = {}; @@ -353,10 +395,17 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt }; outdatedWETHAddressToIsStateLoaded[address] = true; } - this.setState({ - outdatedWETHStateByAddress, - outdatedWETHAddressToIsStateLoaded, - }); + if (!this._isUnmounted) { + this.setState({ + outdatedWETHStateByAddress, + outdatedWETHAddressToIsStateLoaded, + ethTokenState: { + balance: wethBalance, + allowance: wethAllowance, + }, + isWethStateLoaded: true, + }); + } } private _getOutdatedWETHAddresses(): string[] { const outdatedWETHAddresses = _.compact( @@ -371,4 +420,22 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt ); return outdatedWETHAddresses; } + private _getEthToken() { + const tokens = _.values(this.props.tokenByAddress); + const etherToken = _.find(tokens, { symbol: 'WETH' }); + return etherToken; + } + private async _refetchEthTokenStateAsync() { + const etherToken = this._getEthToken(); + const [balance, allowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync( + this.props.userAddress, + etherToken.address, + ); + this.setState({ + ethTokenState: { + balance, + allowance, + }, + }); + } } // tslint:disable:max-file-line-count diff --git a/packages/website/ts/components/fill_order.tsx b/packages/website/ts/components/fill_order.tsx index 1a150e9ee..d0cfd2cf5 100644 --- a/packages/website/ts/components/fill_order.tsx +++ b/packages/website/ts/components/fill_order.tsx @@ -19,7 +19,7 @@ import { VisualOrder } from 'ts/components/visual_order'; import { Dispatcher } from 'ts/redux/dispatcher'; import { orderSchema } from 'ts/schemas/order_schema'; import { SchemaValidator } from 'ts/schemas/validator'; -import { AlertTypes, BlockchainErrs, Order, Token, TokenByAddress, TokenStateByAddress, WebsitePaths } from 'ts/types'; +import { AlertTypes, BlockchainErrs, Order, Token, TokenByAddress, WebsitePaths } from 'ts/types'; import { colors } from 'ts/utils/colors'; import { constants } from 'ts/utils/constants'; import { errorReporter } from 'ts/utils/error_reporter'; @@ -33,9 +33,9 @@ interface FillOrderProps { networkId: number; userAddress: string; tokenByAddress: TokenByAddress; - tokenStateByAddress: TokenStateByAddress; initialOrder: Order; dispatcher: Dispatcher; + lastForceTokenStateRefetch: number; } interface FillOrderState { @@ -59,8 +59,10 @@ interface FillOrderState { export class FillOrder extends React.Component<FillOrderProps, FillOrderState> { private _validator: SchemaValidator; + private _isUnmounted: boolean; constructor(props: FillOrderProps) { super(props); + this._isUnmounted = false; this.state = { globalErrMsg: '', didOrderValidationRun: false, @@ -90,6 +92,9 @@ export class FillOrder extends React.Component<FillOrderProps, FillOrderState> { public componentDidMount() { window.scrollTo(0, 0); } + public componentWillUnmount() { + this._isUnmounted = true; + } public render() { return ( <div className="clearfix lg-px4 md-px4 sm-px2" style={{ minHeight: 600 }}> @@ -185,7 +190,6 @@ export class FillOrder extends React.Component<FillOrderProps, FillOrderState> { symbol: takerToken.symbol, }; const fillToken = this.props.tokenByAddress[takerToken.address]; - const fillTokenState = this.props.tokenStateByAddress[takerToken.address]; const makerTokenAddress = this.state.parsedOrder.maker.token.address; const makerToken = this.props.tokenByAddress[makerTokenAddress]; const makerAssetToken = { @@ -249,14 +253,17 @@ export class FillOrder extends React.Component<FillOrderProps, FillOrderState> { {!isUserMaker && ( <div className="clearfix mx-auto relative" style={{ width: 235, height: 108 }}> <TokenAmountInput + blockchain={this.props.blockchain} + userAddress={this.props.userAddress} + networkId={this.props.networkId} label="Fill amount" onChange={this._onFillAmountChange.bind(this)} shouldShowIncompleteErrs={false} token={fillToken} - tokenState={fillTokenState} amount={fillAssetToken.amount} shouldCheckBalance={true} shouldCheckAllowance={true} + lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch} /> <div className="absolute sm-hide xs-hide" @@ -454,12 +461,14 @@ export class FillOrder extends React.Component<FillOrderProps, FillOrderState> { if (!_.isEmpty(orderJSON)) { orderJSONErrMsg = 'Submitted order JSON is not valid JSON'; } - this.setState({ - didOrderValidationRun: true, - orderJSON, - orderJSONErrMsg, - parsedOrder, - }); + if (!this._isUnmounted) { + this.setState({ + didOrderValidationRun: true, + orderJSON, + orderJSONErrMsg, + parsedOrder, + }); + } return; } @@ -556,11 +565,8 @@ export class FillOrder extends React.Component<FillOrderProps, FillOrderState> { signedOrder, this.props.orderFillAmount, ); - // After fill completes, let's update the token balances - const makerToken = this.props.tokenByAddress[parsedOrder.maker.token.address]; - const takerToken = this.props.tokenByAddress[parsedOrder.taker.token.address]; - const tokens = [makerToken, takerToken]; - await this.props.blockchain.updateTokenBalancesAndAllowancesAsync(tokens); + // After fill completes, let's force fetch the token balances + this.props.dispatcher.forceTokenStateRefetch(); this.setState({ isFilling: false, didFillOrderSucceed: true, @@ -573,7 +579,7 @@ export class FillOrder extends React.Component<FillOrderProps, FillOrderState> { isFilling: false, }); const errMsg = `${err}`; - if (_.includes(errMsg, 'User denied transaction signature')) { + if (utils.didUserDenyWeb3Request(errMsg)) { return; } globalErrMsg = 'Failed to fill order, please refresh and try again'; @@ -653,7 +659,7 @@ export class FillOrder extends React.Component<FillOrderProps, FillOrderState> { isCancelling: false, }); const errMsg = `${err}`; - if (_.includes(errMsg, 'User denied transaction signature')) { + if (utils.didUserDenyWeb3Request(errMsg)) { return; } globalErrMsg = 'Failed to cancel order, please refresh and try again'; diff --git a/packages/website/ts/components/generate_order/asset_picker.tsx b/packages/website/ts/components/generate_order/asset_picker.tsx index df7d87cfd..69fb32a21 100644 --- a/packages/website/ts/components/generate_order/asset_picker.tsx +++ b/packages/website/ts/components/generate_order/asset_picker.tsx @@ -8,7 +8,7 @@ import { TrackTokenConfirmation } from 'ts/components/track_token_confirmation'; import { TokenIcon } from 'ts/components/ui/token_icon'; import { trackedTokenStorage } from 'ts/local_storage/tracked_token_storage'; import { Dispatcher } from 'ts/redux/dispatcher'; -import { DialogConfigs, Token, TokenByAddress, TokenState, TokenVisibility } from 'ts/types'; +import { DialogConfigs, Token, TokenByAddress, TokenVisibility } from 'ts/types'; const TOKEN_ICON_DIMENSION = 100; const TILE_DIMENSION = 146; @@ -223,10 +223,7 @@ export class AssetPicker extends React.Component<AssetPickerProps, AssetPickerSt assetView: AssetViews.NEW_TOKEN_FORM, }); } - private _onNewTokenSubmitted(newToken: Token, newTokenState: TokenState) { - this.props.dispatcher.updateTokenStateByAddress({ - [newToken.address]: newTokenState, - }); + private _onNewTokenSubmitted(newToken: Token) { trackedTokenStorage.addTrackedTokenToUser(this.props.userAddress, this.props.networkId, newToken); this.props.dispatcher.addTokenToTokenByAddress(newToken); this.setState({ @@ -256,15 +253,6 @@ export class AssetPicker extends React.Component<AssetPickerProps, AssetPickerSt newTokenEntry.isTracked = true; trackedTokenStorage.addTrackedTokenToUser(this.props.userAddress, this.props.networkId, newTokenEntry); - const [balance, allowance] = await this.props.blockchain.getCurrentUserTokenBalanceAndAllowanceAsync( - token.address, - ); - this.props.dispatcher.updateTokenStateByAddress({ - [token.address]: { - balance, - allowance, - }, - }); this.props.dispatcher.updateTokenByAddress([newTokenEntry]); this.setState({ isAddingTokenToTracked: false, diff --git a/packages/website/ts/components/generate_order/generate_order_form.tsx b/packages/website/ts/components/generate_order/generate_order_form.tsx index 3ae0d48a7..df1241d8d 100644 --- a/packages/website/ts/components/generate_order/generate_order_form.tsx +++ b/packages/website/ts/components/generate_order/generate_order_form.tsx @@ -27,7 +27,6 @@ import { SignatureData, Token, TokenByAddress, - TokenStateByAddress, } from 'ts/types'; import { colors } from 'ts/utils/colors'; import { errorReporter } from 'ts/utils/error_reporter'; @@ -53,7 +52,7 @@ interface GenerateOrderFormProps { orderSalt: BigNumber; sideToAssetToken: SideToAssetToken; tokenByAddress: TokenByAddress; - tokenStateByAddress: TokenStateByAddress; + lastForceTokenStateRefetch: number; } interface GenerateOrderFormState { @@ -80,10 +79,8 @@ export class GenerateOrderForm extends React.Component<GenerateOrderFormProps, G const dispatcher = this.props.dispatcher; const depositTokenAddress = this.props.sideToAssetToken[Side.Deposit].address; const depositToken = this.props.tokenByAddress[depositTokenAddress]; - const depositTokenState = this.props.tokenStateByAddress[depositTokenAddress]; const receiveTokenAddress = this.props.sideToAssetToken[Side.Receive].address; const receiveToken = this.props.tokenByAddress[receiveTokenAddress]; - const receiveTokenState = this.props.tokenStateByAddress[receiveTokenAddress]; const takerExplanation = 'If a taker is specified, only they are<br> \ allowed to fill this order. If no taker is<br> \ @@ -110,9 +107,12 @@ export class GenerateOrderForm extends React.Component<GenerateOrderFormProps, G tokenByAddress={this.props.tokenByAddress} /> <TokenAmountInput + lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch} + blockchain={this.props.blockchain} + userAddress={this.props.userAddress} + networkId={this.props.networkId} label="Sell amount" token={depositToken} - tokenState={depositTokenState} amount={this.props.sideToAssetToken[Side.Deposit].amount} onChange={this._onTokenAmountChange.bind(this, depositToken, Side.Deposit)} shouldShowIncompleteErrs={this.state.shouldShowIncompleteErrs} @@ -139,9 +139,12 @@ export class GenerateOrderForm extends React.Component<GenerateOrderFormProps, G tokenByAddress={this.props.tokenByAddress} /> <TokenAmountInput + lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch} + blockchain={this.props.blockchain} + userAddress={this.props.userAddress} + networkId={this.props.networkId} label="Receive amount" token={receiveToken} - tokenState={receiveTokenState} amount={this.props.sideToAssetToken[Side.Receive].amount} onChange={this._onTokenAmountChange.bind(this, receiveToken, Side.Receive)} shouldShowIncompleteErrs={this.state.shouldShowIncompleteErrs} @@ -242,8 +245,10 @@ export class GenerateOrderForm extends React.Component<GenerateOrderFormProps, G // Check if all required inputs were supplied const debitToken = this.props.sideToAssetToken[Side.Deposit]; - const debitBalance = this.props.tokenStateByAddress[debitToken.address].balance; - const debitAllowance = this.props.tokenStateByAddress[debitToken.address].allowance; + const [debitBalance, debitAllowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync( + this.props.userAddress, + debitToken.address, + ); const receiveAmount = this.props.sideToAssetToken[Side.Receive].amount; if ( !_.isUndefined(debitToken.amount) && diff --git a/packages/website/ts/components/generate_order/new_token_form.tsx b/packages/website/ts/components/generate_order/new_token_form.tsx index 63645be9a..f76830a49 100644 --- a/packages/website/ts/components/generate_order/new_token_form.tsx +++ b/packages/website/ts/components/generate_order/new_token_form.tsx @@ -1,4 +1,3 @@ -import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; import TextField from 'material-ui/TextField'; import * as React from 'react'; @@ -7,13 +6,13 @@ import { AddressInput } from 'ts/components/inputs/address_input'; import { Alert } from 'ts/components/ui/alert'; import { LifeCycleRaisedButton } from 'ts/components/ui/lifecycle_raised_button'; import { RequiredLabel } from 'ts/components/ui/required_label'; -import { AlertTypes, Token, TokenByAddress, TokenState } from 'ts/types'; +import { AlertTypes, Token, TokenByAddress } from 'ts/types'; import { colors } from 'ts/utils/colors'; interface NewTokenFormProps { blockchain: Blockchain; tokenByAddress: TokenByAddress; - onNewTokenSubmitted: (token: Token, tokenState: TokenState) => void; + onNewTokenSubmitted: (token: Token) => void; } interface NewTokenFormState { @@ -110,13 +109,9 @@ export class NewTokenForm extends React.Component<NewTokenFormProps, NewTokenFor } let hasBalanceAllowanceErr = false; - let balance = new BigNumber(0); - let allowance = new BigNumber(0); if (doesContractExist) { try { - [balance, allowance] = await this.props.blockchain.getCurrentUserTokenBalanceAndAllowanceAsync( - this.state.address, - ); + await this.props.blockchain.getCurrentUserTokenBalanceAndAllowanceAsync(this.state.address); } catch (err) { hasBalanceAllowanceErr = true; } @@ -155,11 +150,7 @@ export class NewTokenForm extends React.Component<NewTokenFormProps, NewTokenFor isTracked: true, isRegistered: false, }; - const newTokenState: TokenState = { - balance, - allowance, - }; - this.props.onNewTokenSubmitted(newToken, newTokenState); + this.props.onNewTokenSubmitted(newToken); } private _onTokenNameChanged(e: any, name: string) { let nameErrText = ''; diff --git a/packages/website/ts/components/inputs/allowance_toggle.tsx b/packages/website/ts/components/inputs/allowance_toggle.tsx index da46db4f4..45531e74b 100644 --- a/packages/website/ts/components/inputs/allowance_toggle.tsx +++ b/packages/website/ts/components/inputs/allowance_toggle.tsx @@ -17,6 +17,8 @@ interface AllowanceToggleProps { token: Token; tokenState: TokenState; userAddress: string; + isDisabled: boolean; + refetchTokenStateAsync: () => Promise<void>; } interface AllowanceToggleState { @@ -45,7 +47,7 @@ export class AllowanceToggle extends React.Component<AllowanceToggleProps, Allow <div className="flex"> <div> <Toggle - disabled={this.state.isSpinnerVisible} + disabled={this.state.isSpinnerVisible || this.props.isDisabled} toggled={this._isAllowanceSet()} onToggle={this._onToggleAllowanceAsync.bind(this)} /> @@ -73,12 +75,13 @@ export class AllowanceToggle extends React.Component<AllowanceToggleProps, Allow } try { await this.props.blockchain.setProxyAllowanceAsync(this.props.token, newAllowanceAmountInBaseUnits); + await this.props.refetchTokenStateAsync(); } catch (err) { this.setState({ isSpinnerVisible: false, }); const errMsg = `${err}`; - if (_.includes(errMsg, 'User denied transaction')) { + if (utils.didUserDenyWeb3Request(errMsg)) { return; } utils.consoleLog(`Unexpected error encountered: ${err}`); diff --git a/packages/website/ts/components/inputs/balance_bounded_input.tsx b/packages/website/ts/components/inputs/balance_bounded_input.tsx index ddc434b51..3bbc7a5f6 100644 --- a/packages/website/ts/components/inputs/balance_bounded_input.tsx +++ b/packages/website/ts/components/inputs/balance_bounded_input.tsx @@ -18,6 +18,7 @@ interface BalanceBoundedInputProps { validate?: (amount: BigNumber) => InputErrMsg; onVisitBalancesPageClick?: () => void; shouldHideVisitBalancesLink?: boolean; + isDisabled?: boolean; } interface BalanceBoundedInputState { @@ -29,6 +30,7 @@ export class BalanceBoundedInput extends React.Component<BalanceBoundedInputProp public static defaultProps: Partial<BalanceBoundedInputProps> = { shouldShowIncompleteErrs: false, shouldHideVisitBalancesLink: false, + isDisabled: false, }; constructor(props: BalanceBoundedInputProps) { super(props); @@ -88,6 +90,7 @@ export class BalanceBoundedInput extends React.Component<BalanceBoundedInputProp hintText={<span style={{ textTransform: 'capitalize' }}>amount</span>} onChange={this._onValueChange.bind(this)} underlineStyle={{ width: 'calc(100% + 50px)' }} + disabled={this.props.isDisabled} /> ); } @@ -100,7 +103,7 @@ export class BalanceBoundedInput extends React.Component<BalanceBoundedInputProp }, () => { const isValid = _.isUndefined(errMsg); - if (utils.isNumeric(amountString)) { + if (utils.isNumeric(amountString) && !_.includes(amountString, '-')) { this.props.onChange(isValid, new BigNumber(amountString)); } else { this.props.onChange(isValid); diff --git a/packages/website/ts/components/inputs/token_amount_input.tsx b/packages/website/ts/components/inputs/token_amount_input.tsx index 63966d759..2b167d875 100644 --- a/packages/website/ts/components/inputs/token_amount_input.tsx +++ b/packages/website/ts/components/inputs/token_amount_input.tsx @@ -3,13 +3,16 @@ import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; import * as React from 'react'; import { Link } from 'react-router-dom'; +import { Blockchain } from 'ts/blockchain'; import { BalanceBoundedInput } from 'ts/components/inputs/balance_bounded_input'; -import { InputErrMsg, Token, TokenState, ValidatedBigNumberCallback, WebsitePaths } from 'ts/types'; +import { InputErrMsg, Token, ValidatedBigNumberCallback, WebsitePaths } from 'ts/types'; import { colors } from 'ts/utils/colors'; interface TokenAmountInputProps { + userAddress: string; + networkId: number; + blockchain: Blockchain; token: Token; - tokenState: TokenState; label?: string; amount?: BigNumber; shouldShowIncompleteErrs: boolean; @@ -17,11 +20,45 @@ interface TokenAmountInputProps { shouldCheckAllowance: boolean; onChange: ValidatedBigNumberCallback; onVisitBalancesPageClick?: () => void; + lastForceTokenStateRefetch: number; } -interface TokenAmountInputState {} +interface TokenAmountInputState { + balance: BigNumber; + allowance: BigNumber; + isBalanceAndAllowanceLoaded: boolean; +} export class TokenAmountInput extends React.Component<TokenAmountInputProps, TokenAmountInputState> { + private _isUnmounted: boolean; + constructor(props: TokenAmountInputProps) { + super(props); + this._isUnmounted = false; + const defaultAmount = new BigNumber(0); + this.state = { + balance: defaultAmount, + allowance: defaultAmount, + isBalanceAndAllowanceLoaded: false, + }; + } + public componentWillMount() { + // tslint:disable-next-line:no-floating-promises + this._fetchBalanceAndAllowanceAsync(this.props.token.address, this.props.userAddress); + } + public componentWillUnmount() { + this._isUnmounted = true; + } + public componentWillReceiveProps(nextProps: TokenAmountInputProps) { + if ( + nextProps.userAddress !== this.props.userAddress || + nextProps.networkId !== this.props.networkId || + nextProps.token.address !== this.props.token.address || + nextProps.lastForceTokenStateRefetch !== this.props.lastForceTokenStateRefetch + ) { + // tslint:disable-next-line:no-floating-promises + this._fetchBalanceAndAllowanceAsync(nextProps.token.address, nextProps.userAddress); + } + } public render() { const amount = this.props.amount ? ZeroEx.toUnitAmount(this.props.amount, this.props.token.decimals) @@ -32,12 +69,13 @@ export class TokenAmountInput extends React.Component<TokenAmountInputProps, Tok <BalanceBoundedInput label={this.props.label} amount={amount} - balance={ZeroEx.toUnitAmount(this.props.tokenState.balance, this.props.token.decimals)} + balance={ZeroEx.toUnitAmount(this.state.balance, this.props.token.decimals)} onChange={this._onChange.bind(this)} validate={this._validate.bind(this)} shouldCheckBalance={this.props.shouldCheckBalance} shouldShowIncompleteErrs={this.props.shouldShowIncompleteErrs} onVisitBalancesPageClick={this.props.onVisitBalancesPageClick} + isDisabled={!this.state.isBalanceAndAllowanceLoaded} /> <div style={{ paddingTop: hasLabel ? 39 : 14 }}>{this.props.token.symbol}</div> </div> @@ -51,7 +89,7 @@ export class TokenAmountInput extends React.Component<TokenAmountInputProps, Tok this.props.onChange(isValid, baseUnitAmount); } private _validate(amount: BigNumber): InputErrMsg { - if (this.props.shouldCheckAllowance && amount.gt(this.props.tokenState.allowance)) { + if (this.props.shouldCheckAllowance && amount.gt(this.state.allowance)) { return ( <span> Insufficient allowance.{' '} @@ -67,4 +105,20 @@ export class TokenAmountInput extends React.Component<TokenAmountInputProps, Tok return undefined; } } + private async _fetchBalanceAndAllowanceAsync(tokenAddress: string, userAddress: string) { + this.setState({ + isBalanceAndAllowanceLoaded: false, + }); + const [balance, allowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync( + userAddress, + tokenAddress, + ); + if (!this._isUnmounted) { + this.setState({ + balance, + allowance, + isBalanceAndAllowanceLoaded: true, + }); + } + } } diff --git a/packages/website/ts/components/portal.tsx b/packages/website/ts/components/portal.tsx index e2e28e8b6..92589f75c 100644 --- a/packages/website/ts/components/portal.tsx +++ b/packages/website/ts/components/portal.tsx @@ -1,11 +1,13 @@ import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; +import CircularProgress from 'material-ui/CircularProgress'; import Paper from 'material-ui/Paper'; import * as React from 'react'; import * as DocumentTitle from 'react-document-title'; import { Route, Switch } from 'react-router-dom'; import { Blockchain } from 'ts/blockchain'; import { BlockchainErrDialog } from 'ts/components/dialogs/blockchain_err_dialog'; +import { LedgerConfigDialog } from 'ts/components/dialogs/ledger_config_dialog'; import { PortalDisclaimerDialog } from 'ts/components/dialogs/portal_disclaimer_dialog'; import { WrappedEthSectionNoticeDialog } from 'ts/components/dialogs/wrapped_eth_section_notice_dialog'; import { EthWrappers } from 'ts/components/eth_wrappers'; @@ -13,25 +15,15 @@ import { FillOrder } from 'ts/components/fill_order'; import { Footer } from 'ts/components/footer'; import { PortalMenu } from 'ts/components/portal_menu'; import { TokenBalances } from 'ts/components/token_balances'; -import { TopBar } from 'ts/components/top_bar'; +import { TopBar } from 'ts/components/top_bar/top_bar'; import { TradeHistory } from 'ts/components/trade_history/trade_history'; import { FlashMessage } from 'ts/components/ui/flash_message'; -import { Loading } from 'ts/components/ui/loading'; import { GenerateOrderForm } from 'ts/containers/generate_order_form'; import { localStorage } from 'ts/local_storage/local_storage'; import { Dispatcher } from 'ts/redux/dispatcher'; import { orderSchema } from 'ts/schemas/order_schema'; import { SchemaValidator } from 'ts/schemas/validator'; -import { - BlockchainErrs, - HashData, - Order, - ScreenWidths, - Token, - TokenByAddress, - TokenStateByAddress, - WebsitePaths, -} from 'ts/types'; +import { BlockchainErrs, HashData, Order, ProviderType, ScreenWidths, TokenByAddress, WebsitePaths } from 'ts/types'; import { colors } from 'ts/utils/colors'; import { configs } from 'ts/utils/configs'; import { constants } from 'ts/utils/constants'; @@ -46,18 +38,20 @@ export interface PortalAllProps { blockchainIsLoaded: boolean; dispatcher: Dispatcher; hashData: HashData; + injectedProviderName: string; networkId: number; nodeVersion: string; orderFillAmount: BigNumber; + providerType: ProviderType; screenWidth: ScreenWidths; tokenByAddress: TokenByAddress; - tokenStateByAddress: TokenStateByAddress; userEtherBalance: BigNumber; userAddress: string; shouldBlockchainErrDialogBeOpen: boolean; userSuppliedOrderCache: Order; location: Location; flashMessage?: string | React.ReactNode; + lastForceTokenStateRefetch: number; } interface PortalAllState { @@ -67,6 +61,7 @@ interface PortalAllState { prevPathname: string; isDisclaimerDialogOpen: boolean; isWethNoticeDialogOpen: boolean; + isLedgerDialogOpen: boolean; } export class Portal extends React.Component<PortalAllProps, PortalAllState> { @@ -96,6 +91,7 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> { prevPathname: this.props.location.pathname, isDisclaimerDialogOpen: !hasAcceptedDisclaimer, isWethNoticeDialogOpen: !hasAlreadyDismissedWethNotice && isViewingBalances, + isLedgerDialogOpen: false, }; } public componentDidMount() { @@ -125,11 +121,6 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> { if (nextProps.userAddress !== this.state.prevUserAddress) { // tslint:disable-next-line:no-floating-promises this._blockchain.userAddressUpdatedFireAndForgetAsync(nextProps.userAddress); - if (!_.isEmpty(nextProps.userAddress) && nextProps.blockchainIsLoaded) { - const tokens = _.values(nextProps.tokenByAddress); - // tslint:disable-next-line:no-floating-promises - this._updateBalanceAndAllowanceWithLoadingScreenAsync(tokens); - } this.setState({ prevUserAddress: nextProps.userAddress, }); @@ -167,8 +158,14 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> { <DocumentTitle title="0x Portal DApp" /> <TopBar userAddress={this.props.userAddress} + networkId={this.props.networkId} + injectedProviderName={this.props.injectedProviderName} + onToggleLedgerDialog={this.onToggleLedgerDialog.bind(this)} + dispatcher={this.props.dispatcher} + providerType={this.props.providerType} blockchainIsLoaded={this.props.blockchainIsLoaded} location={this.props.location} + blockchain={this._blockchain} /> <div id="portal" className="mx-auto max-width-4" style={{ width: '100%' }}> <Paper className="mb3 mt2"> @@ -215,7 +212,19 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> { /> </Switch> ) : ( - <Loading /> + <div className="pt4 sm-px2 sm-pt2 sm-m1" style={{ height: 500 }}> + <div + className="relative sm-px2 sm-pt2 sm-m1" + style={{ height: 122, top: '50%', transform: 'translateY(-50%)' }} + > + <div className="center pb2"> + <CircularProgress size={40} thickness={5} /> + </div> + <div className="center pt2" style={{ paddingBottom: 11 }}> + Loading Portal... + </div> + </div> + </div> )} </div> </div> @@ -239,11 +248,26 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> { onToggleDialog={this._onPortalDisclaimerAccepted.bind(this)} /> <FlashMessage dispatcher={this.props.dispatcher} flashMessage={this.props.flashMessage} /> + {this.props.blockchainIsLoaded && ( + <LedgerConfigDialog + providerType={this.props.providerType} + networkId={this.props.networkId} + blockchain={this._blockchain} + dispatcher={this.props.dispatcher} + toggleDialogFn={this.onToggleLedgerDialog.bind(this)} + isOpen={this.state.isLedgerDialogOpen} + /> + )} </div> - <Footer /> + <Footer />; </div> ); } + public onToggleLedgerDialog() { + this.setState({ + isLedgerDialogOpen: !this.state.isLedgerDialogOpen, + }); + } private _renderEthWrapper() { return ( <EthWrappers @@ -251,9 +275,9 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> { blockchain={this._blockchain} dispatcher={this.props.dispatcher} tokenByAddress={this.props.tokenByAddress} - tokenStateByAddress={this.props.tokenStateByAddress} userAddress={this.props.userAddress} userEtherBalance={this.props.userEtherBalance} + lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch} /> ); } @@ -267,6 +291,8 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> { ); } private _renderTokenBalances() { + const allTokens = _.values(this.props.tokenByAddress); + const trackedTokens = _.filter(allTokens, t => t.isTracked); return ( <TokenBalances blockchain={this._blockchain} @@ -275,10 +301,11 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> { dispatcher={this.props.dispatcher} screenWidth={this.props.screenWidth} tokenByAddress={this.props.tokenByAddress} - tokenStateByAddress={this.props.tokenStateByAddress} + trackedTokens={trackedTokens} userAddress={this.props.userAddress} userEtherBalance={this.props.userEtherBalance} networkId={this.props.networkId} + lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch} /> ); } @@ -296,8 +323,8 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> { networkId={this.props.networkId} userAddress={this.props.userAddress} tokenByAddress={this.props.tokenByAddress} - tokenStateByAddress={this.props.tokenStateByAddress} dispatcher={this.props.dispatcher} + lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch} /> ); } @@ -353,9 +380,4 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> { const newScreenWidth = utils.getScreenWidth(); this.props.dispatcher.updateScreenWidth(newScreenWidth); } - private async _updateBalanceAndAllowanceWithLoadingScreenAsync(tokens: Token[]) { - this.props.dispatcher.updateBlockchainIsLoaded(false); - await this._blockchain.updateTokenBalancesAndAllowancesAsync(tokens); - this.props.dispatcher.updateBlockchainIsLoaded(true); - } } diff --git a/packages/website/ts/components/send_button.tsx b/packages/website/ts/components/send_button.tsx index f94ec346a..ffa165f60 100644 --- a/packages/website/ts/components/send_button.tsx +++ b/packages/website/ts/components/send_button.tsx @@ -5,16 +5,19 @@ import * as React from 'react'; import { Blockchain } from 'ts/blockchain'; import { SendDialog } from 'ts/components/dialogs/send_dialog'; import { Dispatcher } from 'ts/redux/dispatcher'; -import { BlockchainCallErrs, Token, TokenState } from 'ts/types'; +import { BlockchainCallErrs, Token } from 'ts/types'; import { errorReporter } from 'ts/utils/error_reporter'; import { utils } from 'ts/utils/utils'; interface SendButtonProps { + userAddress: string; + networkId: number; token: Token; - tokenState: TokenState; dispatcher: Dispatcher; blockchain: Blockchain; onError: () => void; + lastForceTokenStateRefetch: number; + refetchTokenStateAsync: (tokenAddress: string) => Promise<void>; } interface SendButtonState { @@ -42,11 +45,14 @@ export class SendButton extends React.Component<SendButtonProps, SendButtonState onClick={this._toggleSendDialog.bind(this)} /> <SendDialog + blockchain={this.props.blockchain} + userAddress={this.props.userAddress} + networkId={this.props.networkId} isOpen={this.state.isSendDialogVisible} onComplete={this._onSendAmountSelectedAsync.bind(this)} onCancelled={this._toggleSendDialog.bind(this)} token={this.props.token} - tokenState={this.props.tokenState} + lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch} /> </div> ); @@ -62,18 +68,15 @@ export class SendButton extends React.Component<SendButtonProps, SendButtonState }); this._toggleSendDialog(); const token = this.props.token; - const tokenState = this.props.tokenState; - let balance = tokenState.balance; try { await this.props.blockchain.transferAsync(token, recipient, value); - balance = balance.minus(value); - this.props.dispatcher.replaceTokenBalanceByAddress(token.address, balance); + await this.props.refetchTokenStateAsync(token.address); } catch (err) { const errMsg = `${err}`; if (_.includes(errMsg, BlockchainCallErrs.UserHasNoAssociatedAddresses)) { this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); return; - } else if (!_.includes(errMsg, 'User denied transaction')) { + } else if (!utils.didUserDenyWeb3Request(errMsg)) { utils.consoleLog(`Unexpected error encountered: ${err}`); utils.consoleLog(err.stack); this.props.onError(); diff --git a/packages/website/ts/components/token_balances.tsx b/packages/website/ts/components/token_balances.tsx index 2cef413c7..c6a9a46be 100644 --- a/packages/website/ts/components/token_balances.tsx +++ b/packages/website/ts/components/token_balances.tsx @@ -27,11 +27,11 @@ import { BlockchainCallErrs, BlockchainErrs, EtherscanLinkSuffixes, + Networks, ScreenWidths, Styles, Token, TokenByAddress, - TokenStateByAddress, TokenVisibility, } from 'ts/types'; import { colors } from 'ts/utils/colors'; @@ -58,6 +58,14 @@ const styles: Styles = { }, }; +interface TokenStateByAddress { + [address: string]: { + balance: BigNumber; + allowance: BigNumber; + isLoaded: boolean; + }; +} + interface TokenBalancesProps { blockchain: Blockchain; blockchainErr: BlockchainErrs; @@ -65,10 +73,11 @@ interface TokenBalancesProps { dispatcher: Dispatcher; screenWidth: ScreenWidths; tokenByAddress: TokenByAddress; - tokenStateByAddress: TokenStateByAddress; + trackedTokens: Token[]; userAddress: string; userEtherBalance: BigNumber; networkId: number; + lastForceTokenStateRefetch: number; } interface TokenBalancesState { @@ -76,14 +85,17 @@ interface TokenBalancesState { isBalanceSpinnerVisible: boolean; isDharmaDialogVisible: boolean; isZRXSpinnerVisible: boolean; - currentZrxBalance?: BigNumber; isTokenPickerOpen: boolean; isAddingToken: boolean; + trackedTokenStateByAddress: TokenStateByAddress; } export class TokenBalances extends React.Component<TokenBalancesProps, TokenBalancesState> { + private _isUnmounted: boolean; public constructor(props: TokenBalancesProps) { super(props); + this._isUnmounted = false; + const initialTrackedTokenStateByAddress = this._getInitialTrackedTokenStateByAddress(props.trackedTokens); this.state = { errorType: undefined, isBalanceSpinnerVisible: false, @@ -91,8 +103,17 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala isDharmaDialogVisible: DharmaLoanFrame.isAuthTokenPresent(), isTokenPickerOpen: false, isAddingToken: false, + trackedTokenStateByAddress: initialTrackedTokenStateByAddress, }; } + public componentWillMount() { + const trackedTokenAddresses = _.keys(this.state.trackedTokenStateByAddress); + // tslint:disable-next-line:no-floating-promises + this._fetchBalancesAndAllowancesAsync(trackedTokenAddresses); + } + public componentWillUnmount() { + this._isUnmounted = true; + } public componentWillReceiveProps(nextProps: TokenBalancesProps) { if (nextProps.userEtherBalance !== this.props.userEtherBalance) { if (this.state.isBalanceSpinnerVisible) { @@ -103,18 +124,36 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala isBalanceSpinnerVisible: false, }); } - const nextZrxToken = _.find(_.values(nextProps.tokenByAddress), t => t.symbol === ZRX_TOKEN_SYMBOL); - const nextZrxTokenBalance = nextProps.tokenStateByAddress[nextZrxToken.address].balance; - if (!_.isUndefined(this.state.currentZrxBalance) && !nextZrxTokenBalance.eq(this.state.currentZrxBalance)) { - if (this.state.isZRXSpinnerVisible) { - const receivedAmount = nextZrxTokenBalance.minus(this.state.currentZrxBalance); - const receiveAmountInUnits = ZeroEx.toUnitAmount(receivedAmount, constants.DECIMAL_PLACES_ZRX); - this.props.dispatcher.showFlashMessage(`Received ${receiveAmountInUnits.toString(10)} Kovan ZRX`); + + if ( + nextProps.userAddress !== this.props.userAddress || + nextProps.networkId !== this.props.networkId || + nextProps.lastForceTokenStateRefetch !== this.props.lastForceTokenStateRefetch + ) { + const trackedTokenAddresses = _.keys(this.state.trackedTokenStateByAddress); + // tslint:disable-next-line:no-floating-promises + this._fetchBalancesAndAllowancesAsync(trackedTokenAddresses); + } + + if (!_.isEqual(nextProps.trackedTokens, this.props.trackedTokens)) { + const newTokens = _.difference(nextProps.trackedTokens, this.props.trackedTokens); + const newTokenAddresses = _.map(newTokens, token => token.address); + // Add placeholder entry for this token to the state, since fetching the + // balance/allowance is asynchronous + const trackedTokenStateByAddress = this.state.trackedTokenStateByAddress; + for (const tokenAddress of newTokenAddresses) { + trackedTokenStateByAddress[tokenAddress] = { + balance: new BigNumber(0), + allowance: new BigNumber(0), + isLoaded: false, + }; } this.setState({ - isZRXSpinnerVisible: false, - currentZrxBalance: undefined, + trackedTokenStateByAddress, }); + // Fetch the actual balance/allowance. + // tslint:disable-next-line:no-floating-promises + this._fetchBalancesAndAllowancesAsync(newTokenAddresses); } } public componentDidMount() { @@ -137,13 +176,13 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala onTouchTap={this._onDharmaDialogToggle.bind(this, false)} />, ]; - const isTestNetwork = this.props.networkId === constants.NETWORK_ID_TESTNET; + const isKovanTestNetwork = this.props.networkId === constants.NETWORK_ID_KOVAN; const dharmaButtonColumnStyle = { paddingLeft: 3, - display: isTestNetwork ? 'table-cell' : 'none', + display: isKovanTestNetwork ? 'table-cell' : 'none', }; const stubColumnStyle = { - display: isTestNetwork ? 'none' : 'table-cell', + display: isKovanTestNetwork ? 'none' : 'table-cell', }; const allTokenRowHeight = _.size(this.props.tokenByAddress) * TOKEN_TABLE_ROW_HEIGHT; const tokenTableHeight = @@ -162,10 +201,10 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala smart contract so you can start trading that token.'; return ( <div className="lg-px4 md-px4 sm-px1 pb2"> - <h3>{isTestNetwork ? 'Test ether' : 'Ether'}</h3> + <h3>{isKovanTestNetwork ? 'Test ether' : 'Ether'}</h3> <Divider /> <div className="pt2 pb2"> - {isTestNetwork + {isKovanTestNetwork ? 'In order to try out the 0x Portal Dapp, request some test ether to pay for \ gas costs. It might take a bit of time for the test ether to show up.' : 'Ether must be converted to Ether Tokens in order to be tradable via 0x. \ @@ -177,12 +216,12 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala <TableHeaderColumn>Currency</TableHeaderColumn> <TableHeaderColumn>Balance</TableHeaderColumn> <TableRowColumn className="sm-hide xs-hide" style={stubColumnStyle} /> - {isTestNetwork && ( + {isKovanTestNetwork && ( <TableHeaderColumn style={{ paddingLeft: 3 }}> {isSmallScreen ? 'Faucet' : 'Request from faucet'} </TableHeaderColumn> )} - {isTestNetwork && ( + {isKovanTestNetwork && ( <TableHeaderColumn style={dharmaButtonColumnStyle}> {isSmallScreen ? 'Loan' : 'Request Dharma loan'} <HelpTooltip style={{ paddingLeft: 4 }} explanation={dharmaLoanExplanation} /> @@ -204,7 +243,7 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala )} </TableRowColumn> <TableRowColumn className="sm-hide xs-hide" style={stubColumnStyle} /> - {isTestNetwork && ( + {isKovanTestNetwork && ( <TableRowColumn style={{ paddingLeft: 3 }}> <LifeCycleRaisedButton labelReady="Request" @@ -214,7 +253,7 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala /> </TableRowColumn> )} - {isTestNetwork && ( + {isKovanTestNetwork && ( <TableRowColumn style={dharmaButtonColumnStyle}> <RaisedButton label="Request" @@ -228,7 +267,7 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala </Table> <div className="clearfix" style={{ paddingBottom: 1 }}> <div className="col col-10"> - <h3 className="pt2">{isTestNetwork ? 'Test tokens' : 'Tokens'}</h3> + <h3 className="pt2">{isKovanTestNetwork ? 'Test tokens' : 'Tokens'}</h3> </div> <div className="col col-1 pt3 align-right"> <FloatingActionButton mini={true} zDepth={0} onClick={this._onAddTokenClicked.bind(this)}> @@ -243,7 +282,7 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala </div> <Divider /> <div className="pt2 pb2"> - {isTestNetwork + {isKovanTestNetwork ? "Mint some test tokens you'd like to use to generate or fill an order using 0x." : "Set trading permissions for a token you'd like to start trading."} </div> @@ -303,8 +342,7 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala const isSmallScreen = this.props.screenWidth === ScreenWidths.Sm; const tokenColSpan = isSmallScreen ? TOKEN_COL_SPAN_SM : TOKEN_COL_SPAN_LG; const actionPaddingX = isSmallScreen ? 2 : 24; - const allTokens = _.values(this.props.tokenByAddress); - const trackedTokens = _.filter(allTokens, t => t.isTracked); + const trackedTokens = this.props.trackedTokens; const trackedTokensStartingWithEtherToken = trackedTokens.sort( firstBy((t: Token) => t.symbol !== ETHER_TOKEN_SYMBOL) .thenBy((t: Token) => t.symbol !== ZRX_TOKEN_SYMBOL) @@ -317,7 +355,7 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala return tableRows; } private _renderTokenRow(tokenColSpan: number, actionPaddingX: number, token: Token) { - const tokenState = this.props.tokenStateByAddress[token.address]; + const tokenState = this.state.trackedTokenStateByAddress[token.address]; const tokenLink = utils.getEtherScanLinkIfExists( token.address, this.props.networkId, @@ -338,13 +376,19 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala )} </TableRowColumn> <TableRowColumn style={{ paddingRight: 3, paddingLeft: 3 }}> - {this._renderAmount(tokenState.balance, token.decimals)} {token.symbol} - {this.state.isZRXSpinnerVisible && - token.symbol === ZRX_TOKEN_SYMBOL && ( - <span className="pl1"> - <i className="zmdi zmdi-spinner zmdi-hc-spin" /> - </span> - )} + {tokenState.isLoaded ? ( + <span> + {this._renderAmount(tokenState.balance, token.decimals)} {token.symbol} + {this.state.isZRXSpinnerVisible && + token.symbol === ZRX_TOKEN_SYMBOL && ( + <span className="pl1"> + <i className="zmdi zmdi-spinner zmdi-hc-spin" /> + </span> + )} + </span> + ) : ( + <i className="zmdi zmdi-spinner zmdi-hc-spin" /> + )} </TableRowColumn> <TableRowColumn> <AllowanceToggle @@ -354,6 +398,8 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala tokenState={tokenState} onErrorOccurred={this._onErrorOccurred.bind(this)} userAddress={this.props.userAddress} + isDisabled={!tokenState.isLoaded} + refetchTokenStateAsync={this._refetchTokenStateAsync.bind(this, token.address)} /> </TableRowColumn> <TableRowColumn style={{ paddingLeft: actionPaddingX, paddingRight: actionPaddingX }}> @@ -366,7 +412,7 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala /> )} {token.symbol === ZRX_TOKEN_SYMBOL && - this.props.networkId === constants.NETWORK_ID_TESTNET && ( + this.props.networkId === constants.NETWORK_ID_KOVAN && ( <LifeCycleRaisedButton labelReady="Request" labelLoading="Sending..." @@ -383,11 +429,14 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala }} > <SendButton + userAddress={this.props.userAddress} + networkId={this.props.networkId} blockchain={this.props.blockchain} dispatcher={this.props.dispatcher} token={token} - tokenState={tokenState} onError={this._onSendFailed.bind(this)} + lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch} + refetchTokenStateAsync={this._refetchTokenStateAsync.bind(this, token.address)} /> </TableRowColumn> )} @@ -414,7 +463,6 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala } else { this.props.dispatcher.removeTokenToTokenByAddress(token); } - this.props.dispatcher.removeFromTokenStateByAddress(tokenAddress); trackedTokenStorage.removeTrackedToken(this.props.userAddress, this.props.networkId, tokenAddress); } else if (isDefaultTrackedToken) { this.props.dispatcher.showFlashMessage(`Cannot remove ${token.name} because it's a default token`); @@ -449,9 +497,9 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala case BalanceErrs.incorrectNetworkForFaucet: return ( <div> - Our faucet can only send test Ether to addresses on the {constants.TESTNET_NAME} testnet - (networkId {constants.NETWORK_ID_TESTNET}). Please make sure you are connected to the{' '} - {constants.TESTNET_NAME} testnet and try requesting ether again. + Our faucet can only send test Ether to addresses on the {Networks.Kovan} testnet (networkId{' '} + {constants.NETWORK_ID_KOVAN}). Please make sure you are connected to the {Networks.Kovan}{' '} + testnet and try requesting ether again. </div> ); @@ -510,6 +558,7 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala private async _onMintTestTokensAsync(token: Token): Promise<boolean> { try { await this.props.blockchain.mintTestTokensAsync(token); + await this._refetchTokenStateAsync(token.address); const amount = ZeroEx.toUnitAmount(constants.MINT_AMOUNT, token.decimals); this.props.dispatcher.showFlashMessage(`Successfully minted ${amount.toString(10)} ${token.symbol}`); return true; @@ -519,7 +568,7 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); return false; } - if (_.includes(errMsg, 'User denied transaction')) { + if (utils.didUserDenyWeb3Request(errMsg)) { return false; } utils.consoleLog(`Unexpected error encountered: ${err}`); @@ -539,7 +588,7 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala // If on another network other then the testnet our faucet serves test ether // from, we must show user an error message - if (this.props.blockchain.networkId !== constants.NETWORK_ID_TESTNET) { + if (this.props.blockchain.networkId !== constants.NETWORK_ID_KOVAN) { this.setState({ errorType: BalanceErrs.incorrectNetworkForFaucet, }); @@ -569,15 +618,11 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala isBalanceSpinnerVisible: true, }); } else { - const tokens = _.values(this.props.tokenByAddress); - const zrxToken = _.find(tokens, t => t.symbol === ZRX_TOKEN_SYMBOL); - const zrxTokenState = this.props.tokenStateByAddress[zrxToken.address]; this.setState({ isZRXSpinnerVisible: true, - currentZrxBalance: zrxTokenState.balance, }); // tslint:disable-next-line:no-floating-promises - this.props.blockchain.pollTokenBalanceAsync(zrxToken); + this._startPollingZrxBalanceAsync(); } return true; } @@ -603,4 +648,65 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala isAddingToken: false, }); } + private async _startPollingZrxBalanceAsync() { + const tokens = _.values(this.props.tokenByAddress); + const zrxToken = _.find(tokens, t => t.symbol === ZRX_TOKEN_SYMBOL); + + // tslint:disable-next-line:no-floating-promises + const balance = await this.props.blockchain.pollTokenBalanceAsync(zrxToken); + const trackedTokenStateByAddress = this.state.trackedTokenStateByAddress; + trackedTokenStateByAddress[zrxToken.address] = { + ...trackedTokenStateByAddress[zrxToken.address], + balance, + }; + this.setState({ + isZRXSpinnerVisible: false, + }); + } + private async _fetchBalancesAndAllowancesAsync(tokenAddresses: string[]) { + const trackedTokenStateByAddress = this.state.trackedTokenStateByAddress; + for (const tokenAddress of tokenAddresses) { + const [balance, allowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync( + this.props.userAddress, + tokenAddress, + ); + trackedTokenStateByAddress[tokenAddress] = { + balance, + allowance, + isLoaded: true, + }; + } + if (!this._isUnmounted) { + this.setState({ + trackedTokenStateByAddress, + }); + } + } + private _getInitialTrackedTokenStateByAddress(trackedTokens: Token[]) { + const trackedTokenStateByAddress: TokenStateByAddress = {}; + _.each(trackedTokens, token => { + trackedTokenStateByAddress[token.address] = { + balance: new BigNumber(0), + allowance: new BigNumber(0), + isLoaded: false, + }; + }); + return trackedTokenStateByAddress; + } + private async _refetchTokenStateAsync(tokenAddress: string) { + const [balance, allowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync( + this.props.userAddress, + tokenAddress, + ); + this.setState({ + trackedTokenStateByAddress: { + ...this.state.trackedTokenStateByAddress, + [tokenAddress]: { + balance, + allowance, + isLoaded: true, + }, + }, + }); + } } // tslint:disable:max-file-line-count diff --git a/packages/website/ts/components/top_bar/provider_display.tsx b/packages/website/ts/components/top_bar/provider_display.tsx new file mode 100644 index 000000000..39e7f2a8c --- /dev/null +++ b/packages/website/ts/components/top_bar/provider_display.tsx @@ -0,0 +1,148 @@ +import * as _ from 'lodash'; +import RaisedButton from 'material-ui/RaisedButton'; +import * as React from 'react'; +import { Blockchain } from 'ts/blockchain'; +import { ProviderPicker } from 'ts/components/top_bar/provider_picker'; +import { DropDown } from 'ts/components/ui/drop_down'; +import { Identicon } from 'ts/components/ui/identicon'; +import { Dispatcher } from 'ts/redux/dispatcher'; +import { ProviderType } from 'ts/types'; +import { colors } from 'ts/utils/colors'; +import { constants } from 'ts/utils/constants'; +import { utils } from 'ts/utils/utils'; + +const IDENTICON_DIAMETER = 32; + +interface ProviderDisplayProps { + dispatcher: Dispatcher; + userAddress: string; + networkId: number; + injectedProviderName: string; + providerType: ProviderType; + onToggleLedgerDialog: () => void; + blockchain: Blockchain; +} + +interface ProviderDisplayState {} + +export class ProviderDisplay extends React.Component<ProviderDisplayProps, ProviderDisplayState> { + public render() { + const isAddressAvailable = !_.isEmpty(this.props.userAddress); + const isExternallyInjectedProvider = ProviderType.Injected && this.props.injectedProviderName !== '0x Public'; + const displayAddress = isAddressAvailable + ? utils.getAddressBeginAndEnd(this.props.userAddress) + : isExternallyInjectedProvider ? 'Account locked' : '0x0000...0000'; + // If the "injected" provider is our fallback public node, then we want to + // show the "connect a wallet" message instead of the providerName + const injectedProviderName = isExternallyInjectedProvider + ? this.props.injectedProviderName + : 'Connect a wallet'; + const providerTitle = + this.props.providerType === ProviderType.Injected ? injectedProviderName : 'Ledger Nano S'; + const hoverActiveNode = ( + <div className="flex right lg-pr0 md-pr2 sm-pr2" style={{ paddingTop: 16 }}> + <div> + <Identicon address={this.props.userAddress} diameter={IDENTICON_DIAMETER} /> + </div> + <div style={{ marginLeft: 12, paddingTop: 1 }}> + <div style={{ fontSize: 12, color: colors.amber800 }}>{providerTitle}</div> + <div style={{ fontSize: 14 }}>{displayAddress}</div> + </div> + <div + style={{ borderLeft: `1px solid ${colors.grey300}`, marginLeft: 17, paddingTop: 1 }} + className="px2" + > + <i style={{ fontSize: 30, color: colors.grey300 }} className="zmdi zmdi zmdi-chevron-down" /> + </div> + </div> + ); + const hasInjectedProvider = + this.props.injectedProviderName !== '0x Public' && this.props.providerType === ProviderType.Injected; + const hasLedgerProvider = this.props.providerType === ProviderType.Ledger; + const horizontalPosition = hasInjectedProvider || hasLedgerProvider ? 'left' : 'middle'; + return ( + <div style={{ width: 'fit-content', height: 48, float: 'right' }}> + <DropDown + hoverActiveNode={hoverActiveNode} + popoverContent={this.renderPopoverContent(hasInjectedProvider, hasLedgerProvider)} + anchorOrigin={{ horizontal: horizontalPosition, vertical: 'bottom' }} + targetOrigin={{ horizontal: horizontalPosition, vertical: 'top' }} + zDepth={1} + /> + </div> + ); + } + public renderPopoverContent(hasInjectedProvider: boolean, hasLedgerProvider: boolean) { + if (hasInjectedProvider || hasLedgerProvider) { + return ( + <ProviderPicker + dispatcher={this.props.dispatcher} + networkId={this.props.networkId} + injectedProviderName={this.props.injectedProviderName} + providerType={this.props.providerType} + onToggleLedgerDialog={this.props.onToggleLedgerDialog} + blockchain={this.props.blockchain} + /> + ); + } else { + // Nothing to connect to, show install/info popover + return ( + <div className="px2" style={{ maxWidth: 420 }}> + <div className="center h4 py2" style={{ color: colors.grey700 }}> + Choose a wallet: + </div> + <div className="flex pb3"> + <div className="center px2"> + <div style={{ color: colors.darkGrey }}>Install a browser wallet</div> + <div className="py2"> + <img src="/images/metamask_or_parity.png" width="135" /> + </div> + <div> + Use{' '} + <a + href={constants.URL_METAMASK_CHROME_STORE} + target="_blank" + style={{ color: colors.lightBlueA700 }} + > + Metamask + </a>{' '} + or{' '} + <a + href={constants.URL_PARITY_CHROME_STORE} + target="_blank" + style={{ color: colors.lightBlueA700 }} + > + Parity Signer + </a> + </div> + </div> + <div> + <div + className="pl1 ml1" + style={{ borderLeft: `1px solid ${colors.grey300}`, height: 65 }} + /> + <div className="py1">or</div> + <div + className="pl1 ml1" + style={{ borderLeft: `1px solid ${colors.grey300}`, height: 68 }} + /> + </div> + <div className="px2 center"> + <div style={{ color: colors.darkGrey }}>Connect to a ledger hardware wallet</div> + <div style={{ paddingTop: 21, paddingBottom: 29 }}> + <img src="/images/ledger_icon.png" style={{ width: 80 }} /> + </div> + <div> + <RaisedButton + style={{ width: '100%' }} + label="Use Ledger" + onClick={this.props.onToggleLedgerDialog} + /> + </div> + </div> + </div> + </div> + ); + } + } +} diff --git a/packages/website/ts/components/top_bar/provider_picker.tsx b/packages/website/ts/components/top_bar/provider_picker.tsx new file mode 100644 index 000000000..be7e57d6f --- /dev/null +++ b/packages/website/ts/components/top_bar/provider_picker.tsx @@ -0,0 +1,81 @@ +import * as _ from 'lodash'; +import { RadioButton, RadioButtonGroup } from 'material-ui/RadioButton'; +import * as React from 'react'; +import { Blockchain } from 'ts/blockchain'; +import { Dispatcher } from 'ts/redux/dispatcher'; +import { ProviderType } from 'ts/types'; +import { colors } from 'ts/utils/colors'; +import { constants } from 'ts/utils/constants'; + +interface ProviderPickerProps { + networkId: number; + injectedProviderName: string; + providerType: ProviderType; + onToggleLedgerDialog: () => void; + dispatcher: Dispatcher; + blockchain: Blockchain; +} + +interface ProviderPickerState {} + +export class ProviderPicker extends React.Component<ProviderPickerProps, ProviderPickerState> { + public render() { + const isLedgerSelected = this.props.providerType === ProviderType.Ledger; + const menuStyle = { + padding: 10, + paddingTop: 15, + paddingBottom: 15, + }; + // Show dropdown with two options + return ( + <div style={{ width: 225, overflow: 'hidden' }}> + <RadioButtonGroup name="provider" defaultSelected={this.props.providerType}> + <RadioButton + onClick={this._onProviderRadioChanged.bind(this, ProviderType.Injected)} + style={{ ...menuStyle, backgroundColor: !isLedgerSelected && colors.grey50 }} + value={ProviderType.Injected} + label={this._renderLabel(this.props.injectedProviderName, !isLedgerSelected)} + /> + <RadioButton + onClick={this._onProviderRadioChanged.bind(this, ProviderType.Ledger)} + style={{ ...menuStyle, backgroundColor: isLedgerSelected && colors.grey50 }} + value={ProviderType.Ledger} + label={this._renderLabel('Ledger Nano S', isLedgerSelected)} + /> + </RadioButtonGroup> + </div> + ); + } + private _renderLabel(title: string, shouldShowNetwork: boolean) { + const label = ( + <div className="flex"> + <div style={{ fontSize: 14 }}>{title}</div> + {shouldShowNetwork && this._renderNetwork()} + </div> + ); + return label; + } + private _renderNetwork() { + const networkName = constants.NETWORK_NAME_BY_ID[this.props.networkId]; + return ( + <div className="flex" style={{ marginTop: 1 }}> + <div className="relative" style={{ width: 14, paddingLeft: 14 }}> + <img + src={`/images/network_icons/${networkName.toLowerCase()}.png`} + className="absolute" + style={{ top: 6, width: 10 }} + /> + </div> + <div style={{ color: colors.lightGrey, fontSize: 11 }}>{networkName}</div> + </div> + ); + } + private _onProviderRadioChanged(value: string) { + if (value === ProviderType.Ledger) { + this.props.onToggleLedgerDialog(); + } else { + // tslint:disable-next-line:no-floating-promises + this.props.blockchain.updateProviderToInjectedAsync(); + } + } +} diff --git a/packages/website/ts/components/top_bar.tsx b/packages/website/ts/components/top_bar/top_bar.tsx index 11d3e7cc2..1a0691e83 100644 --- a/packages/website/ts/components/top_bar.tsx +++ b/packages/website/ts/components/top_bar/top_bar.tsx @@ -1,21 +1,31 @@ import * as _ from 'lodash'; import Drawer from 'material-ui/Drawer'; +import Menu from 'material-ui/Menu'; import MenuItem from 'material-ui/MenuItem'; import * as React from 'react'; import { Link } from 'react-router-dom'; import ReactTooltip = require('react-tooltip'); +import { Blockchain } from 'ts/blockchain'; import { PortalMenu } from 'ts/components/portal_menu'; -import { TopBarMenuItem } from 'ts/components/top_bar_menu_item'; -import { DropDownMenuItem } from 'ts/components/ui/drop_down_menu_item'; +import { ProviderDisplay } from 'ts/components/top_bar/provider_display'; +import { TopBarMenuItem } from 'ts/components/top_bar/top_bar_menu_item'; +import { DropDown } from 'ts/components/ui/drop_down'; import { Identicon } from 'ts/components/ui/identicon'; import { DocsInfo } from 'ts/pages/documentation/docs_info'; import { NestedSidebarMenu } from 'ts/pages/shared/nested_sidebar_menu'; -import { DocsMenu, MenuSubsectionsBySection, Styles, WebsitePaths } from 'ts/types'; +import { Dispatcher } from 'ts/redux/dispatcher'; +import { DocsMenu, MenuSubsectionsBySection, ProviderType, Styles, WebsitePaths } from 'ts/types'; import { colors } from 'ts/utils/colors'; import { constants } from 'ts/utils/constants'; interface TopBarProps { userAddress?: string; + networkId?: number; + injectedProviderName?: string; + providerType?: ProviderType; + onToggleLedgerDialog?: () => void; + blockchain?: Blockchain; + dispatcher?: Dispatcher; blockchainIsLoaded: boolean; location: Location; docsVersion?: string; @@ -125,6 +135,15 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> { cursor: 'pointer', paddingTop: 16, }; + const hoverActiveNode = ( + <div className="flex relative" style={{ color: menuIconStyle.color }}> + <div style={{ paddingRight: 10 }}>Developers</div> + <div className="absolute" style={{ paddingLeft: 3, right: 3, top: -2 }}> + <i className="zmdi zmdi-caret-right" style={{ fontSize: 22 }} /> + </div> + </div> + ); + const popoverContent = <Menu style={{ color: colors.darkGrey }}>{developerSectionMenuItems}</Menu>; return ( <div style={{ ...styles.topBar, ...bottomBorderStyle, ...this.props.style }} className="pb1"> <div className={parentClassNames}> @@ -138,11 +157,12 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> { {!this._isViewingPortal() && ( <div className={menuClasses}> <div className="flex justify-between"> - <DropDownMenuItem - title="Developers" - subMenuItems={developerSectionMenuItems} + <DropDown + hoverActiveNode={hoverActiveNode} + popoverContent={popoverContent} + anchorOrigin={{ horizontal: 'middle', vertical: 'bottom' }} + targetOrigin={{ horizontal: 'middle', vertical: 'top' }} style={styles.menuItem} - isNightVersion={isNightVersion} /> <TopBarMenuItem title="Wiki" @@ -167,10 +187,19 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> { </div> </div> )} - {this.props.blockchainIsLoaded && - !_.isEmpty(this.props.userAddress) && ( - <div className="col col-5 sm-hide xs-hide">{this._renderUser()}</div> - )} + {this.props.blockchainIsLoaded && ( + <div className="sm-hide xs-hide col col-5"> + <ProviderDisplay + dispatcher={this.props.dispatcher} + userAddress={this.props.userAddress} + networkId={this.props.networkId} + injectedProviderName={this.props.injectedProviderName} + providerType={this.props.providerType} + onToggleLedgerDialog={this.props.onToggleLedgerDialog} + blockchain={this.props.blockchain} + /> + </div> + )} <div className={`col ${isFullWidthPage ? 'col-2 pl2' : 'col-1'} md-hide lg-hide`}> <div style={menuIconStyle}> <i className="zmdi zmdi-menu" onClick={this._onMenuButtonClick.bind(this)} /> diff --git a/packages/website/ts/components/top_bar_menu_item.tsx b/packages/website/ts/components/top_bar/top_bar_menu_item.tsx index 96ee86142..96ee86142 100644 --- a/packages/website/ts/components/top_bar_menu_item.tsx +++ b/packages/website/ts/components/top_bar/top_bar_menu_item.tsx diff --git a/packages/website/ts/components/ui/drop_down_menu_item.tsx b/packages/website/ts/components/ui/drop_down.tsx index a578fb4f9..63b9eec0b 100644 --- a/packages/website/ts/components/ui/drop_down_menu_item.tsx +++ b/packages/website/ts/components/ui/drop_down.tsx @@ -1,36 +1,35 @@ import * as _ from 'lodash'; -import Menu from 'material-ui/Menu'; -import Popover from 'material-ui/Popover'; +import Popover, { PopoverAnimationVertical } from 'material-ui/Popover'; import * as React from 'react'; -import { colors } from 'ts/utils/colors'; +import { MaterialUIPosition } from 'ts/types'; const CHECK_CLOSE_POPOVER_INTERVAL_MS = 300; const DEFAULT_STYLE = { fontSize: 14, }; -interface DropDownMenuItemProps { - title: string; - subMenuItems: React.ReactNode[]; +interface DropDownProps { + hoverActiveNode: React.ReactNode; + popoverContent: React.ReactNode; + anchorOrigin: MaterialUIPosition; + targetOrigin: MaterialUIPosition; style?: React.CSSProperties; - menuItemStyle?: React.CSSProperties; - isNightVersion?: boolean; + zDepth?: number; } -interface DropDownMenuItemState { +interface DropDownState { isDropDownOpen: boolean; anchorEl?: HTMLInputElement; } -export class DropDownMenuItem extends React.Component<DropDownMenuItemProps, DropDownMenuItemState> { - public static defaultProps: Partial<DropDownMenuItemProps> = { +export class DropDown extends React.Component<DropDownProps, DropDownState> { + public static defaultProps: Partial<DropDownProps> = { style: DEFAULT_STYLE, - menuItemStyle: DEFAULT_STYLE, - isNightVersion: false, + zDepth: 1, }; private _isHovering: boolean; private _popoverCloseCheckIntervalId: number; - constructor(props: DropDownMenuItemProps) { + constructor(props: DropDownProps) { super(props); this.state = { isDropDownOpen: false, @@ -44,30 +43,35 @@ export class DropDownMenuItem extends React.Component<DropDownMenuItemProps, Dro public componentWillUnmount() { window.clearInterval(this._popoverCloseCheckIntervalId); } + public componentWillReceiveProps(nextProps: DropDownProps) { + // HACK: If the popoverContent is updated to a different dimension and the users + // mouse is no longer above it, the dropdown can enter an inconsistent state where + // it believes the user is still hovering over it. In order to remedy this, we + // call hoverOff whenever the dropdown receives updated props. This is a hack + // because it will effectively close the dropdown on any prop update, barring + // dropdowns from having dynamic content. + this._onHoverOff(); + } public render() { - const colorStyle = this.props.isNightVersion ? 'white' : this.props.style.color; return ( <div - style={{ ...this.props.style, color: colorStyle }} + style={{ ...this.props.style, width: 'fit-content', height: '100%' }} onMouseEnter={this._onHover.bind(this)} onMouseLeave={this._onHoverOff.bind(this)} > - <div className="flex relative"> - <div style={{ paddingRight: 10 }}>{this.props.title}</div> - <div className="absolute" style={{ paddingLeft: 3, right: 3, top: -2 }}> - <i className="zmdi zmdi-caret-right" style={{ fontSize: 22 }} /> - </div> - </div> + {this.props.hoverActiveNode} <Popover open={this.state.isDropDownOpen} anchorEl={this.state.anchorEl} - anchorOrigin={{ horizontal: 'middle', vertical: 'bottom' }} - targetOrigin={{ horizontal: 'middle', vertical: 'top' }} + anchorOrigin={this.props.anchorOrigin} + targetOrigin={this.props.targetOrigin} onRequestClose={this._closePopover.bind(this)} useLayerForClickAway={false} + animation={PopoverAnimationVertical} + zDepth={this.props.zDepth} > <div onMouseEnter={this._onHover.bind(this)} onMouseLeave={this._onHoverOff.bind(this)}> - <Menu style={{ color: colors.grey }}>{this.props.subMenuItems}</Menu> + {this.props.popoverContent} </div> </Popover> </div> @@ -87,7 +91,7 @@ export class DropDownMenuItem extends React.Component<DropDownMenuItemProps, Dro anchorEl: event.currentTarget, }); } - private _onHoverOff(event: React.FormEvent<HTMLInputElement>) { + private _onHoverOff() { this._isHovering = false; } private _checkIfShouldClosePopover() { diff --git a/packages/website/ts/components/ui/loading.tsx b/packages/website/ts/components/ui/loading.tsx deleted file mode 100644 index aa319e9e9..000000000 --- a/packages/website/ts/components/ui/loading.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import * as _ from 'lodash'; -import Paper from 'material-ui/Paper'; -import * as React from 'react'; -import { DefaultPlayer as Video } from 'react-html5video'; -import 'react-html5video/dist/styles.css'; -import { utils } from 'ts/utils/utils'; - -interface LoadingProps {} - -interface LoadingState {} - -export class Loading extends React.Component<LoadingProps, LoadingState> { - public render() { - return ( - <div className="pt4 sm-px2 sm-pt2 sm-m1" style={{ height: 500 }}> - <Paper className="mx-auto" style={{ maxWidth: 400 }}> - {utils.isUserOnMobile() ? ( - <img className="p1" src="/gifs/0xAnimation.gif" width="96%" /> - ) : ( - <div style={{ pointerEvents: 'none' }}> - <Video - autoPlay={true} - loop={true} - muted={true} - controls={[]} - poster="/images/loading_poster.png" - > - <source src="/videos/0xAnimation.mp4" type="video/mp4" /> - </Video> - </div> - )} - <div className="center pt2" style={{ paddingBottom: 11 }}> - Connecting to the blockchain... - </div> - </Paper> - </div> - ); - } -} diff --git a/packages/website/ts/containers/generate_order_form.tsx b/packages/website/ts/containers/generate_order_form.tsx index 3fd31087f..57863dbae 100644 --- a/packages/website/ts/containers/generate_order_form.tsx +++ b/packages/website/ts/containers/generate_order_form.tsx @@ -6,14 +6,7 @@ import { Blockchain } from 'ts/blockchain'; import { GenerateOrderForm as GenerateOrderFormComponent } from 'ts/components/generate_order/generate_order_form'; import { Dispatcher } from 'ts/redux/dispatcher'; import { State } from 'ts/redux/reducer'; -import { - BlockchainErrs, - HashData, - SideToAssetToken, - SignatureData, - TokenByAddress, - TokenStateByAddress, -} from 'ts/types'; +import { BlockchainErrs, HashData, SideToAssetToken, SignatureData, TokenByAddress } from 'ts/types'; interface GenerateOrderFormProps { blockchain: Blockchain; @@ -32,7 +25,7 @@ interface ConnectedState { networkId: number; sideToAssetToken: SideToAssetToken; tokenByAddress: TokenByAddress; - tokenStateByAddress: TokenStateByAddress; + lastForceTokenStateRefetch: number; } const mapStateToProps = (state: State, ownProps: GenerateOrderFormProps): ConnectedState => ({ @@ -45,8 +38,8 @@ const mapStateToProps = (state: State, ownProps: GenerateOrderFormProps): Connec networkId: state.networkId, sideToAssetToken: state.sideToAssetToken, tokenByAddress: state.tokenByAddress, - tokenStateByAddress: state.tokenStateByAddress, userAddress: state.userAddress, + lastForceTokenStateRefetch: state.lastForceTokenStateRefetch, }); export const GenerateOrderForm: React.ComponentClass<GenerateOrderFormProps> = connect(mapStateToProps)( diff --git a/packages/website/ts/containers/portal.tsx b/packages/website/ts/containers/portal.tsx index f0247935b..bcca0d70f 100644 --- a/packages/website/ts/containers/portal.tsx +++ b/packages/website/ts/containers/portal.tsx @@ -6,18 +6,20 @@ import { Dispatch } from 'redux'; import { Portal as PortalComponent, PortalAllProps as PortalComponentAllProps } from 'ts/components/portal'; import { Dispatcher } from 'ts/redux/dispatcher'; import { State } from 'ts/redux/reducer'; -import { BlockchainErrs, HashData, Order, ScreenWidths, Side, TokenByAddress, TokenStateByAddress } from 'ts/types'; +import { BlockchainErrs, HashData, Order, ProviderType, ScreenWidths, Side, TokenByAddress } from 'ts/types'; import { constants } from 'ts/utils/constants'; interface ConnectedState { blockchainErr: BlockchainErrs; blockchainIsLoaded: boolean; hashData: HashData; + injectedProviderName: string; networkId: number; nodeVersion: string; orderFillAmount: BigNumber; + providerType: ProviderType; tokenByAddress: TokenByAddress; - tokenStateByAddress: TokenStateByAddress; + lastForceTokenStateRefetch: number; userEtherBalance: BigNumber; screenWidth: ScreenWidths; shouldBlockchainErrDialogBeOpen: boolean; @@ -57,14 +59,16 @@ const mapStateToProps = (state: State, ownProps: PortalComponentAllProps): Conne return { blockchainErr: state.blockchainErr, blockchainIsLoaded: state.blockchainIsLoaded, + hashData, + injectedProviderName: state.injectedProviderName, networkId: state.networkId, nodeVersion: state.nodeVersion, orderFillAmount: state.orderFillAmount, - hashData, + providerType: state.providerType, screenWidth: state.screenWidth, shouldBlockchainErrDialogBeOpen: state.shouldBlockchainErrDialogBeOpen, tokenByAddress: state.tokenByAddress, - tokenStateByAddress: state.tokenStateByAddress, + lastForceTokenStateRefetch: state.lastForceTokenStateRefetch, userAddress: state.userAddress, userEtherBalance: state.userEtherBalance, userSuppliedOrderCache: state.userSuppliedOrderCache, diff --git a/packages/website/ts/globals.d.ts b/packages/website/ts/globals.d.ts index 383e5cbe0..d7f887c6d 100644 --- a/packages/website/ts/globals.d.ts +++ b/packages/website/ts/globals.d.ts @@ -10,7 +10,6 @@ declare module 'thenby'; declare module 'react-highlight'; declare module 'react-recaptcha'; declare module 'react-document-title'; -declare module 'ledgerco'; declare module 'ethereumjs-tx'; declare module '*.json' { diff --git a/packages/website/ts/local_storage/tracked_token_storage.ts b/packages/website/ts/local_storage/tracked_token_storage.ts index 7733e8436..f865f8109 100644 --- a/packages/website/ts/local_storage/tracked_token_storage.ts +++ b/packages/website/ts/local_storage/tracked_token_storage.ts @@ -1,6 +1,6 @@ import * as _ from 'lodash'; import { localStorage } from 'ts/local_storage/local_storage'; -import { Token, TrackedTokensByUserAddress } from 'ts/types'; +import { Token, TokenByAddress, TrackedTokensByUserAddress } from 'ts/types'; import { configs } from 'ts/utils/configs'; const TRACKED_TOKENS_KEY = 'trackedTokens'; @@ -39,18 +39,22 @@ export const trackedTokenStorage = { const trackedTokensByUserAddress = JSON.parse(trackedTokensJSONString); return trackedTokensByUserAddress; }, - getTrackedTokensIfExists(userAddress: string, networkId: number): Token[] { + getTrackedTokensByAddress(userAddress: string, networkId: number): TokenByAddress { + const trackedTokensByAddress: TokenByAddress = {}; const trackedTokensJSONString = localStorage.getItemIfExists(TRACKED_TOKENS_KEY); if (_.isEmpty(trackedTokensJSONString)) { - return undefined; + return trackedTokensByAddress; } const trackedTokensByUserAddress = JSON.parse(trackedTokensJSONString); const trackedTokensByNetworkId = trackedTokensByUserAddress[userAddress]; if (_.isUndefined(trackedTokensByNetworkId)) { - return undefined; + return trackedTokensByAddress; } const trackedTokens = trackedTokensByNetworkId[networkId]; - return trackedTokens; + _.each(trackedTokens, (trackedToken: Token) => { + trackedTokensByAddress[trackedToken.address] = trackedToken; + }); + return trackedTokensByAddress; }, removeTrackedToken(userAddress: string, networkId: number, tokenAddress: string): void { const trackedTokensByUserAddress = this.getTrackedTokensByUserAddress(); diff --git a/packages/website/ts/pages/about/about.tsx b/packages/website/ts/pages/about/about.tsx index c929673f5..0889e79f3 100644 --- a/packages/website/ts/pages/about/about.tsx +++ b/packages/website/ts/pages/about/about.tsx @@ -2,7 +2,7 @@ import * as _ from 'lodash'; import * as React from 'react'; import * as DocumentTitle from 'react-document-title'; import { Footer } from 'ts/components/footer'; -import { TopBar } from 'ts/components/top_bar'; +import { TopBar } from 'ts/components/top_bar/top_bar'; import { Profile } from 'ts/pages/about/profile'; import { ProfileInfo, Styles } from 'ts/types'; import { colors } from 'ts/utils/colors'; diff --git a/packages/website/ts/pages/documentation/documentation.tsx b/packages/website/ts/pages/documentation/documentation.tsx index 2315847ad..7ad1d3b9c 100644 --- a/packages/website/ts/pages/documentation/documentation.tsx +++ b/packages/website/ts/pages/documentation/documentation.tsx @@ -5,7 +5,7 @@ import * as React from 'react'; import DocumentTitle = require('react-document-title'); import { scroller } from 'react-scroll'; import semverSort = require('semver-sort'); -import { TopBar } from 'ts/components/top_bar'; +import { TopBar } from 'ts/components/top_bar/top_bar'; import { Badge } from 'ts/components/ui/badge'; import { Comment } from 'ts/pages/documentation/comment'; import { DocsInfo } from 'ts/pages/documentation/docs_info'; @@ -40,9 +40,9 @@ import { utils } from 'ts/utils/utils'; const SCROLL_TOP_ID = 'docsScrollTop'; const networkNameToColor: { [network: string]: string } = { - [Networks.kovan]: colors.purple, - [Networks.ropsten]: colors.red, - [Networks.mainnet]: colors.turquois, + [Networks.Kovan]: colors.purple, + [Networks.Ropsten]: colors.red, + [Networks.Mainnet]: colors.turquois, }; export interface DocumentationAllProps { @@ -78,8 +78,10 @@ const styles: Styles = { }; export class Documentation extends React.Component<DocumentationAllProps, DocumentationState> { + private _isUnmounted: boolean; constructor(props: DocumentationAllProps) { super(props); + this._isUnmounted = false; this.state = { docAgnosticFormat: undefined, }; @@ -92,6 +94,9 @@ export class Documentation extends React.Component<DocumentationAllProps, Docume // tslint:disable-next-line:no-floating-promises this._fetchJSONDocsFireAndForgetAsync(preferredVersionIfExists); } + public componentWillUnmount() { + this._isUnmounted = true; + } public render() { const menuSubsectionsBySection = _.isUndefined(this.state.docAgnosticFormat) ? {} @@ -367,13 +372,15 @@ export class Documentation extends React.Component<DocumentationAllProps, Docume ); const docAgnosticFormat = this.props.docsInfo.convertToDocAgnosticFormat(versionDocObj as DoxityDocObj); - this.setState( - { - docAgnosticFormat, - }, - () => { - this._scrollToHash(); - }, - ); + if (!this._isUnmounted) { + this.setState( + { + docAgnosticFormat, + }, + () => { + this._scrollToHash(); + }, + ); + } } } diff --git a/packages/website/ts/pages/faq/faq.tsx b/packages/website/ts/pages/faq/faq.tsx index b4b5214a2..0a7eecc2d 100644 --- a/packages/website/ts/pages/faq/faq.tsx +++ b/packages/website/ts/pages/faq/faq.tsx @@ -2,7 +2,7 @@ import * as _ from 'lodash'; import * as React from 'react'; import * as DocumentTitle from 'react-document-title'; import { Footer } from 'ts/components/footer'; -import { TopBar } from 'ts/components/top_bar'; +import { TopBar } from 'ts/components/top_bar/top_bar'; import { Question } from 'ts/pages/faq/question'; import { FAQQuestion, FAQSection, Styles, WebsitePaths } from 'ts/types'; import { colors } from 'ts/utils/colors'; diff --git a/packages/website/ts/pages/landing/landing.tsx b/packages/website/ts/pages/landing/landing.tsx index ca76497df..b0c622fb4 100644 --- a/packages/website/ts/pages/landing/landing.tsx +++ b/packages/website/ts/pages/landing/landing.tsx @@ -4,7 +4,7 @@ import * as React from 'react'; import DocumentTitle = require('react-document-title'); import { Link } from 'react-router-dom'; import { Footer } from 'ts/components/footer'; -import { TopBar } from 'ts/components/top_bar'; +import { TopBar } from 'ts/components/top_bar/top_bar'; import { ScreenWidths, WebsitePaths } from 'ts/types'; import { colors } from 'ts/utils/colors'; import { constants } from 'ts/utils/constants'; diff --git a/packages/website/ts/pages/not_found.tsx b/packages/website/ts/pages/not_found.tsx index ff277c377..0a6ec071c 100644 --- a/packages/website/ts/pages/not_found.tsx +++ b/packages/website/ts/pages/not_found.tsx @@ -1,7 +1,7 @@ import * as _ from 'lodash'; import * as React from 'react'; import { Footer } from 'ts/components/footer'; -import { TopBar } from 'ts/components/top_bar'; +import { TopBar } from 'ts/components/top_bar/top_bar'; import { Styles } from 'ts/types'; export interface NotFoundProps { diff --git a/packages/website/ts/pages/wiki/wiki.tsx b/packages/website/ts/pages/wiki/wiki.tsx index d065614ba..daf5c27a7 100644 --- a/packages/website/ts/pages/wiki/wiki.tsx +++ b/packages/website/ts/pages/wiki/wiki.tsx @@ -3,7 +3,7 @@ import CircularProgress from 'material-ui/CircularProgress'; import * as React from 'react'; import DocumentTitle = require('react-document-title'); import { scroller } from 'react-scroll'; -import { TopBar } from 'ts/components/top_bar'; +import { TopBar } from 'ts/components/top_bar/top_bar'; import { MarkdownSection } from 'ts/pages/shared/markdown_section'; import { NestedSidebarMenu } from 'ts/pages/shared/nested_sidebar_menu'; import { SectionHeader } from 'ts/pages/shared/section_header'; @@ -45,8 +45,10 @@ const styles: Styles = { export class Wiki extends React.Component<WikiProps, WikiState> { private _wikiBackoffTimeoutId: number; + private _isUnmounted: boolean; constructor(props: WikiProps) { super(props); + this._isUnmounted = false; this.state = { articlesBySection: undefined, }; @@ -56,6 +58,7 @@ export class Wiki extends React.Component<WikiProps, WikiState> { this._fetchArticlesBySectionAsync(); } public componentWillUnmount() { + this._isUnmounted = true; clearTimeout(this._wikiBackoffTimeoutId); } public render() { @@ -179,14 +182,16 @@ export class Wiki extends React.Component<WikiProps, WikiState> { return; } const articlesBySection = await response.json(); - this.setState( - { - articlesBySection, - }, - () => { - this._scrollToHash(); - }, - ); + if (!this._isUnmounted) { + this.setState( + { + articlesBySection, + }, + () => { + this._scrollToHash(); + }, + ); + } } private _getMenuSubsectionsBySection(articlesBySection: ArticlesBySection) { const sectionNames = _.keys(articlesBySection); diff --git a/packages/website/ts/redux/dispatcher.ts b/packages/website/ts/redux/dispatcher.ts index 42989e5e1..87415b285 100644 --- a/packages/website/ts/redux/dispatcher.ts +++ b/packages/website/ts/redux/dispatcher.ts @@ -9,9 +9,10 @@ import { ProviderType, ScreenWidths, Side, + SideToAssetToken, SignatureData, Token, - TokenStateByAddress, + TokenByAddress, } from 'ts/types'; export class Dispatcher { @@ -120,9 +121,20 @@ export class Dispatcher { type: ActionTypes.RemoveTokenFromTokenByAddress, }); } - public clearTokenByAddress() { + public batchDispatch( + tokenByAddress: TokenByAddress, + networkId: number, + userAddress: string, + sideToAssetToken: SideToAssetToken, + ) { this._dispatch({ - type: ActionTypes.ClearTokenByAddress, + data: { + tokenByAddress, + networkId, + userAddress, + sideToAssetToken, + }, + type: ActionTypes.BatchDispatch, }); } public updateTokenByAddress(tokens: Token[]) { @@ -131,43 +143,9 @@ export class Dispatcher { type: ActionTypes.UpdateTokenByAddress, }); } - public updateTokenStateByAddress(tokenStateByAddress: TokenStateByAddress) { - this._dispatch({ - data: tokenStateByAddress, - type: ActionTypes.UpdateTokenStateByAddress, - }); - } - public removeFromTokenStateByAddress(tokenAddress: string) { - this._dispatch({ - data: tokenAddress, - type: ActionTypes.RemoveFromTokenStateByAddress, - }); - } - public replaceTokenAllowanceByAddress(address: string, allowance: BigNumber) { + public forceTokenStateRefetch() { this._dispatch({ - data: { - address, - allowance, - }, - type: ActionTypes.ReplaceTokenAllowanceByAddress, - }); - } - public replaceTokenBalanceByAddress(address: string, balance: BigNumber) { - this._dispatch({ - data: { - address, - balance, - }, - type: ActionTypes.ReplaceTokenBalanceByAddress, - }); - } - public updateTokenBalanceByAddress(address: string, balanceDelta: BigNumber) { - this._dispatch({ - data: { - address, - balanceDelta, - }, - type: ActionTypes.UpdateTokenBalanceByAddress, + type: ActionTypes.ForceTokenStateRefetch, }); } public updateSignatureData(signatureData: SignatureData) { diff --git a/packages/website/ts/redux/reducer.ts b/packages/website/ts/redux/reducer.ts index 06ac8b670..7b0b03dae 100644 --- a/packages/website/ts/redux/reducer.ts +++ b/packages/website/ts/redux/reducer.ts @@ -1,6 +1,7 @@ import { ZeroEx } from '0x.js'; import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; +import * as moment from 'moment'; import { Action, ActionTypes, @@ -12,8 +13,6 @@ import { SideToAssetToken, SignatureData, TokenByAddress, - TokenState, - TokenStateByAddress, } from 'ts/types'; import { utils } from 'ts/utils/utils'; @@ -37,7 +36,7 @@ export interface State { shouldBlockchainErrDialogBeOpen: boolean; sideToAssetToken: SideToAssetToken; tokenByAddress: TokenByAddress; - tokenStateByAddress: TokenStateByAddress; + lastForceTokenStateRefetch: number; userAddress: string; userEtherBalance: BigNumber; // Note: cache of supplied orderJSON in fill order step. Do not use for anything else. @@ -76,7 +75,7 @@ const INITIAL_STATE: State = { [Side.Receive]: {}, }, tokenByAddress: {}, - tokenStateByAddress: {}, + lastForceTokenStateRefetch: moment().unix(), userAddress: '', userEtherBalance: new BigNumber(0), userSuppliedOrderCache: undefined, @@ -139,13 +138,6 @@ export function reducer(state: State = INITIAL_STATE, action: Action) { }; } - case ActionTypes.ClearTokenByAddress: { - return { - ...state, - tokenByAddress: {}, - }; - } - case ActionTypes.AddTokenToTokenByAddress: { const newTokenByAddress = state.tokenByAddress; newTokenByAddress[action.data.address] = action.data; @@ -180,74 +172,21 @@ export function reducer(state: State = INITIAL_STATE, action: Action) { }; } - case ActionTypes.UpdateTokenStateByAddress: { - const tokenStateByAddress = state.tokenStateByAddress; - const updatedTokenStateByAddress = action.data; - _.each(updatedTokenStateByAddress, (tokenState: TokenState, address: string) => { - const updatedTokenState = { - ...tokenStateByAddress[address], - ...tokenState, - }; - tokenStateByAddress[address] = updatedTokenState; - }); - return { - ...state, - tokenStateByAddress, - }; - } - - case ActionTypes.RemoveFromTokenStateByAddress: { - const tokenStateByAddress = state.tokenStateByAddress; - const tokenAddress = action.data; - delete tokenStateByAddress[tokenAddress]; - return { - ...state, - tokenStateByAddress, - }; - } - - case ActionTypes.ReplaceTokenAllowanceByAddress: { - const tokenStateByAddress = state.tokenStateByAddress; - const allowance = action.data.allowance; - const tokenAddress = action.data.address; - tokenStateByAddress[tokenAddress] = { - ...tokenStateByAddress[tokenAddress], - allowance, - }; + case ActionTypes.BatchDispatch: { return { ...state, - tokenStateByAddress, + networkId: action.data.networkId, + userAddress: action.data.userAddress, + sideToAssetToken: action.data.sideToAssetToken, + tokenByAddress: action.data.tokenByAddress, }; } - case ActionTypes.ReplaceTokenBalanceByAddress: { - const tokenStateByAddress = state.tokenStateByAddress; - const balance = action.data.balance; - const tokenAddress = action.data.address; - tokenStateByAddress[tokenAddress] = { - ...tokenStateByAddress[tokenAddress], - balance, - }; + case ActionTypes.ForceTokenStateRefetch: return { ...state, - tokenStateByAddress, + lastForceTokenStateRefetch: moment().unix(), }; - } - - case ActionTypes.UpdateTokenBalanceByAddress: { - const tokenStateByAddress = state.tokenStateByAddress; - const balanceDelta = action.data.balanceDelta; - const tokenAddress = action.data.address; - const currBalance = tokenStateByAddress[tokenAddress].balance; - tokenStateByAddress[tokenAddress] = { - ...tokenStateByAddress[tokenAddress], - balance: currBalance.plus(balanceDelta), - }; - return { - ...state, - tokenStateByAddress, - }; - } case ActionTypes.UpdateOrderSignatureData: { return { diff --git a/packages/website/ts/types.ts b/packages/website/ts/types.ts index f873f95fa..c48c88cae 100644 --- a/packages/website/ts/types.ts +++ b/packages/website/ts/types.ts @@ -25,10 +25,6 @@ export interface TokenState { balance: BigNumber; } -export interface TokenStateByAddress { - [address: string]: TokenState; -} - export interface AssetToken { address?: string; amount?: BigNumber; @@ -110,12 +106,12 @@ export enum BalanceErrs { export enum ActionTypes { // Portal + BatchDispatch = 'BATCH_DISPATCH', UpdateScreenWidth = 'UPDATE_SCREEN_WIDTH', UpdateNodeVersion = 'UPDATE_NODE_VERSION', ResetState = 'RESET_STATE', AddTokenToTokenByAddress = 'ADD_TOKEN_TO_TOKEN_BY_ADDRESS', BlockchainErrEncountered = 'BLOCKCHAIN_ERR_ENCOUNTERED', - ClearTokenByAddress = 'CLEAR_TOKEN_BY_ADDRESS', UpdateBlockchainIsLoaded = 'UPDATE_BLOCKCHAIN_IS_LOADED', UpdateNetworkId = 'UPDATE_NETWORK_ID', UpdateChosenAssetToken = 'UPDATE_CHOSEN_ASSET_TOKEN', @@ -125,11 +121,7 @@ export enum ActionTypes { UpdateOrderSignatureData = 'UPDATE_ORDER_SIGNATURE_DATA', UpdateTokenByAddress = 'UPDATE_TOKEN_BY_ADDRESS', RemoveTokenFromTokenByAddress = 'REMOVE_TOKEN_FROM_TOKEN_BY_ADDRESS', - UpdateTokenStateByAddress = 'UPDATE_TOKEN_STATE_BY_ADDRESS', - RemoveFromTokenStateByAddress = 'REMOVE_FROM_TOKEN_STATE_BY_ADDRESS', - ReplaceTokenAllowanceByAddress = 'REPLACE_TOKEN_ALLOWANCE_BY_ADDRESS', - ReplaceTokenBalanceByAddress = 'REPLACE_TOKEN_BALANCE_BY_ADDRESS', - UpdateTokenBalanceByAddress = 'UPDATE_TOKEN_BALANCE_BY_ADDRESS', + ForceTokenStateRefetch = 'FORCE_TOKEN_STATE_REFETCH', UpdateOrderExpiry = 'UPDATE_ORDER_EXPIRY', SwapAssetTokens = 'SWAP_ASSET_TOKENS', UpdateUserAddress = 'UPDATE_USER_ADDRESS', @@ -496,16 +488,6 @@ export interface SignPersonalMessageParams { data: string; } -export interface TxParams { - nonce: string; - gasPrice?: number; - gasLimit: string; - to: string; - value?: string; - data?: string; - chainId: number; // EIP 155 chainId - mainnet: 1, ropsten: 3 -} - export interface PublicNodeUrlsByNetworkId { [networkId: number]: string[]; } @@ -610,10 +592,10 @@ export interface AddressByContractName { } export enum Networks { - mainnet = 'Mainnet', - kovan = 'Kovan', - ropsten = 'Ropsten', - rinkeby = 'Rinkeby', + Mainnet = 'Mainnet', + Kovan = 'Kovan', + Ropsten = 'Ropsten', + Rinkeby = 'Rinkeby', } export enum AbiTypes { @@ -678,4 +660,9 @@ export enum SmartContractDocSections { ZRXToken = 'ZRXToken', } +export interface MaterialUIPosition { + vertical: 'bottom' | 'top' | 'center'; + horizontal: 'left' | 'middle' | 'right'; +} + // tslint:disable:max-file-line-count diff --git a/packages/website/ts/utils/configs.ts b/packages/website/ts/utils/configs.ts index 3d37a89ab..874ad04c2 100644 --- a/packages/website/ts/utils/configs.ts +++ b/packages/website/ts/utils/configs.ts @@ -16,24 +16,24 @@ const isDevelopment = _.includes( const INFURA_API_KEY = 'T5WSC8cautR4KXyYgsRs'; export const configs = { - BACKEND_BASE_URL: isDevelopment ? 'https://localhost:3001' : 'https://website-api.0xproject.com', + BACKEND_BASE_URL: 'https://website-api.0xproject.com', BASE_URL, BITLY_ACCESS_TOKEN: 'ffc4c1a31e5143848fb7c523b39f91b9b213d208', CONTRACT_ADDRESS: { '1.0.0': { - [Networks.mainnet]: { + [Networks.Mainnet]: { [SmartContractDocSections.Exchange]: '0x12459c951127e0c374ff9105dda097662a027093', [SmartContractDocSections.TokenTransferProxy]: '0x8da0d80f5007ef1e431dd2127178d224e32c2ef4', [SmartContractDocSections.ZRXToken]: '0xe41d2489571d322189246dafa5ebde1f4699f498', [SmartContractDocSections.TokenRegistry]: '0x926a74c5c36adf004c87399e65f75628b0f98d2c', }, - [Networks.ropsten]: { + [Networks.Ropsten]: { [SmartContractDocSections.Exchange]: '0x479cc461fecd078f766ecc58533d6f69580cf3ac', [SmartContractDocSections.TokenTransferProxy]: '0x4e9aad8184de8833365fea970cd9149372fdf1e6', [SmartContractDocSections.ZRXToken]: '0xa8e9fa8f91e5ae138c74648c9c304f1c75003a8d', [SmartContractDocSections.TokenRegistry]: '0x6b1a50f0bb5a7995444bd3877b22dc89c62843ed', }, - [Networks.kovan]: { + [Networks.Kovan]: { [SmartContractDocSections.Exchange]: '0x90fe2af704b34e0224bf2299c838e04d4dcf1364', [SmartContractDocSections.TokenTransferProxy]: '0x087Eed4Bc1ee3DE49BeFbd66C662B434B15d49d4', [SmartContractDocSections.ZRXToken]: '0x6ff6c0ff1d68b964901f986d4c9fa3ac68346570', @@ -120,6 +120,8 @@ export const configs = { PUBLIC_NODE_URLS_BY_NETWORK_ID: { [1]: [`https://mainnet.infura.io/${INFURA_API_KEY}`, 'https://mainnet.0xproject.com'], [42]: [`https://kovan.infura.io/${INFURA_API_KEY}`, 'https://kovan.0xproject.com'], + [3]: [`https://ropsten.infura.io/${INFURA_API_KEY}`], + [4]: [`https://rinkeby.infura.io/${INFURA_API_KEY}`], } as PublicNodeUrlsByNetworkId, SHOULD_DEPRECATE_OLD_WETH_TOKEN: true, SYMBOLS_OF_MINTABLE_TOKENS: ['MKR', 'MLN', 'GNT', 'DGD', 'REP'], diff --git a/packages/website/ts/utils/constants.ts b/packages/website/ts/utils/constants.ts index dded82114..26a793f38 100644 --- a/packages/website/ts/utils/constants.ts +++ b/packages/website/ts/utils/constants.ts @@ -10,6 +10,8 @@ export const constants = { 1: 4145578, 42: 3117574, 50: 0, + 3: 1719261, + 4: 1570919, } as { [networkId: number]: number }, HOME_SCROLL_DURATION_MS: 500, HTTP_NO_CONTENT_STATUS_CODE: 204, @@ -19,19 +21,19 @@ export const constants = { MAINNET_NAME: 'Main network', MINT_AMOUNT: new BigNumber('100000000000000000000'), NETWORK_ID_MAINNET: 1, - NETWORK_ID_TESTNET: 42, + NETWORK_ID_KOVAN: 42, NETWORK_ID_TESTRPC: 50, NETWORK_NAME_BY_ID: { - 1: Networks.mainnet, - 3: Networks.ropsten, - 4: Networks.rinkeby, - 42: Networks.kovan, + 1: Networks.Mainnet, + 3: Networks.Ropsten, + 4: Networks.Rinkeby, + 42: Networks.Kovan, } as { [symbol: number]: string }, NETWORK_ID_BY_NAME: { - [Networks.mainnet]: 1, - [Networks.ropsten]: 3, - [Networks.rinkeby]: 4, - [Networks.kovan]: 42, + [Networks.Mainnet]: 1, + [Networks.Ropsten]: 3, + [Networks.Rinkeby]: 4, + [Networks.Kovan]: 42, } as { [networkName: string]: number }, NULL_ADDRESS: '0x0000000000000000000000000000000000000000', PROVIDER_NAME_LEDGER: 'Ledger', diff --git a/packages/website/ts/utils/mui_theme.ts b/packages/website/ts/utils/mui_theme.ts index d73e80606..32891baca 100644 --- a/packages/website/ts/utils/mui_theme.ts +++ b/packages/website/ts/utils/mui_theme.ts @@ -8,6 +8,7 @@ export const muiTheme = getMuiTheme({ textColor: colors.black, }, palette: { + accent1Color: colors.lightBlueA700, pickerHeaderColor: colors.lightBlue, primary1Color: colors.lightBlue, primary2Color: colors.lightBlue, diff --git a/packages/website/ts/utils/utils.ts b/packages/website/ts/utils/utils.ts index 13a6d6ae2..7e69b1c5f 100644 --- a/packages/website/ts/utils/utils.ts +++ b/packages/website/ts/utils/utils.ts @@ -151,7 +151,7 @@ export const utils = { if (_.isUndefined(networkName)) { return undefined; } - const etherScanPrefix = networkName === Networks.mainnet ? '' : `${networkName.toLowerCase()}.`; + const etherScanPrefix = networkName === Networks.Mainnet ? '' : `${networkName.toLowerCase()}.`; return `https://${etherScanPrefix}etherscan.io/${suffix}/${addressOrTxHash}`; }, setUrlHash(anchorId: string) { @@ -183,7 +183,7 @@ export const utils = { // after a user was prompted to sign a message or send a transaction and decided to // reject the request. didUserDenyWeb3Request(errMsg: string) { - const metamaskDenialErrMsg = 'User denied message'; + const metamaskDenialErrMsg = 'User denied'; const paritySignerDenialErrMsg = 'Request has been rejected'; const ledgerDenialErrMsg = 'Invalid status 6985'; const isUserDeniedErrMsg = @@ -276,4 +276,10 @@ export const utils = { exchangeContractErrorToHumanReadableError[error] || ZeroExErrorToHumanReadableError[error]; return humanReadableErrorMsg; }, + isParityNode(nodeVersion: string): boolean { + return _.includes(nodeVersion, 'Parity'); + }, + isTestRpc(nodeVersion: string): boolean { + return _.includes(nodeVersion, 'TestRPC'); + }, }; diff --git a/packages/website/ts/web3_wrapper.ts b/packages/website/ts/web3_wrapper.ts index 415df6e8b..9d8d771af 100644 --- a/packages/website/ts/web3_wrapper.ts +++ b/packages/website/ts/web3_wrapper.ts @@ -24,9 +24,6 @@ export class Web3Wrapper { this._web3 = new Web3(); this._web3.setProvider(provider); - - // tslint:disable-next-line:no-floating-promises - this._startEmittingNetworkConnectionAndUserBalanceStateAsync(); } public isAddress(address: string) { return this._web3.isAddress(address); @@ -90,11 +87,7 @@ export class Web3Wrapper { public updatePrevUserAddress(userAddress: string) { this._prevUserAddress = userAddress; } - private async _getNetworkAsync() { - const networkId = await promisify(this._web3.version.getNetwork)(); - return networkId; - } - private async _startEmittingNetworkConnectionAndUserBalanceStateAsync() { + public startEmittingNetworkConnectionAndUserBalanceState() { if (!_.isUndefined(this._watchNetworkAndBalanceIntervalId)) { return; // we are already emitting the state } @@ -127,7 +120,7 @@ export class Web3Wrapper { } // Check for user ether balance changes - if (userAddressIfExists !== '') { + if (!_.isEmpty(userAddressIfExists)) { await this._updateUserEtherBalanceAsync(userAddressIfExists); } } else { @@ -140,11 +133,15 @@ export class Web3Wrapper { }, 5000, (err: Error) => { - utils.consoleLog(`Watching network and balances failed: ${err}`); + utils.consoleLog(`Watching network and balances failed: ${err.stack}`); this._stopEmittingNetworkConnectionAndUserBalanceStateAsync(); }, ); } + private async _getNetworkAsync() { + const networkId = await promisify(this._web3.version.getNetwork)(); + return networkId; + } private async _updateUserEtherBalanceAsync(userAddress: string) { const balance = await this.getBalanceInEthAsync(userAddress); if (!balance.eq(this._prevUserEtherBalanceInEth)) { @@ -153,6 +150,8 @@ export class Web3Wrapper { } } private _stopEmittingNetworkConnectionAndUserBalanceStateAsync() { - intervalUtils.clearAsyncExcludingInterval(this._watchNetworkAndBalanceIntervalId); + if (!_.isUndefined(this._watchNetworkAndBalanceIntervalId)) { + intervalUtils.clearAsyncExcludingInterval(this._watchNetworkAndBalanceIntervalId); + } } } |