Merge branch 'v2-prototype' into eth-lightwallet-subprovider-final

This commit is contained in:
Jacob Evans
2018-07-04 20:07:05 +10:00
committed by Jacob Evans
54 changed files with 706 additions and 561 deletions

View File

@@ -1,4 +1,12 @@
[
{
"version": "0.0.6",
"changes": [
{
"note": "Update blockstream to v5.0 and propogate up caught errors to active subscriptions"
}
]
},
{
"timestamp": 1529397769,
"version": "0.0.5",

View File

@@ -74,7 +74,7 @@
"source-map-support": "^0.5.0",
"tslint": "5.8.0",
"typescript": "2.7.1",
"web3-provider-engine": "^14.0.4"
"web3-provider-engine": "14.0.6"
},
"dependencies": {
"@0xproject/assert": "^0.2.12",
@@ -87,7 +87,7 @@
"@0xproject/utils": "^0.7.1",
"@0xproject/web3-wrapper": "^0.7.1",
"ethereum-types": "^0.0.2",
"ethereumjs-blockstream": "^2.0.6",
"ethereumjs-blockstream": "5.0.0",
"ethereumjs-util": "^5.1.1",
"ethers": "3.0.22",
"js-sha3": "^0.7.0",

View File

@@ -9,7 +9,7 @@ import {
} from '@0xproject/types';
import { intervalUtils } from '@0xproject/utils';
import { Web3Wrapper } from '@0xproject/web3-wrapper';
import { Block, BlockAndLogStreamer } from 'ethereumjs-blockstream';
import { Block, BlockAndLogStreamer, Log } from 'ethereumjs-blockstream';
import * as _ from 'lodash';
import {
@@ -39,7 +39,7 @@ export abstract class ContractWrapper {
public abstract abi: ContractAbi;
protected _web3Wrapper: Web3Wrapper;
protected _networkId: number;
private _blockAndLogStreamerIfExists?: BlockAndLogStreamer;
private _blockAndLogStreamerIfExists: BlockAndLogStreamer<Block, Log> | undefined;
private _blockAndLogStreamIntervalIfExists?: NodeJS.Timer;
private _filters: { [filterToken: string]: FilterObject };
private _filterCallbacks: {
@@ -163,6 +163,7 @@ export abstract class ContractWrapper {
this._blockAndLogStreamerIfExists = new BlockAndLogStreamer(
this._web3Wrapper.getBlockAsync.bind(this._web3Wrapper),
this._web3Wrapper.getLogsAsync.bind(this._web3Wrapper),
this._onBlockAndLogStreamerError.bind(this),
);
const catchAllLogFilter = {};
this._blockAndLogStreamerIfExists.addLogFilter(catchAllLogFilter);
@@ -180,6 +181,14 @@ export abstract class ContractWrapper {
this._onLogStateChanged.bind(this, isRemoved),
);
}
private _onBlockAndLogStreamerError(err: Error): void {
// Propogate all Blockstream subscriber errors to all
// top-level subscriptions
const filterCallbacks = _.values(this._filterCallbacks);
_.each(filterCallbacks, filterCallback => {
filterCallback(err);
});
}
private _onReconcileBlockError(err: Error): void {
const filterTokens = _.keys(this._filterCallbacks);
_.each(filterTokens, filterToken => {

View File

@@ -5,7 +5,7 @@ import * as chai from 'chai';
import { MixinAuthorizableContract } from '../../generated_contract_wrappers/mixin_authorizable';
import { artifacts } from '../utils/artifacts';
import { expectRevertReasonOrAlwaysFailingTransactionAsync } from '../utils/assertions';
import { expectTransactionFailedAsync } from '../utils/assertions';
import { chaiSetup } from '../utils/chai_setup';
import { constants } from '../utils/constants';
import { provider, txDefaults, web3Wrapper } from '../utils/web3_wrapper';
@@ -44,7 +44,7 @@ describe('Authorizable', () => {
});
describe('addAuthorizedAddress', () => {
it('should throw if not called by owner', async () => {
return expectRevertReasonOrAlwaysFailingTransactionAsync(
return expectTransactionFailedAsync(
authorizable.addAuthorizedAddress.sendTransactionAsync(notOwner, { from: notOwner }),
RevertReason.OnlyContractOwner,
);
@@ -62,7 +62,7 @@ describe('Authorizable', () => {
await authorizable.addAuthorizedAddress.sendTransactionAsync(address, { from: owner }),
constants.AWAIT_TRANSACTION_MINED_MS,
);
return expectRevertReasonOrAlwaysFailingTransactionAsync(
return expectTransactionFailedAsync(
authorizable.addAuthorizedAddress.sendTransactionAsync(address, { from: owner }),
RevertReason.TargetAlreadyAuthorized,
);
@@ -75,7 +75,7 @@ describe('Authorizable', () => {
await authorizable.addAuthorizedAddress.sendTransactionAsync(address, { from: owner }),
constants.AWAIT_TRANSACTION_MINED_MS,
);
return expectRevertReasonOrAlwaysFailingTransactionAsync(
return expectTransactionFailedAsync(
authorizable.removeAuthorizedAddress.sendTransactionAsync(address, {
from: notOwner,
}),
@@ -99,7 +99,7 @@ describe('Authorizable', () => {
});
it('should throw if owner attempts to remove an address that is not authorized', async () => {
return expectRevertReasonOrAlwaysFailingTransactionAsync(
return expectTransactionFailedAsync(
authorizable.removeAuthorizedAddress.sendTransactionAsync(address, {
from: owner,
}),
@@ -115,7 +115,7 @@ describe('Authorizable', () => {
constants.AWAIT_TRANSACTION_MINED_MS,
);
const index = new BigNumber(0);
return expectRevertReasonOrAlwaysFailingTransactionAsync(
return expectTransactionFailedAsync(
authorizable.removeAuthorizedAddressAtIndex.sendTransactionAsync(address, index, {
from: notOwner,
}),
@@ -128,7 +128,7 @@ describe('Authorizable', () => {
constants.AWAIT_TRANSACTION_MINED_MS,
);
const index = new BigNumber(1);
return expectRevertReasonOrAlwaysFailingTransactionAsync(
return expectTransactionFailedAsync(
authorizable.removeAuthorizedAddressAtIndex.sendTransactionAsync(address, index, {
from: owner,
}),
@@ -137,7 +137,7 @@ describe('Authorizable', () => {
});
it('should throw if owner attempts to remove an address that is not authorized', async () => {
const index = new BigNumber(0);
return expectRevertReasonOrAlwaysFailingTransactionAsync(
return expectTransactionFailedAsync(
authorizable.removeAuthorizedAddressAtIndex.sendTransactionAsync(address, index, {
from: owner,
}),
@@ -156,7 +156,7 @@ describe('Authorizable', () => {
constants.AWAIT_TRANSACTION_MINED_MS,
);
const address1Index = new BigNumber(0);
return expectRevertReasonOrAlwaysFailingTransactionAsync(
return expectTransactionFailedAsync(
authorizable.removeAuthorizedAddressAtIndex.sendTransactionAsync(address2, address1Index, {
from: owner,
}),

View File

@@ -17,7 +17,7 @@ import { ERC20ProxyContract } from '../../generated_contract_wrappers/e_r_c20_pr
import { ERC721ProxyContract } from '../../generated_contract_wrappers/e_r_c721_proxy';
import { IAssetProxyContract } from '../../generated_contract_wrappers/i_asset_proxy';
import { artifacts } from '../utils/artifacts';
import { expectRevertReasonOrAlwaysFailingTransactionAsync } from '../utils/assertions';
import { expectTransactionFailedAsync } from '../utils/assertions';
import { chaiSetup } from '../utils/chai_setup';
import { constants } from '../utils/constants';
import { ERC20Wrapper } from '../utils/erc20_wrapper';
@@ -184,7 +184,7 @@ describe('Asset Transfer Proxies', () => {
constants.AWAIT_TRANSACTION_MINED_MS,
);
// Perform a transfer; expect this to fail.
return expectRevertReasonOrAlwaysFailingTransactionAsync(
await expectTransactionFailedAsync(
web3Wrapper.sendTransactionAsync({
to: erc20Proxy.address,
data,
@@ -205,7 +205,7 @@ describe('Asset Transfer Proxies', () => {
takerAddress,
amount,
);
return expectRevertReasonOrAlwaysFailingTransactionAsync(
await expectTransactionFailedAsync(
web3Wrapper.sendTransactionAsync({
to: erc20Proxy.address,
data,
@@ -344,7 +344,7 @@ describe('Asset Transfer Proxies', () => {
erc20Proxy.address, // the ERC20 proxy does not have an ERC721 receiver
amount,
);
return expectRevertReasonOrAlwaysFailingTransactionAsync(
return expectTransactionFailedAsync(
web3Wrapper.sendTransactionAsync({
to: erc721Proxy.address,
data,
@@ -369,7 +369,7 @@ describe('Asset Transfer Proxies', () => {
takerAddress,
amount,
);
return expectRevertReasonOrAlwaysFailingTransactionAsync(
return expectTransactionFailedAsync(
web3Wrapper.sendTransactionAsync({
to: erc721Proxy.address,
data,
@@ -393,7 +393,7 @@ describe('Asset Transfer Proxies', () => {
takerAddress,
amount,
);
return expectRevertReasonOrAlwaysFailingTransactionAsync(
return expectTransactionFailedAsync(
web3Wrapper.sendTransactionAsync({
to: erc721Proxy.address,
data,
@@ -421,7 +421,7 @@ describe('Asset Transfer Proxies', () => {
takerAddress,
amount,
);
return expectRevertReasonOrAlwaysFailingTransactionAsync(
return expectTransactionFailedAsync(
web3Wrapper.sendTransactionAsync({
to: erc721Proxy.address,
data,
@@ -442,7 +442,7 @@ describe('Asset Transfer Proxies', () => {
takerAddress,
amount,
);
return expectRevertReasonOrAlwaysFailingTransactionAsync(
return expectTransactionFailedAsync(
web3Wrapper.sendTransactionAsync({
to: erc721Proxy.address,
data,

View File

@@ -14,7 +14,7 @@ import { ERC20ProxyContract } from '../../generated_contract_wrappers/e_r_c20_pr
import { ERC721ProxyContract } from '../../generated_contract_wrappers/e_r_c721_proxy';
import { CancelContractEventArgs, ExchangeContract } from '../../generated_contract_wrappers/exchange';
import { artifacts } from '../utils/artifacts';
import { expectRevertReasonOrAlwaysFailingTransactionAsync } from '../utils/assertions';
import { expectTransactionFailedAsync } from '../utils/assertions';
import { chaiSetup } from '../utils/chai_setup';
import { constants } from '../utils/constants';
import { ERC20Wrapper } from '../utils/erc20_wrapper';
@@ -144,7 +144,7 @@ describe('Exchange core', () => {
const invalidSigBuff = Buffer.concat([v, invalidR, invalidS, signatureType]);
const invalidSigHex = `0x${invalidSigBuff.toString('hex')}`;
signedOrder.signature = invalidSigHex;
return expectRevertReasonOrAlwaysFailingTransactionAsync(
return expectTransactionFailedAsync(
exchangeWrapper.fillOrderAsync(signedOrder, takerAddress),
RevertReason.InvalidOrderSignature,
);
@@ -153,7 +153,7 @@ describe('Exchange core', () => {
it('should throw if no value is filled', async () => {
signedOrder = orderFactory.newSignedOrder();
await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress);
return expectRevertReasonOrAlwaysFailingTransactionAsync(
return expectTransactionFailedAsync(
exchangeWrapper.fillOrderAsync(signedOrder, takerAddress),
RevertReason.OrderUnfillable,
);
@@ -167,7 +167,7 @@ describe('Exchange core', () => {
});
it('should throw if not sent by maker', async () => {
return expectRevertReasonOrAlwaysFailingTransactionAsync(
return expectTransactionFailedAsync(
exchangeWrapper.cancelOrderAsync(signedOrder, takerAddress),
RevertReason.InvalidMaker,
);
@@ -178,7 +178,7 @@ describe('Exchange core', () => {
makerAssetAmount: new BigNumber(0),
});
return expectRevertReasonOrAlwaysFailingTransactionAsync(
return expectTransactionFailedAsync(
exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress),
RevertReason.OrderUnfillable,
);
@@ -189,7 +189,7 @@ describe('Exchange core', () => {
takerAssetAmount: new BigNumber(0),
});
return expectRevertReasonOrAlwaysFailingTransactionAsync(
return expectTransactionFailedAsync(
exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress),
RevertReason.OrderUnfillable,
);
@@ -197,7 +197,7 @@ describe('Exchange core', () => {
it('should be able to cancel a full order', async () => {
await exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress);
return expectRevertReasonOrAlwaysFailingTransactionAsync(
return expectTransactionFailedAsync(
exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, {
takerAssetFillAmount: signedOrder.takerAssetAmount.div(2),
}),
@@ -222,7 +222,7 @@ describe('Exchange core', () => {
it('should throw if already cancelled', async () => {
await exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress);
return expectRevertReasonOrAlwaysFailingTransactionAsync(
return expectTransactionFailedAsync(
exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress),
RevertReason.OrderUnfillable,
);
@@ -232,7 +232,7 @@ describe('Exchange core', () => {
signedOrder = orderFactory.newSignedOrder({
expirationTimeSeconds: new BigNumber(Math.floor((Date.now() - 10000) / 1000)),
});
return expectRevertReasonOrAlwaysFailingTransactionAsync(
return expectTransactionFailedAsync(
exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress),
RevertReason.OrderUnfillable,
);
@@ -250,7 +250,7 @@ describe('Exchange core', () => {
});
const fillTakerAssetAmount2 = new BigNumber(1);
return expectRevertReasonOrAlwaysFailingTransactionAsync(
return expectTransactionFailedAsync(
exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, {
takerAssetFillAmount: fillTakerAssetAmount2,
}),
@@ -264,7 +264,7 @@ describe('Exchange core', () => {
const orderEpoch = new BigNumber(1);
await exchangeWrapper.cancelOrdersUpToAsync(orderEpoch, makerAddress);
const lesserOrderEpoch = new BigNumber(0);
return expectRevertReasonOrAlwaysFailingTransactionAsync(
return expectTransactionFailedAsync(
exchangeWrapper.cancelOrdersUpToAsync(lesserOrderEpoch, makerAddress),
RevertReason.InvalidNewOrderEpoch,
);
@@ -273,7 +273,7 @@ describe('Exchange core', () => {
it('should fail to set orderEpoch equal to existing orderEpoch', async () => {
const orderEpoch = new BigNumber(1);
await exchangeWrapper.cancelOrdersUpToAsync(orderEpoch, makerAddress);
return expectRevertReasonOrAlwaysFailingTransactionAsync(
return expectTransactionFailedAsync(
exchangeWrapper.cancelOrdersUpToAsync(orderEpoch, makerAddress),
RevertReason.InvalidNewOrderEpoch,
);
@@ -363,7 +363,7 @@ describe('Exchange core', () => {
expect(initialOwnerTakerAsset).to.be.bignumber.equal(takerAddress);
// Call Exchange
const takerAssetFillAmount = signedOrder.takerAssetAmount;
return expectRevertReasonOrAlwaysFailingTransactionAsync(
return expectTransactionFailedAsync(
exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount }),
RevertReason.TransferFailed,
);
@@ -386,7 +386,7 @@ describe('Exchange core', () => {
expect(initialOwnerTakerAsset).to.be.bignumber.not.equal(takerAddress);
// Call Exchange
const takerAssetFillAmount = signedOrder.takerAssetAmount;
return expectRevertReasonOrAlwaysFailingTransactionAsync(
return expectTransactionFailedAsync(
exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount }),
RevertReason.TransferFailed,
);
@@ -409,7 +409,7 @@ describe('Exchange core', () => {
expect(initialOwnerTakerAsset).to.be.bignumber.equal(takerAddress);
// Call Exchange
const takerAssetFillAmount = signedOrder.takerAssetAmount;
return expectRevertReasonOrAlwaysFailingTransactionAsync(
return expectTransactionFailedAsync(
exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount }),
RevertReason.InvalidAmount,
);
@@ -432,7 +432,7 @@ describe('Exchange core', () => {
expect(initialOwnerTakerAsset).to.be.bignumber.equal(takerAddress);
// Call Exchange
const takerAssetFillAmount = signedOrder.takerAssetAmount;
return expectRevertReasonOrAlwaysFailingTransactionAsync(
return expectTransactionFailedAsync(
exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount }),
RevertReason.InvalidAmount,
);
@@ -449,7 +449,7 @@ describe('Exchange core', () => {
});
// Call Exchange
const takerAssetFillAmount = signedOrder.takerAssetAmount.div(2);
return expectRevertReasonOrAlwaysFailingTransactionAsync(
return expectTransactionFailedAsync(
exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount }),
RevertReason.RoundingError,
);
@@ -475,7 +475,7 @@ describe('Exchange core', () => {
expect(initialOwnerTakerAsset).to.be.bignumber.equal(takerAddress);
// Call Exchange
const takerAssetFillAmount = signedOrder.takerAssetAmount;
return expectRevertReasonOrAlwaysFailingTransactionAsync(
return expectTransactionFailedAsync(
exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount }),
RevertReason.LengthGreaterThan131Required,
);

View File

@@ -14,7 +14,7 @@ import {
TestAssetProxyDispatcherContract,
} from '../../generated_contract_wrappers/test_asset_proxy_dispatcher';
import { artifacts } from '../utils/artifacts';
import { expectRevertReasonOrAlwaysFailingTransactionAsync } from '../utils/assertions';
import { expectTransactionFailedAsync } from '../utils/assertions';
import { chaiSetup } from '../utils/chai_setup';
import { constants } from '../utils/constants';
import { ERC20Wrapper } from '../utils/erc20_wrapper';
@@ -129,7 +129,7 @@ describe('AssetProxyDispatcher', () => {
txDefaults,
);
// Register new ERC20 Transfer Proxy contract
return expectRevertReasonOrAlwaysFailingTransactionAsync(
return expectTransactionFailedAsync(
assetProxyDispatcher.registerAssetProxy.sendTransactionAsync(newErc20TransferProxy.address, {
from: owner,
}),
@@ -138,7 +138,7 @@ describe('AssetProxyDispatcher', () => {
});
it('should throw if requesting address is not owner', async () => {
return expectRevertReasonOrAlwaysFailingTransactionAsync(
return expectTransactionFailedAsync(
assetProxyDispatcher.registerAssetProxy.sendTransactionAsync(erc20Proxy.address, { from: notOwner }),
RevertReason.OnlyContractOwner,
);
@@ -210,7 +210,7 @@ describe('AssetProxyDispatcher', () => {
const encodedAssetData = assetProxyUtils.encodeERC20AssetData(zrxToken.address);
// Perform a transfer from makerAddress to takerAddress
const amount = new BigNumber(10);
return expectRevertReasonOrAlwaysFailingTransactionAsync(
return expectTransactionFailedAsync(
assetProxyDispatcher.publicDispatchTransferFrom.sendTransactionAsync(
encodedAssetData,
makerAddress,

View File

@@ -12,7 +12,7 @@ import { ERC20ProxyContract } from '../../generated_contract_wrappers/e_r_c20_pr
import { ERC721ProxyContract } from '../../generated_contract_wrappers/e_r_c721_proxy';
import { ExchangeContract } from '../../generated_contract_wrappers/exchange';
import { artifacts } from '../utils/artifacts';
import { expectRevertReasonOrAlwaysFailingTransactionAsync } from '../utils/assertions';
import { expectTransactionFailedAsync } from '../utils/assertions';
import { chaiSetup } from '../utils/chai_setup';
import { constants } from '../utils/constants';
import { ERC20Wrapper } from '../utils/erc20_wrapper';
@@ -602,7 +602,7 @@ describe('matchOrders', () => {
// Cancel left order
await exchangeWrapper.cancelOrderAsync(signedOrderLeft, signedOrderLeft.makerAddress);
// Match orders
return expectRevertReasonOrAlwaysFailingTransactionAsync(
return expectTransactionFailedAsync(
exchangeWrapper.matchOrdersAsync(signedOrderLeft, signedOrderRight, takerAddress),
RevertReason.OrderUnfillable,
);
@@ -627,7 +627,7 @@ describe('matchOrders', () => {
// Cancel right order
await exchangeWrapper.cancelOrderAsync(signedOrderRight, signedOrderRight.makerAddress);
// Match orders
return expectRevertReasonOrAlwaysFailingTransactionAsync(
return expectTransactionFailedAsync(
exchangeWrapper.matchOrdersAsync(signedOrderLeft, signedOrderRight, takerAddress),
RevertReason.OrderUnfillable,
);
@@ -650,7 +650,7 @@ describe('matchOrders', () => {
feeRecipientAddress: feeRecipientAddressRight,
});
// Match orders
return expectRevertReasonOrAlwaysFailingTransactionAsync(
return expectTransactionFailedAsync(
exchangeWrapper.matchOrdersAsync(signedOrderLeft, signedOrderRight, takerAddress),
RevertReason.NegativeSpreadRequired,
);
@@ -673,7 +673,7 @@ describe('matchOrders', () => {
feeRecipientAddress: feeRecipientAddressRight,
});
// Match orders
return expectRevertReasonOrAlwaysFailingTransactionAsync(
return expectTransactionFailedAsync(
exchangeWrapper.matchOrdersAsync(signedOrderLeft, signedOrderRight, takerAddress),
// We are assuming assetData fields of the right order are the
// reverse of the left order, rather than checking equality. This
@@ -702,7 +702,7 @@ describe('matchOrders', () => {
feeRecipientAddress: feeRecipientAddressRight,
});
// Match orders
return expectRevertReasonOrAlwaysFailingTransactionAsync(
return expectTransactionFailedAsync(
exchangeWrapper.matchOrdersAsync(signedOrderLeft, signedOrderRight, takerAddress),
RevertReason.InvalidOrderSignature,
);

View File

@@ -13,7 +13,7 @@ import { TestValidatorContract } from '../../generated_contract_wrappers/test_va
import { TestWalletContract } from '../../generated_contract_wrappers/test_wallet';
import { addressUtils } from '../utils/address_utils';
import { artifacts } from '../utils/artifacts';
import { expectRevertOrOtherErrorAsync } from '../utils/assertions';
import { expectContractCallFailed } from '../utils/assertions';
import { chaiSetup } from '../utils/chai_setup';
import { constants } from '../utils/constants';
import { LogDecoder } from '../utils/log_decoder';
@@ -101,7 +101,7 @@ describe('MixinSignatureValidator', () => {
it('should revert when signature is empty', async () => {
const emptySignature = '0x';
const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder);
return expectRevertOrOtherErrorAsync(
return expectContractCallFailed(
signatureValidator.publicIsValidSignature.callAsync(
orderHashHex,
signedOrder.makerAddress,
@@ -115,7 +115,7 @@ describe('MixinSignatureValidator', () => {
const unsupportedSignatureType = SignatureType.NSignatureTypes;
const unsupportedSignatureHex = `0x${unsupportedSignatureType}`;
const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder);
return expectRevertOrOtherErrorAsync(
return expectContractCallFailed(
signatureValidator.publicIsValidSignature.callAsync(
orderHashHex,
signedOrder.makerAddress,
@@ -128,7 +128,7 @@ describe('MixinSignatureValidator', () => {
it('should revert when SignatureType=Illegal', async () => {
const unsupportedSignatureHex = `0x${SignatureType.Illegal}`;
const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder);
return expectRevertOrOtherErrorAsync(
return expectContractCallFailed(
signatureValidator.publicIsValidSignature.callAsync(
orderHashHex,
signedOrder.makerAddress,
@@ -155,7 +155,7 @@ describe('MixinSignatureValidator', () => {
const signatureBuffer = Buffer.concat([fillerData, signatureType]);
const signatureHex = ethUtil.bufferToHex(signatureBuffer);
const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder);
return expectRevertOrOtherErrorAsync(
return expectContractCallFailed(
signatureValidator.publicIsValidSignature.callAsync(
orderHashHex,
signedOrder.makerAddress,

View File

@@ -11,7 +11,7 @@ import { ExchangeContract } from '../../generated_contract_wrappers/exchange';
import { ExchangeWrapperContract } from '../../generated_contract_wrappers/exchange_wrapper';
import { WhitelistContract } from '../../generated_contract_wrappers/whitelist';
import { artifacts } from '../utils/artifacts';
import { expectRevertReasonOrAlwaysFailingTransactionAsync } from '../utils/assertions';
import { expectTransactionFailedAsync } from '../utils/assertions';
import { chaiSetup } from '../utils/chai_setup';
import { constants } from '../utils/constants';
import { ERC20Wrapper } from '../utils/erc20_wrapper';
@@ -134,7 +134,7 @@ describe('Exchange transactions', () => {
});
it('should throw if not called by specified sender', async () => {
return expectRevertReasonOrAlwaysFailingTransactionAsync(
return expectTransactionFailedAsync(
exchangeWrapper.executeTransactionAsync(signedTx, takerAddress),
RevertReason.FailedExecution,
);
@@ -177,7 +177,7 @@ describe('Exchange transactions', () => {
it('should throw if the a 0x transaction with the same transactionHash has already been executed', async () => {
await exchangeWrapper.executeTransactionAsync(signedTx, senderAddress);
return expectRevertReasonOrAlwaysFailingTransactionAsync(
return expectTransactionFailedAsync(
exchangeWrapper.executeTransactionAsync(signedTx, senderAddress),
RevertReason.InvalidTxHash,
);
@@ -197,7 +197,7 @@ describe('Exchange transactions', () => {
});
it('should throw if not called by specified sender', async () => {
return expectRevertReasonOrAlwaysFailingTransactionAsync(
return expectTransactionFailedAsync(
exchangeWrapper.executeTransactionAsync(signedTx, makerAddress),
RevertReason.FailedExecution,
);
@@ -205,7 +205,7 @@ describe('Exchange transactions', () => {
it('should cancel the order when signed by maker and called by sender', async () => {
await exchangeWrapper.executeTransactionAsync(signedTx, senderAddress);
return expectRevertReasonOrAlwaysFailingTransactionAsync(
return expectTransactionFailedAsync(
exchangeWrapper.fillOrderAsync(signedOrder, senderAddress),
RevertReason.OrderUnfillable,
);
@@ -250,7 +250,7 @@ describe('Exchange transactions', () => {
signedOrder.signature,
);
const signedFillTx = takerTransactionFactory.newSignedTransaction(fillData);
return expectRevertReasonOrAlwaysFailingTransactionAsync(
return expectTransactionFailedAsync(
exchangeWrapperContract.fillOrder.sendTransactionAsync(
orderWithoutExchangeAddress,
takerAssetFillAmount,
@@ -370,7 +370,7 @@ describe('Exchange transactions', () => {
orderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress(signedOrder);
const takerAssetFillAmount = signedOrder.takerAssetAmount;
const salt = generatePseudoRandomSalt();
return expectRevertReasonOrAlwaysFailingTransactionAsync(
return expectTransactionFailedAsync(
whitelist.fillOrderIfWhitelisted.sendTransactionAsync(
orderWithoutExchangeAddress,
takerAssetFillAmount,
@@ -392,7 +392,7 @@ describe('Exchange transactions', () => {
orderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress(signedOrder);
const takerAssetFillAmount = signedOrder.takerAssetAmount;
const salt = generatePseudoRandomSalt();
return expectRevertReasonOrAlwaysFailingTransactionAsync(
return expectTransactionFailedAsync(
whitelist.fillOrderIfWhitelisted.sendTransactionAsync(
orderWithoutExchangeAddress,
takerAssetFillAmount,

View File

@@ -12,7 +12,7 @@ import { ERC20ProxyContract } from '../../generated_contract_wrappers/e_r_c20_pr
import { ERC721ProxyContract } from '../../generated_contract_wrappers/e_r_c721_proxy';
import { ExchangeContract } from '../../generated_contract_wrappers/exchange';
import { artifacts } from '../utils/artifacts';
import { expectRevertReasonOrAlwaysFailingTransactionAsync } from '../utils/assertions';
import { expectTransactionFailedAsync } from '../utils/assertions';
import { chaiSetup } from '../utils/chai_setup';
import { constants } from '../utils/constants';
import { ERC20Wrapper } from '../utils/erc20_wrapper';
@@ -174,7 +174,7 @@ describe('Exchange wrappers', () => {
expirationTimeSeconds: new BigNumber(Math.floor((Date.now() - 10000) / 1000)),
});
return expectRevertReasonOrAlwaysFailingTransactionAsync(
return expectTransactionFailedAsync(
exchangeWrapper.fillOrKillOrderAsync(signedOrder, takerAddress),
RevertReason.OrderUnfillable,
);
@@ -187,7 +187,7 @@ describe('Exchange wrappers', () => {
takerAssetFillAmount: signedOrder.takerAssetAmount.div(2),
});
return expectRevertReasonOrAlwaysFailingTransactionAsync(
return expectTransactionFailedAsync(
exchangeWrapper.fillOrKillOrderAsync(signedOrder, takerAddress),
RevertReason.CompleteFillFailed,
);
@@ -500,7 +500,7 @@ describe('Exchange wrappers', () => {
await exchangeWrapper.fillOrKillOrderAsync(signedOrders[0], takerAddress);
return expectRevertReasonOrAlwaysFailingTransactionAsync(
return expectTransactionFailedAsync(
exchangeWrapper.batchFillOrKillOrdersAsync(signedOrders, takerAddress, {
takerAssetFillAmounts,
}),
@@ -703,7 +703,7 @@ describe('Exchange wrappers', () => {
orderFactory.newSignedOrder(),
];
return expectRevertReasonOrAlwaysFailingTransactionAsync(
return expectTransactionFailedAsync(
exchangeWrapper.marketSellOrdersAsync(signedOrders, takerAddress, {
takerAssetFillAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(1000), 18),
}),
@@ -921,7 +921,7 @@ describe('Exchange wrappers', () => {
orderFactory.newSignedOrder(),
];
return expectRevertReasonOrAlwaysFailingTransactionAsync(
return expectTransactionFailedAsync(
exchangeWrapper.marketBuyOrdersAsync(signedOrders, takerAddress, {
makerAssetFillAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(1000), 18),
}),

View File

@@ -9,7 +9,7 @@ import * as _ from 'lodash';
import { TestLibBytesContract } from '../../generated_contract_wrappers/test_lib_bytes';
import { artifacts } from '../utils/artifacts';
import { expectRevertOrOtherErrorAsync } from '../utils/assertions';
import { expectContractCallFailed } from '../utils/assertions';
import { chaiSetup } from '../utils/chai_setup';
import { constants } from '../utils/constants';
import { provider, txDefaults, web3Wrapper } from '../utils/web3_wrapper';
@@ -100,7 +100,7 @@ describe('LibBytes', () => {
describe('popLastByte', () => {
it('should revert if length is 0', async () => {
return expectRevertOrOtherErrorAsync(
return expectContractCallFailed(
libBytes.publicPopLastByte.callAsync(constants.NULL_BYTES),
RevertReason.LibBytesGreaterThanZeroLengthRequired,
);
@@ -116,7 +116,7 @@ describe('LibBytes', () => {
describe('popLast20Bytes', () => {
it('should revert if length is less than 20', async () => {
return expectRevertOrOtherErrorAsync(
return expectContractCallFailed(
libBytes.publicPopLast20Bytes.callAsync(byteArrayShorterThan20Bytes),
RevertReason.LibBytesGreaterOrEqualTo20LengthRequired,
);
@@ -184,7 +184,7 @@ describe('LibBytes', () => {
describe('deepCopyBytes', () => {
it('should revert if dest is shorter than source', async () => {
return expectRevertOrOtherErrorAsync(
return expectContractCallFailed(
libBytes.publicDeepCopyBytes.callAsync(byteArrayShorterThan32Bytes, byteArrayLongerThan32Bytes),
RevertReason.LibBytesGreaterOrEqualToSourceBytesLengthRequired,
);
@@ -237,7 +237,7 @@ describe('LibBytes', () => {
it('should fail if the byte array is too short to hold an address', async () => {
const shortByteArray = '0xabcdef';
const offset = new BigNumber(0);
return expectRevertOrOtherErrorAsync(
return expectContractCallFailed(
libBytes.publicReadAddress.callAsync(shortByteArray, offset),
RevertReason.LibBytesGreaterOrEqualTo20LengthRequired,
);
@@ -245,7 +245,7 @@ describe('LibBytes', () => {
it('should fail if the length between the offset and end of the byte array is too short to hold an address', async () => {
const byteArray = testAddress;
const badOffset = new BigNumber(ethUtil.toBuffer(byteArray).byteLength);
return expectRevertOrOtherErrorAsync(
return expectContractCallFailed(
libBytes.publicReadAddress.callAsync(byteArray, badOffset),
RevertReason.LibBytesGreaterOrEqualTo20LengthRequired,
);
@@ -281,7 +281,7 @@ describe('LibBytes', () => {
});
it('should fail if the byte array is too short to hold an address', async () => {
const offset = new BigNumber(0);
return expectRevertOrOtherErrorAsync(
return expectContractCallFailed(
libBytes.publicWriteAddress.callAsync(byteArrayShorterThan20Bytes, offset, testAddress),
RevertReason.LibBytesGreaterOrEqualTo20LengthRequired,
);
@@ -289,7 +289,7 @@ describe('LibBytes', () => {
it('should fail if the length between the offset and end of the byte array is too short to hold an address', async () => {
const byteArray = byteArrayLongerThan32Bytes;
const badOffset = new BigNumber(ethUtil.toBuffer(byteArray).byteLength);
return expectRevertOrOtherErrorAsync(
return expectContractCallFailed(
libBytes.publicWriteAddress.callAsync(byteArray, badOffset, testAddress),
RevertReason.LibBytesGreaterOrEqualTo20LengthRequired,
);
@@ -313,14 +313,14 @@ describe('LibBytes', () => {
});
it('should fail if the byte array is too short to hold a bytes32', async () => {
const offset = new BigNumber(0);
return expectRevertOrOtherErrorAsync(
return expectContractCallFailed(
libBytes.publicReadBytes32.callAsync(byteArrayShorterThan32Bytes, offset),
RevertReason.LibBytesGreaterOrEqualTo32LengthRequired,
);
});
it('should fail if the length between the offset and end of the byte array is too short to hold a bytes32', async () => {
const badOffset = new BigNumber(ethUtil.toBuffer(testBytes32).byteLength);
return expectRevertOrOtherErrorAsync(
return expectContractCallFailed(
libBytes.publicReadBytes32.callAsync(testBytes32, badOffset),
RevertReason.LibBytesGreaterOrEqualTo32LengthRequired,
);
@@ -356,7 +356,7 @@ describe('LibBytes', () => {
});
it('should fail if the byte array is too short to hold a bytes32', async () => {
const offset = new BigNumber(0);
return expectRevertOrOtherErrorAsync(
return expectContractCallFailed(
libBytes.publicWriteBytes32.callAsync(byteArrayShorterThan32Bytes, offset, testBytes32),
RevertReason.LibBytesGreaterOrEqualTo32LengthRequired,
);
@@ -364,7 +364,7 @@ describe('LibBytes', () => {
it('should fail if the length between the offset and end of the byte array is too short to hold a bytes32', async () => {
const byteArray = byteArrayLongerThan32Bytes;
const badOffset = new BigNumber(ethUtil.toBuffer(byteArray).byteLength);
return expectRevertOrOtherErrorAsync(
return expectContractCallFailed(
libBytes.publicWriteBytes32.callAsync(byteArray, badOffset, testBytes32),
RevertReason.LibBytesGreaterOrEqualTo32LengthRequired,
);
@@ -392,7 +392,7 @@ describe('LibBytes', () => {
});
it('should fail if the byte array is too short to hold a uint256', async () => {
const offset = new BigNumber(0);
return expectRevertOrOtherErrorAsync(
return expectContractCallFailed(
libBytes.publicReadUint256.callAsync(byteArrayShorterThan32Bytes, offset),
RevertReason.LibBytesGreaterOrEqualTo32LengthRequired,
);
@@ -402,7 +402,7 @@ describe('LibBytes', () => {
const testUint256AsBuffer = ethUtil.toBuffer(formattedTestUint256);
const byteArray = ethUtil.bufferToHex(testUint256AsBuffer);
const badOffset = new BigNumber(testUint256AsBuffer.byteLength);
return expectRevertOrOtherErrorAsync(
return expectContractCallFailed(
libBytes.publicReadUint256.callAsync(byteArray, badOffset),
RevertReason.LibBytesGreaterOrEqualTo32LengthRequired,
);
@@ -442,7 +442,7 @@ describe('LibBytes', () => {
});
it('should fail if the byte array is too short to hold a uint256', async () => {
const offset = new BigNumber(0);
return expectRevertOrOtherErrorAsync(
return expectContractCallFailed(
libBytes.publicWriteUint256.callAsync(byteArrayShorterThan32Bytes, offset, testUint256),
RevertReason.LibBytesGreaterOrEqualTo32LengthRequired,
);
@@ -450,7 +450,7 @@ describe('LibBytes', () => {
it('should fail if the length between the offset and end of the byte array is too short to hold a uint256', async () => {
const byteArray = byteArrayLongerThan32Bytes;
const badOffset = new BigNumber(ethUtil.toBuffer(byteArray).byteLength);
return expectRevertOrOtherErrorAsync(
return expectContractCallFailed(
libBytes.publicWriteUint256.callAsync(byteArray, badOffset, testUint256),
RevertReason.LibBytesGreaterOrEqualTo32LengthRequired,
);
@@ -461,7 +461,7 @@ describe('LibBytes', () => {
// AssertionError: expected promise to be rejected with an error including 'revert' but it was fulfilled with '0x08c379a0'
it('should revert if byte array has a length < 4', async () => {
const byteArrayLessThan4Bytes = '0x010101';
return expectRevertOrOtherErrorAsync(
return expectContractCallFailed(
libBytes.publicReadBytes4.callAsync(byteArrayLessThan4Bytes, new BigNumber(0)),
RevertReason.LibBytesGreaterOrEqualTo4LengthRequired,
);
@@ -516,28 +516,28 @@ describe('LibBytes', () => {
it('should fail if the byte array is too short to hold the length of a nested byte array', async () => {
// The length of the nested array is 32 bytes. By storing less than 32 bytes, a length cannot be read.
const offset = new BigNumber(0);
return expectRevertOrOtherErrorAsync(
return expectContractCallFailed(
libBytes.publicReadBytesWithLength.callAsync(byteArrayShorterThan32Bytes, offset),
RevertReason.LibBytesGreaterOrEqualTo32LengthRequired,
);
});
it('should fail if we store a nested byte array length, without a nested byte array', async () => {
const offset = new BigNumber(0);
return expectRevertOrOtherErrorAsync(
return expectContractCallFailed(
libBytes.publicReadBytesWithLength.callAsync(testBytes32, offset),
RevertReason.LibBytesGreaterOrEqualToNestedBytesLengthRequired,
);
});
it('should fail if the length between the offset and end of the byte array is too short to hold the length of a nested byte array', async () => {
const badOffset = new BigNumber(ethUtil.toBuffer(byteArrayShorterThan32Bytes).byteLength);
return expectRevertOrOtherErrorAsync(
return expectContractCallFailed(
libBytes.publicReadBytesWithLength.callAsync(byteArrayShorterThan32Bytes, badOffset),
RevertReason.LibBytesGreaterOrEqualTo32LengthRequired,
);
});
it('should fail if the length between the offset and end of the byte array is too short to hold the nested byte array', async () => {
const badOffset = new BigNumber(ethUtil.toBuffer(testBytes32).byteLength);
return expectRevertOrOtherErrorAsync(
return expectContractCallFailed(
libBytes.publicReadBytesWithLength.callAsync(testBytes32, badOffset),
RevertReason.LibBytesGreaterOrEqualTo32LengthRequired,
);
@@ -649,7 +649,7 @@ describe('LibBytes', () => {
it('should fail if the byte array is too short to hold the length of a nested byte array', async () => {
const offset = new BigNumber(0);
const emptyByteArray = ethUtil.bufferToHex(new Buffer(1));
return expectRevertOrOtherErrorAsync(
return expectContractCallFailed(
libBytes.publicWriteBytesWithLength.callAsync(emptyByteArray, offset, longData),
RevertReason.LibBytesGreaterOrEqualToNestedBytesLengthRequired,
);
@@ -657,7 +657,7 @@ describe('LibBytes', () => {
it('should fail if the length between the offset and end of the byte array is too short to hold the length of a nested byte array)', async () => {
const emptyByteArray = ethUtil.bufferToHex(new Buffer(shortTestBytesAsBuffer.byteLength));
const badOffset = new BigNumber(ethUtil.toBuffer(shortTestBytesAsBuffer).byteLength);
return expectRevertOrOtherErrorAsync(
return expectContractCallFailed(
libBytes.publicWriteBytesWithLength.callAsync(emptyByteArray, badOffset, shortData),
RevertReason.LibBytesGreaterOrEqualToNestedBytesLengthRequired,
);

View File

@@ -14,8 +14,9 @@ import { MixinAuthorizableContract } from '../../generated_contract_wrappers/mix
import { TestAssetProxyOwnerContract } from '../../generated_contract_wrappers/test_asset_proxy_owner';
import { artifacts } from '../utils/artifacts';
import {
expectRevertOrAlwaysFailingTransactionAsync,
expectRevertOrContractCallFailedAsync,
expectContractCallFailedWithoutReasonAsync,
expectContractCreationFailedWithoutReason,
expectTransactionFailedWithoutReasonAsync,
} from '../utils/assertions';
import { chaiSetup } from '../utils/chai_setup';
import { constants } from '../utils/constants';
@@ -108,7 +109,7 @@ describe('AssetProxyOwner', () => {
});
it('should throw if a null address is included in assetProxyContracts', async () => {
const assetProxyContractAddresses = [erc20Proxy.address, constants.NULL_ADDRESS];
return expectRevertOrAlwaysFailingTransactionAsync(
return expectContractCreationFailedWithoutReason(
AssetProxyOwnerContract.deployFrom0xArtifactAsync(
artifacts.AssetProxyOwner,
provider,
@@ -150,7 +151,7 @@ describe('AssetProxyOwner', () => {
describe('registerAssetProxy', () => {
it('should throw if not called by multisig', async () => {
const isRegistered = true;
return expectRevertOrAlwaysFailingTransactionAsync(
return expectTransactionFailedWithoutReasonAsync(
testAssetProxyOwner.registerAssetProxy.sendTransactionAsync(erc20Proxy.address, isRegistered, {
from: owners[0],
}),
@@ -277,7 +278,7 @@ describe('AssetProxyOwner', () => {
);
const log = submitTxRes.logs[0] as LogWithDecodedArgs<SubmissionContractEventArgs>;
const txId = log.args.transactionId;
return expectRevertOrContractCallFailedAsync(
return expectContractCallFailedWithoutReasonAsync(
testAssetProxyOwner.testValidRemoveAuthorizedAddressAtIndexTx.callAsync(txId),
);
});
@@ -312,7 +313,7 @@ describe('AssetProxyOwner', () => {
);
const log = submitTxRes.logs[0] as LogWithDecodedArgs<SubmissionContractEventArgs>;
const txId = log.args.transactionId;
return expectRevertOrContractCallFailedAsync(
return expectContractCallFailedWithoutReasonAsync(
testAssetProxyOwner.testValidRemoveAuthorizedAddressAtIndexTx.callAsync(txId),
);
});
@@ -332,7 +333,7 @@ describe('AssetProxyOwner', () => {
const log = res.logs[0] as LogWithDecodedArgs<SubmissionContractEventArgs>;
const txId = log.args.transactionId;
return expectRevertOrAlwaysFailingTransactionAsync(
return expectTransactionFailedWithoutReasonAsync(
testAssetProxyOwner.executeRemoveAuthorizedAddressAtIndex.sendTransactionAsync(txId, {
from: owners[1],
}),
@@ -354,7 +355,7 @@ describe('AssetProxyOwner', () => {
await multiSigWrapper.confirmTransactionAsync(txId, owners[1]);
return expectRevertOrAlwaysFailingTransactionAsync(
return expectTransactionFailedWithoutReasonAsync(
testAssetProxyOwner.executeRemoveAuthorizedAddressAtIndex.sendTransactionAsync(txId, {
from: owners[1],
}),
@@ -376,7 +377,7 @@ describe('AssetProxyOwner', () => {
await multiSigWrapper.confirmTransactionAsync(txId, owners[1]);
return expectRevertOrAlwaysFailingTransactionAsync(
return expectTransactionFailedWithoutReasonAsync(
testAssetProxyOwner.executeRemoveAuthorizedAddressAtIndex.sendTransactionAsync(txId, {
from: owners[1],
}),
@@ -433,7 +434,7 @@ describe('AssetProxyOwner', () => {
const isExecuted = tx[3];
expect(isExecuted).to.equal(true);
return expectRevertOrAlwaysFailingTransactionAsync(
return expectTransactionFailedWithoutReasonAsync(
testAssetProxyOwner.executeRemoveAuthorizedAddressAtIndex.sendTransactionAsync(txId, {
from: owners[1],
}),

View File

@@ -8,7 +8,7 @@ import {
SubmissionContractEventArgs,
} from '../../generated_contract_wrappers/multi_sig_wallet_with_time_lock';
import { artifacts } from '../utils/artifacts';
import { expectRevertOrAlwaysFailingTransactionAsync } from '../utils/assertions';
import { expectTransactionFailedWithoutReasonAsync } from '../utils/assertions';
import { chaiSetup } from '../utils/chai_setup';
import { constants } from '../utils/constants';
import { increaseTimeAndMineBlockAsync } from '../utils/increase_time';
@@ -67,7 +67,7 @@ describe('MultiSigWalletWithTimeLock', () => {
});
it('should throw when not called by wallet', async () => {
return expectRevertOrAlwaysFailingTransactionAsync(
return expectTransactionFailedWithoutReasonAsync(
multiSig.changeTimeLock.sendTransactionAsync(SECONDS_TIME_LOCKED, { from: owners[0] }),
);
});
@@ -78,7 +78,7 @@ describe('MultiSigWalletWithTimeLock', () => {
const res = await multiSigWrapper.submitTransactionAsync(destination, changeTimeLockData, owners[0]);
const log = res.logs[0] as LogWithDecodedArgs<SubmissionContractEventArgs>;
const txId = log.args.transactionId;
return expectRevertOrAlwaysFailingTransactionAsync(
return expectTransactionFailedWithoutReasonAsync(
multiSig.executeTransaction.sendTransactionAsync(txId, { from: owners[0] }),
);
});
@@ -147,7 +147,7 @@ describe('MultiSigWalletWithTimeLock', () => {
});
it('should throw if it has enough confirmations but is not past the time lock', async () => {
return expectRevertOrAlwaysFailingTransactionAsync(
return expectTransactionFailedWithoutReasonAsync(
multiSig.executeTransaction.sendTransactionAsync(txId, { from: owners[0] }),
);
});

View File

@@ -7,7 +7,7 @@ import * as _ from 'lodash';
import { TokenRegistryContract } from '../generated_contract_wrappers/token_registry';
import { artifacts } from './utils/artifacts';
import { expectRevertOrAlwaysFailingTransactionAsync } from './utils/assertions';
import { expectTransactionFailedWithoutReasonAsync } from './utils/assertions';
import { chaiSetup } from './utils/chai_setup';
import { constants } from './utils/constants';
import { TokenRegWrapper } from './utils/token_registry_wrapper';
@@ -75,7 +75,7 @@ describe('TokenRegistry', () => {
describe('addToken', () => {
it('should throw when not called by owner', async () => {
return expectRevertOrAlwaysFailingTransactionAsync(tokenRegWrapper.addTokenAsync(token1, notOwner));
return expectTransactionFailedWithoutReasonAsync(tokenRegWrapper.addTokenAsync(token1, notOwner));
});
it('should add token metadata when called by owner', async () => {
@@ -87,20 +87,18 @@ describe('TokenRegistry', () => {
it('should throw if token already exists', async () => {
await tokenRegWrapper.addTokenAsync(token1, owner);
return expectRevertOrAlwaysFailingTransactionAsync(tokenRegWrapper.addTokenAsync(token1, owner));
return expectTransactionFailedWithoutReasonAsync(tokenRegWrapper.addTokenAsync(token1, owner));
});
it('should throw if token address is null', async () => {
return expectRevertOrAlwaysFailingTransactionAsync(tokenRegWrapper.addTokenAsync(nullToken, owner));
return expectTransactionFailedWithoutReasonAsync(tokenRegWrapper.addTokenAsync(nullToken, owner));
});
it('should throw if name already exists', async () => {
await tokenRegWrapper.addTokenAsync(token1, owner);
const duplicateNameToken = _.assign({}, token2, { name: token1.name });
return expectRevertOrAlwaysFailingTransactionAsync(
tokenRegWrapper.addTokenAsync(duplicateNameToken, owner),
);
return expectTransactionFailedWithoutReasonAsync(tokenRegWrapper.addTokenAsync(duplicateNameToken, owner));
});
it('should throw if symbol already exists', async () => {
@@ -109,7 +107,7 @@ describe('TokenRegistry', () => {
symbol: token1.symbol,
});
return expectRevertOrAlwaysFailingTransactionAsync(
return expectTransactionFailedWithoutReasonAsync(
tokenRegWrapper.addTokenAsync(duplicateSymbolToken, owner),
);
});
@@ -136,7 +134,7 @@ describe('TokenRegistry', () => {
describe('setTokenName', () => {
it('should throw when not called by owner', async () => {
return expectRevertOrAlwaysFailingTransactionAsync(
return expectTransactionFailedWithoutReasonAsync(
tokenReg.setTokenName.sendTransactionAsync(token1.address, token2.name, { from: notOwner }),
);
});
@@ -162,13 +160,13 @@ describe('TokenRegistry', () => {
it('should throw if the name already exists', async () => {
await tokenRegWrapper.addTokenAsync(token2, owner);
return expectRevertOrAlwaysFailingTransactionAsync(
return expectTransactionFailedWithoutReasonAsync(
tokenReg.setTokenName.sendTransactionAsync(token1.address, token2.name, { from: owner }),
);
});
it('should throw if token does not exist', async () => {
return expectRevertOrAlwaysFailingTransactionAsync(
return expectTransactionFailedWithoutReasonAsync(
tokenReg.setTokenName.sendTransactionAsync(nullToken.address, token2.name, { from: owner }),
);
});
@@ -176,7 +174,7 @@ describe('TokenRegistry', () => {
describe('setTokenSymbol', () => {
it('should throw when not called by owner', async () => {
return expectRevertOrAlwaysFailingTransactionAsync(
return expectTransactionFailedWithoutReasonAsync(
tokenReg.setTokenSymbol.sendTransactionAsync(token1.address, token2.symbol, {
from: notOwner,
}),
@@ -202,7 +200,7 @@ describe('TokenRegistry', () => {
it('should throw if the symbol already exists', async () => {
await tokenRegWrapper.addTokenAsync(token2, owner);
return expectRevertOrAlwaysFailingTransactionAsync(
return expectTransactionFailedWithoutReasonAsync(
tokenReg.setTokenSymbol.sendTransactionAsync(token1.address, token2.symbol, {
from: owner,
}),
@@ -210,7 +208,7 @@ describe('TokenRegistry', () => {
});
it('should throw if token does not exist', async () => {
return expectRevertOrAlwaysFailingTransactionAsync(
return expectTransactionFailedWithoutReasonAsync(
tokenReg.setTokenSymbol.sendTransactionAsync(nullToken.address, token2.symbol, {
from: owner,
}),
@@ -221,7 +219,7 @@ describe('TokenRegistry', () => {
describe('removeToken', () => {
it('should throw if not called by owner', async () => {
const index = new BigNumber(0);
return expectRevertOrAlwaysFailingTransactionAsync(
return expectTransactionFailedWithoutReasonAsync(
tokenReg.removeToken.sendTransactionAsync(token1.address, index, { from: notOwner }),
);
});
@@ -240,7 +238,7 @@ describe('TokenRegistry', () => {
it('should throw if token does not exist', async () => {
const index = new BigNumber(0);
return expectRevertOrAlwaysFailingTransactionAsync(
return expectTransactionFailedWithoutReasonAsync(
tokenReg.removeToken.sendTransactionAsync(nullToken.address, index, { from: owner }),
);
});
@@ -248,7 +246,7 @@ describe('TokenRegistry', () => {
it('should throw if token at given index does not match address', async () => {
await tokenRegWrapper.addTokenAsync(token2, owner);
const incorrectIndex = new BigNumber(0);
return expectRevertOrAlwaysFailingTransactionAsync(
return expectTransactionFailedWithoutReasonAsync(
tokenReg.removeToken.sendTransactionAsync(token2.address, incorrectIndex, { from: owner }),
);
});

View File

@@ -5,7 +5,7 @@ import * as chai from 'chai';
import { WETH9Contract } from '../../generated_contract_wrappers/weth9';
import { artifacts } from '../utils/artifacts';
import { expectInsufficientFundsAsync, expectRevertOrAlwaysFailingTransactionAsync } from '../utils/assertions';
import { expectInsufficientFundsAsync, expectTransactionFailedWithoutReasonAsync } from '../utils/assertions';
import { chaiSetup } from '../utils/chai_setup';
import { constants } from '../utils/constants';
import { provider, txDefaults, web3Wrapper } from '../utils/web3_wrapper';
@@ -74,7 +74,7 @@ describe('EtherToken', () => {
const initEthTokenBalance = await etherToken.balanceOf.callAsync(account);
const ethTokensToWithdraw = initEthTokenBalance.plus(1);
return expectRevertOrAlwaysFailingTransactionAsync(
return expectTransactionFailedWithoutReasonAsync(
etherToken.withdraw.sendTransactionAsync(ethTokensToWithdraw),
);
});

View File

@@ -5,7 +5,7 @@ import * as chai from 'chai';
import { DummyERC20TokenContract } from '../../generated_contract_wrappers/dummy_e_r_c20_token';
import { artifacts } from '../utils/artifacts';
import { expectRevertOrOtherErrorAsync } from '../utils/assertions';
import { expectContractCallFailed } from '../utils/assertions';
import { chaiSetup } from '../utils/chai_setup';
import { constants } from '../utils/constants';
import { provider, txDefaults, web3Wrapper } from '../utils/web3_wrapper';
@@ -54,7 +54,7 @@ describe('UnlimitedAllowanceToken', () => {
it('should throw if owner has insufficient balance', async () => {
const ownerBalance = await token.balanceOf.callAsync(owner);
const amountToTransfer = ownerBalance.plus(1);
return expectRevertOrOtherErrorAsync(
return expectContractCallFailed(
token.transfer.callAsync(spender, amountToTransfer, { from: owner }),
RevertReason.Erc20InsufficientBalance,
);
@@ -93,7 +93,7 @@ describe('UnlimitedAllowanceToken', () => {
await token.approve.sendTransactionAsync(spender, amountToTransfer, { from: owner }),
constants.AWAIT_TRANSACTION_MINED_MS,
);
return expectRevertOrOtherErrorAsync(
return expectContractCallFailed(
token.transferFrom.callAsync(owner, spender, amountToTransfer, {
from: spender,
}),
@@ -109,7 +109,7 @@ describe('UnlimitedAllowanceToken', () => {
const isSpenderAllowanceInsufficient = spenderAllowance.cmp(amountToTransfer) < 0;
expect(isSpenderAllowanceInsufficient).to.be.true();
return expectRevertOrOtherErrorAsync(
return expectContractCallFailed(
token.transferFrom.callAsync(owner, spender, amountToTransfer, {
from: spender,
}),

View File

@@ -1,108 +1,159 @@
import { RevertReason } from '@0xproject/types';
import { logUtils } from '@0xproject/utils';
import { NodeType } from '@0xproject/web3-wrapper';
import * as chai from 'chai';
import { TransactionReceipt, TransactionReceiptWithDecodedLogs } from 'ethereum-types';
import { TransactionReceipt, TransactionReceiptStatus, TransactionReceiptWithDecodedLogs } from 'ethereum-types';
import * as _ from 'lodash';
import { constants } from './constants';
import { web3Wrapper } from './web3_wrapper';
const expect = chai.expect;
function _expectEitherErrorAsync<T>(p: Promise<T>, error1: string, error2: string): PromiseLike<void> {
return expect(p)
.to.be.rejected()
.then(e => {
expect(e).to.satisfy(
(err: Error) => _.includes(err.message, error1) || _.includes(err.message, error2),
`expected promise to reject with error message that includes "${error1}" or "${error2}", but got: ` +
`"${e.message}"\n`,
);
});
let nodeType: NodeType | undefined;
// Represents the return value of a `sendTransaction` call. The Promise should
// resolve with either a transaction receipt or a transaction hash.
export type sendTransactionResult = Promise<TransactionReceipt | TransactionReceiptWithDecodedLogs | string>;
async function _getGanacheOrGethError(ganacheError: string, gethError: string): Promise<string> {
if (_.isUndefined(nodeType)) {
nodeType = await web3Wrapper.getNodeTypeAsync();
}
switch (nodeType) {
case NodeType.Ganache:
return ganacheError;
case NodeType.Geth:
return gethError;
default:
throw new Error(`Unknown node type: ${nodeType}`);
}
}
async function _getInsufficientFundsErrorMessageAsync(): Promise<string> {
return _getGanacheOrGethError("sender doesn't have enough funds", 'insufficient funds');
}
async function _getTransactionFailedErrorMessageAsync(): Promise<string> {
return _getGanacheOrGethError('revert', 'always failing transaction');
}
async function _getContractCallFailedErrorMessageAsync(): Promise<string> {
return _getGanacheOrGethError('revert', 'Contract call failed');
}
/**
* Rejects if the given Promise does not reject with an error indicating
* insufficient funds.
* @param p the Promise which is expected to reject
* @param p a promise resulting from a contract call or sendTransaction call.
* @returns a new Promise which will reject if the conditions are not met and
* otherwise resolve with no value.
*/
export function expectInsufficientFundsAsync<T>(p: Promise<T>): PromiseLike<void> {
return _expectEitherErrorAsync(p, 'insufficient funds', "sender doesn't have enough funds");
export async function expectInsufficientFundsAsync<T>(p: Promise<T>): Promise<void> {
const errMessage = await _getInsufficientFundsErrorMessageAsync();
return expect(p).to.be.rejectedWith(errMessage);
}
/**
* Rejects if the given Promise does not reject with a "revert" error or the
* given otherError.
* @param p the Promise which is expected to reject
* @param otherError the other error which is accepted as a valid reject error.
* @returns a new Promise which will reject if the conditions are not met and
* otherwise resolve with no value.
*/
export function expectRevertOrOtherErrorAsync<T>(p: Promise<T>, otherError: string): PromiseLike<void> {
return _expectEitherErrorAsync(p, constants.REVERT, otherError);
}
/**
* Rejects if the given Promise does not reject with a "revert" or "always
* failing transaction" error.
* @param p the Promise which is expected to reject
* @returns a new Promise which will reject if the conditions are not met and
* otherwise resolve with no value.
*/
export function expectRevertOrAlwaysFailingTransactionAsync<T>(p: Promise<T>): PromiseLike<void> {
return expectRevertOrOtherErrorAsync(p, 'always failing transaction');
}
/**
* Rejects if at least one the following conditions is not met:
* 1) The given Promise rejects with the given revert reason.
* 2) The given Promise rejects with an error containing "always failing transaction"
* 3) The given Promise fulfills with a txReceipt that has a status of 0 or '0', indicating the transaction failed.
* 4) The given Promise fulfills with a txHash and corresponding txReceipt has a status of 0 or '0'.
* @param p the Promise which is expected to reject
* Resolves if the the sendTransaction call fails with the given revert reason.
* However, since Geth does not support revert reasons for sendTransaction, this
* falls back to expectTransactionFailedWithoutReasonAsync if the backing
* Ethereum node is Geth.
* @param p a Promise resulting from a sendTransaction call
* @param reason a specific revert reason
* @returns a new Promise which will reject if the conditions are not met and
* otherwise resolve with no value.
*/
export async function expectRevertReasonOrAlwaysFailingTransactionAsync(
p: Promise<string | TransactionReceiptWithDecodedLogs | TransactionReceipt>,
reason: RevertReason,
): Promise<void> {
export async function expectTransactionFailedAsync(p: sendTransactionResult, reason: RevertReason): Promise<void> {
// HACK(albrow): This dummy `catch` should not be necessary, but if you
// remove it, there is an uncaught exception and the Node process will
// forcibly exit. It's possible this is a false positive in
// make-promises-safe.
p.catch(e => {
_.noop(e);
});
if (_.isUndefined(nodeType)) {
nodeType = await web3Wrapper.getNodeTypeAsync();
}
switch (nodeType) {
case NodeType.Ganache:
return expect(p).to.be.rejectedWith(reason);
case NodeType.Geth:
logUtils.warn(
'WARNING: Geth does not support revert reasons for sendTransaction. This test will pass if the transaction fails for any reason.',
);
return expectTransactionFailedWithoutReasonAsync(p);
default:
throw new Error(`Unknown node type: ${nodeType}`);
}
}
/**
* Resolves if the transaction fails without a revert reason, or if the
* corresponding transactionReceipt has a status of 0 or '0', indicating
* failure.
* @param p a Promise resulting from a sendTransaction call
* @returns a new Promise which will reject if the conditions are not met and
* otherwise resolve with no value.
*/
export async function expectTransactionFailedWithoutReasonAsync(p: sendTransactionResult): Promise<void> {
return p
.then(async result => {
let txReceiptStatus: string | 0 | 1 | null;
if (typeof result === 'string') {
// Result is a txHash. We need to make a web3 call to get the receipt.
let txReceiptStatus: TransactionReceiptStatus;
if (_.isString(result)) {
// Result is a txHash. We need to make a web3 call to get the
// receipt, then get the status from the receipt.
const txReceipt = await web3Wrapper.awaitTransactionMinedAsync(result);
txReceiptStatus = txReceipt.status;
} else if ('status' in result) {
// Result is a TransactionReceiptWithDecodedLogs or TransactionReceipt
// and status is a field of result.
// Result is a transaction receipt, so we can get the status
// directly.
txReceiptStatus = result.status;
} else {
throw new Error('Unexpected result type');
throw new Error('Unexpected result type: ' + typeof result);
}
expect(_.toString(txReceiptStatus)).to.equal(
'0',
'transactionReceipt had a non-zero status, indicating success',
'Expected transaction to fail but receipt had a non-zero status, indicating success',
);
})
.catch(err => {
expect(err.message).to.satisfy(
(msg: string) => _.includes(msg, reason) || _.includes(msg, 'always failing transaction'),
`Expected ${reason} or 'always failing transaction' but error message was ${err.message}`,
);
.catch(async err => {
// If the promise rejects, we expect a specific error message,
// depending on the backing Ethereum node type.
const errMessage = await _getTransactionFailedErrorMessageAsync();
expect(err.message).to.include(errMessage);
});
}
/**
* Rejects if the given Promise does not reject with a "revert" or "Contract
* call failed" error.
* @param p the Promise which is expected to reject
* Resolves if the the contract call fails with the given revert reason.
* @param p a Promise resulting from a contract call
* @param reason a specific revert reason
* @returns a new Promise which will reject if the conditions are not met and
* otherwise resolve with no value.
*/
export function expectRevertOrContractCallFailedAsync<T>(p: Promise<T>): PromiseLike<void> {
return expectRevertOrOtherErrorAsync<T>(p, 'Contract call failed');
export async function expectContractCallFailed<T>(p: Promise<T>, reason: RevertReason): Promise<void> {
return expect(p).to.be.rejectedWith(reason);
}
/**
* Resolves if the contract call fails without a revert reason.
* @param p a Promise resulting from a contract call
* @returns a new Promise which will reject if the conditions are not met and
* otherwise resolve with no value.
*/
export async function expectContractCallFailedWithoutReasonAsync<T>(p: Promise<T>): Promise<void> {
const errMessage = await _getContractCallFailedErrorMessageAsync();
return expect(p).to.be.rejectedWith(errMessage);
}
/**
* Resolves if the contract creation/deployment fails without a revert reason.
* @param p a Promise resulting from a contract creation/deployment
* @returns a new Promise which will reject if the conditions are not met and
* otherwise resolve with no value.
*/
export async function expectContractCreationFailedWithoutReason<T>(p: Promise<T>): Promise<void> {
const errMessage = await _getTransactionFailedErrorMessageAsync();
return expect(p).to.be.rejectedWith(errMessage);
}

View File

@@ -18,7 +18,6 @@ const TESTRPC_PRIVATE_KEYS_STRINGS = [
export const constants = {
INVALID_OPCODE: 'invalid opcode',
REVERT: 'revert',
TESTRPC_NETWORK_ID: 50,
// Note(albrow): In practice V8 and most other engines limit the minimum
// interval for setInterval to 10ms. We still set it to 0 here in order to

View File

@@ -17,7 +17,7 @@ import 'make-promises-safe';
import { ExchangeContract, FillContractEventArgs } from '../../generated_contract_wrappers/exchange';
import { artifacts } from './artifacts';
import { expectRevertReasonOrAlwaysFailingTransactionAsync } from './assertions';
import { expectTransactionFailedAsync } from './assertions';
import { AssetWrapper } from './asset_wrapper';
import { chaiSetup } from './chai_setup';
import { constants } from './constants';
@@ -418,7 +418,7 @@ export class CoreCombinatorialUtils {
fillRevertReasonIfExists: RevertReason | undefined,
): Promise<void> {
if (!_.isUndefined(fillRevertReasonIfExists)) {
return expectRevertReasonOrAlwaysFailingTransactionAsync(
return expectTransactionFailedAsync(
this.exchangeWrapper.fillOrderAsync(signedOrder, this.takerAddress, { takerAssetFillAmount }),
fillRevertReasonIfExists,
);

View File

@@ -53,7 +53,7 @@
"@0xproject/web3-wrapper": "^0.7.1",
"lodash": "^4.17.4",
"web3": "^0.20.0",
"web3-provider-engine": "^14.0.4"
"web3-provider-engine": "14.0.6"
},
"publishConfig": {
"access": "public"

View File

@@ -1,11 +1,6 @@
import { logUtils } from '@0xproject/utils';
import { uniqueVersionIds, Web3Wrapper } from '@0xproject/web3-wrapper';
import { includes } from 'lodash';
enum NodeType {
Geth = 'GETH',
Ganache = 'GANACHE',
}
import { NodeType, Web3Wrapper } from '@0xproject/web3-wrapper';
import * as _ from 'lodash';
// HACK(albrow): 🐉 We have to do this so that debug.setHead works correctly.
// (Geth does not seem to like debug.setHead(0), so by sending some transactions
@@ -18,6 +13,7 @@ export class BlockchainLifecycle {
private _web3Wrapper: Web3Wrapper;
private _snapshotIdsStack: number[];
private _addresses: string[] = [];
private _nodeType: NodeType | undefined;
constructor(web3Wrapper: Web3Wrapper) {
this._web3Wrapper = web3Wrapper;
this._snapshotIdsStack = [];
@@ -61,16 +57,6 @@ export class BlockchainLifecycle {
throw new Error(`Unknown node type: ${nodeType}`);
}
}
private async _getNodeTypeAsync(): Promise<NodeType> {
const version = await this._web3Wrapper.getNodeVersionAsync();
if (includes(version, uniqueVersionIds.geth)) {
return NodeType.Geth;
} else if (includes(version, uniqueVersionIds.ganache)) {
return NodeType.Ganache;
} else {
throw new Error(`Unknown client version: ${version}`);
}
}
private async _mineMinimumBlocksAsync(): Promise<void> {
logUtils.warn('WARNING: minimum block number for tests not met. Mining additional blocks...');
if (this._addresses.length === 0) {
@@ -92,4 +78,10 @@ export class BlockchainLifecycle {
}
logUtils.warn('Done mining the minimum number of blocks.');
}
private async _getNodeTypeAsync(): Promise<NodeType> {
if (_.isUndefined(this._nodeType)) {
this._nodeType = await this._web3Wrapper.getNodeTypeAsync();
}
return this._nodeType;
}
}

View File

@@ -5,6 +5,10 @@
{
"note": "Add `TraceParams` interface for `debug_traceTransaction` parameters",
"pr": 675
},
{
"note": "Add `TransactionReceiptStatus` type",
"pr": 812
}
]
},

View File

@@ -215,6 +215,8 @@ export interface TxDataPayable extends TxData {
value?: BigNumber;
}
export type TransactionReceiptStatus = null | string | 0 | 1;
export interface TransactionReceipt {
blockHash: string;
blockNumber: number;
@@ -222,7 +224,7 @@ export interface TransactionReceipt {
transactionIndex: number;
from: string;
to: string;
status: null | string | 0 | 1;
status: TransactionReceiptStatus;
cumulativeGasUsed: number;
gasUsed: number;
contractAddress: string | null;

View File

@@ -45,7 +45,7 @@
"ethers": "3.0.22",
"lodash": "^4.17.4",
"run-s": "^0.0.0",
"web3-provider-engine": "^14.0.4"
"web3-provider-engine": "14.0.6"
},
"devDependencies": {
"@0xproject/dev-utils": "^0.4.4",

File diff suppressed because one or more lines are too long

View File

@@ -70,7 +70,7 @@
"ethereum-types": "^0.0.2",
"ethers": "3.0.22",
"lodash": "^4.17.4",
"web3-provider-engine": "^14.0.4"
"web3-provider-engine": "14.0.6"
},
"optionalDependencies": {
"@ledgerhq/hw-transport-node-hid": "^4.3.0"

View File

@@ -56,12 +56,11 @@
"lodash": "^4.17.4",
"semaphore-async-await": "^1.5.1",
"web3": "^0.20.0",
"web3-provider-engine": "^14.0.4"
"web3-provider-engine": "14.0.6"
},
"devDependencies": {
"@0xproject/monorepo-scripts": "^0.2.1",
"@0xproject/tslint-config": "^0.4.20",
"@0xproject/typescript-typings": "^0.4.2",
"@0xproject/utils": "^0.7.1",
"@types/bip39": "^2.4.0",
"@types/bn.js": "^4.11.0",
@@ -70,6 +69,7 @@
"@types/lodash": "4.14.104",
"@types/mocha": "^2.2.42",
"@types/node": "^8.0.53",
"@types/sinon": "^2.2.2",
"chai": "^4.0.1",
"chai-as-promised": "^7.1.0",
"copyfiles": "^1.2.0",
@@ -79,6 +79,7 @@
"npm-run-all": "^4.1.2",
"nyc": "^11.0.1",
"shx": "^0.2.2",
"sinon": "^4.0.0",
"tslint": "5.8.0",
"typedoc": "0xProject/typedoc",
"typescript": "2.7.1",

View File

@@ -1,6 +1,7 @@
import { DoneCallback } from '@0xproject/types';
import * as chai from 'chai';
import { JSONRPCResponsePayload } from 'ethereum-types';
import * as Sinon from 'sinon';
import Web3ProviderEngine = require('web3-provider-engine');
import RpcSubprovider = require('web3-provider-engine/subproviders/rpc');
@@ -41,6 +42,9 @@ describe('RedundantSubprovider', () => {
const nonExistentSubprovider = new RpcSubprovider({
rpcUrl: 'http://does-not-exist:3000',
});
const handleRequestStub = Sinon.stub(nonExistentSubprovider, 'handleRequest').throws(
new Error('REQUEST_FAILED'),
);
const subproviders = [nonExistentSubprovider as Subprovider, ganacheSubprovider];
const redundantSubprovider = new RedundantSubprovider(subproviders);
provider.addProvider(redundantSubprovider);
@@ -55,6 +59,7 @@ describe('RedundantSubprovider', () => {
const callback = reportCallbackErrors(done)((err: Error, response: JSONRPCResponsePayload) => {
expect(err).to.be.a('null');
expect(response.result.length).to.be.equal(DEFAULT_NUM_ACCOUNTS);
handleRequestStub.restore();
done();
});
provider.sendAsync(payload, callback);

View File

@@ -29,7 +29,7 @@
"lodash": "^4.17.4",
"rollbar": "^0.6.5",
"web3": "^0.20.0",
"web3-provider-engine": "^14.0.4"
"web3-provider-engine": "14.0.6"
},
"devDependencies": {
"@0xproject/tslint-config": "^0.4.20",

View File

@@ -1,4 +1,14 @@
[
{
"timestamp": 1529397769,
"version": "0.7.2",
"changes": [
{
"note": "Add `getNodeTypeAsync` method",
"pr": 812
}
]
},
{
"timestamp": 1529397769,
"version": "0.7.1",

View File

@@ -1,2 +1,2 @@
export { Web3Wrapper, uniqueVersionIds } from './web3_wrapper';
export { Web3Wrapper, uniqueVersionIds, NodeType } from './web3_wrapper';
export { Web3WrapperErrors } from './types';

View File

@@ -31,6 +31,12 @@ export const uniqueVersionIds = {
ganache: 'EthereumJS TestRPC',
};
// NodeType represents the type of the backing Ethereum node.
export enum NodeType {
Geth = 'GETH',
Ganache = 'GANACHE',
}
/**
* A wrapper around the Web3.js 0.x library that provides a consistent, clean promise-based interface.
*/
@@ -489,6 +495,20 @@ export class Web3Wrapper {
public async setHeadAsync(blockNumber: number): Promise<void> {
await this._sendRawPayloadAsync<void>({ method: 'debug_setHead', params: [this._web3.toHex(blockNumber)] });
}
/**
* Returns either NodeType.Geth or NodeType.Ganache depending on the type of
* the backing Ethereum node. Throws for any other type of node.
*/
public async getNodeTypeAsync(): Promise<NodeType> {
const version = await this.getNodeVersionAsync();
if (_.includes(version, uniqueVersionIds.geth)) {
return NodeType.Geth;
} else if (_.includes(version, uniqueVersionIds.ganache)) {
return NodeType.Ganache;
} else {
throw new Error(`Unknown client version: ${version}`);
}
}
private async _sendRawPayloadAsync<A>(payload: Partial<JSONRPCRequestPayload>): Promise<A> {
const sendAsync = this._web3.currentProvider.sendAsync.bind(this._web3.currentProvider);
const payloadWithDefaults = {

View File

@@ -59,7 +59,7 @@
"thenby": "^1.2.3",
"truffle-contract": "2.0.1",
"web3": "^0.20.0",
"web3-provider-engine": "^14.0.4",
"web3-provider-engine": "14.0.6",
"whatwg-fetch": "^2.0.3",
"xml-js": "^1.6.4"
},

View File

@@ -70,7 +70,18 @@
})(document, 'script', 'twitter-wjs');
</script>
<!-- End Twitter SDK -->
<!-- Hotjar Tracking Code for https://0xproject.com/ -->
<script>
(function (h, o, t, j, a, r) {
h.hj = h.hj || function () { (h.hj.q = h.hj.q || []).push(arguments) };
h._hjSettings = { hjid: 935597, hjsv: 6 };
a = o.getElementsByTagName('head')[0];
r = o.createElement('script'); r.async = 1;
r.src = t + h._hjSettings.hjid + j + h._hjSettings.hjsv;
a.appendChild(r);
})(window, document, 'https://static.hotjar.com/c/hotjar-', '.js?sv=');
</script>
<!-- End Hotjar Tracking Code -->
<!-- Main -->
<script type="text/javascript" crossorigin="anonymous" src="/bundle.js" charset="utf-8"></script>
</body>

View File

@@ -10,6 +10,7 @@ export class BlockchainWatcher {
private _watchBalanceIntervalId: NodeJS.Timer;
private _prevUserEtherBalanceInWei?: BigNumber;
private _prevUserAddressIfExists: string;
private _prevNodeVersionIfExists: string;
constructor(dispatcher: Dispatcher, web3Wrapper: Web3Wrapper, shouldPollUserAddress: boolean) {
this._dispatcher = dispatcher;
this._shouldPollUserAddress = shouldPollUserAddress;
@@ -43,11 +44,9 @@ export class BlockchainWatcher {
);
}
private async _updateBalanceAsync(): Promise<void> {
let prevNodeVersion: string;
// Check for node version changes
const currentNodeVersion = await this._web3Wrapper.getNodeVersionAsync();
if (currentNodeVersion !== prevNodeVersion) {
prevNodeVersion = currentNodeVersion;
if (this._prevNodeVersionIfExists !== currentNodeVersion) {
this._prevNodeVersionIfExists = currentNodeVersion;
this._dispatcher.updateNodeVersion(currentNodeVersion);
}

View File

@@ -39,7 +39,7 @@ export const OnboardingCard: React.StatelessComponent<OnboardingCardProps> = ({
borderRadius,
}) => (
<Island borderRadius={borderRadius}>
<Container paddingRight="30px" paddingLeft="30px" maxWidth={350} paddingTop="15px" paddingBottom="15px">
<Container paddingRight="30px" paddingLeft="30px" paddingTop="15px" paddingBottom="15px">
<div className="flex flex-column">
<div className="flex justify-between">
<Title>{title}</Title>

View File

@@ -6,13 +6,29 @@ import { ContinueButtonDisplay, OnboardingTooltip } from 'ts/components/onboardi
import { Animation } from 'ts/components/ui/animation';
import { Container } from 'ts/components/ui/container';
import { Overlay } from 'ts/components/ui/overlay';
import { PointerDirection } from 'ts/components/ui/pointer';
import { zIndex } from 'ts/style/z_index';
export interface Step {
export interface FixedPositionSettings {
type: 'fixed';
top?: string;
bottom?: string;
left?: string;
right?: string;
pointerDirection?: PointerDirection;
}
export interface TargetPositionSettings {
type: 'target';
target: string;
placement: Placement;
}
export interface Step {
// Provide either a CSS selector, or fixed position settings. Only applies to desktop.
position: TargetPositionSettings | FixedPositionSettings;
title?: string;
content: React.ReactNode;
placement?: Placement;
shouldHideBackButton?: boolean;
shouldHideNextButton?: boolean;
continueButtonDisplay?: ContinueButtonDisplay;
@@ -40,18 +56,30 @@ export class OnboardingFlow extends React.Component<OnboardingFlowProps> {
return null;
}
let onboardingElement = null;
const currentStep = this._getCurrentStep();
if (this.props.isMobile) {
onboardingElement = <Animation type="easeUpFromBottom">{this._renderOnboardignCard()}</Animation>;
} else {
onboardingElement = <Animation type="easeUpFromBottom">{this._renderOnboardingCard()}</Animation>;
} else if (currentStep.position.type === 'target') {
const { placement, target } = currentStep.position;
onboardingElement = (
<Popper
referenceElement={this._getElementForStep()}
placement={this._getCurrentStep().placement}
positionFixed={true}
>
<Popper referenceElement={document.querySelector(target)} placement={placement} positionFixed={true}>
{this._renderPopperChildren.bind(this)}
</Popper>
);
} else if (currentStep.position.type === 'fixed') {
const { top, right, bottom, left, pointerDirection } = currentStep.position;
onboardingElement = (
<Container
position="fixed"
zIndex={zIndex.aboveOverlay}
top={top}
right={right}
bottom={bottom}
left={left}
>
{this._renderToolTip(pointerDirection)}
</Container>
);
}
if (this.props.disableOverlay) {
return onboardingElement;
@@ -63,9 +91,6 @@ export class OnboardingFlow extends React.Component<OnboardingFlowProps> {
</div>
);
}
private _getElementForStep(): Element {
return document.querySelector(this._getCurrentStep().target);
}
private _renderPopperChildren(props: PopperChildrenProps): React.ReactNode {
const customStyles = { zIndex: zIndex.aboveOverlay };
// On re-render, we want to re-center the popper.
@@ -76,7 +101,7 @@ export class OnboardingFlow extends React.Component<OnboardingFlowProps> {
</div>
);
}
private _renderToolTip(): React.ReactNode {
private _renderToolTip(pointerDirection?: PointerDirection): React.ReactNode {
const { steps, stepIndex } = this.props;
const step = steps[stepIndex];
const isLastStep = steps.length - 1 === stepIndex;
@@ -94,12 +119,13 @@ export class OnboardingFlow extends React.Component<OnboardingFlowProps> {
continueButtonDisplay={step.continueButtonDisplay}
continueButtonText={step.continueButtonText}
onContinueButtonClick={step.onContinueButtonClick}
pointerDirection={pointerDirection}
/>
</Container>
);
}
private _renderOnboardignCard(): React.ReactNode {
private _renderOnboardingCard(): React.ReactNode {
const { steps, stepIndex } = this.props;
const step = steps[stepIndex];
const isLastStep = steps.length - 1 === stepIndex;

View File

@@ -9,7 +9,12 @@ import { AddEthOnboardingStep } from 'ts/components/onboarding/add_eth_onboardin
import { CongratsOnboardingStep } from 'ts/components/onboarding/congrats_onboarding_step';
import { InstallWalletOnboardingStep } from 'ts/components/onboarding/install_wallet_onboarding_step';
import { IntroOnboardingStep } from 'ts/components/onboarding/intro_onboarding_step';
import { OnboardingFlow, Step } from 'ts/components/onboarding/onboarding_flow';
import {
FixedPositionSettings,
OnboardingFlow,
Step,
TargetPositionSettings,
} from 'ts/components/onboarding/onboarding_flow';
import { SetAllowancesOnboardingStep } from 'ts/components/onboarding/set_allowances_onboarding_step';
import { UnlockWalletOnboardingStep } from 'ts/components/onboarding/unlock_wallet_onboarding_step';
import {
@@ -45,8 +50,6 @@ class PlainPortalOnboardingFlow extends React.Component<PortalOnboardingFlowProp
private _unlisten: () => void;
public componentDidMount(): void {
this._adjustStepIfShould();
// Wait until the step is adjusted to decide whether we should show onboarding.
setTimeout(this._autoStartOnboardingIfShould.bind(this), 1000);
// If there is a route change, just close onboarding.
this._unlisten = this.props.history.listen(() => this.props.updateIsRunning(false));
}
@@ -61,6 +64,9 @@ class PlainPortalOnboardingFlow extends React.Component<PortalOnboardingFlowProp
document.querySelector('.wallet').scrollIntoView();
}
}
if (!prevProps.blockchainIsLoaded && this.props.blockchainIsLoaded) {
this._autoStartOnboardingIfShould();
}
}
public render(): React.ReactNode {
return (
@@ -76,56 +82,61 @@ class PlainPortalOnboardingFlow extends React.Component<PortalOnboardingFlowProp
);
}
private _getSteps(): Step[] {
const nextToWalletPosition: TargetPositionSettings = {
type: 'target',
target: '.wallet',
placement: 'right',
};
const underMetamaskExtension: FixedPositionSettings = {
type: 'fixed',
top: '30px',
right: '10px',
pointerDirection: 'top',
};
const steps: Step[] = [
{
target: '.wallet',
position: nextToWalletPosition,
title: '0x Ecosystem Setup',
content: <InstallWalletOnboardingStep />,
placement: 'right',
shouldHideBackButton: true,
shouldHideNextButton: true,
},
{
target: '.wallet',
position: underMetamaskExtension,
title: '0x Ecosystem Setup',
content: <UnlockWalletOnboardingStep />,
placement: 'right',
shouldHideBackButton: true,
shouldHideNextButton: true,
},
{
target: '.wallet',
position: nextToWalletPosition,
title: '0x Ecosystem Account Setup',
content: <IntroOnboardingStep />,
placement: 'right',
shouldHideBackButton: true,
continueButtonDisplay: 'enabled',
},
{
target: '.wallet',
position: nextToWalletPosition,
title: 'Step 1: Add ETH',
content: (
<AddEthOnboardingStep userEthBalanceInWei={this.props.userEtherBalanceInWei || new BigNumber(0)} />
),
placement: 'right',
continueButtonDisplay: this._userHasVisibleEth() ? 'enabled' : 'disabled',
},
{
target: '.wallet',
position: nextToWalletPosition,
title: 'Step 2: Wrap ETH',
content: <WrapEthOnboardingStep1 />,
placement: 'right',
continueButtonDisplay: 'enabled',
},
{
target: '.wallet',
position: nextToWalletPosition,
title: 'Step 2: Wrap ETH',
content: <WrapEthOnboardingStep2 />,
placement: 'right',
continueButtonDisplay: this._userHasVisibleWeth() ? 'enabled' : 'disabled',
},
{
target: '.wallet',
position: nextToWalletPosition,
title: 'Step 2: Wrap ETH',
content: (
<WrapEthOnboardingStep3
@@ -134,11 +145,10 @@ class PlainPortalOnboardingFlow extends React.Component<PortalOnboardingFlowProp
}
/>
),
placement: 'right',
continueButtonDisplay: this._userHasVisibleWeth() ? 'enabled' : 'disabled',
},
{
target: '.wallet',
position: nextToWalletPosition,
title: 'Step 3: Unlock Tokens',
content: (
<SetAllowancesOnboardingStep
@@ -147,14 +157,12 @@ class PlainPortalOnboardingFlow extends React.Component<PortalOnboardingFlowProp
doesUserHaveAllowancesForWethAndZrx={this._doesUserHaveAllowancesForWethAndZrx()}
/>
),
placement: 'right',
continueButtonDisplay: this._doesUserHaveAllowancesForWethAndZrx() ? 'enabled' : 'disabled',
},
{
target: '.wallet',
position: nextToWalletPosition,
title: '🎉 The Ecosystem Awaits',
content: <CongratsOnboardingStep />,
placement: 'right',
continueButtonDisplay: 'enabled',
shouldHideNextButton: true,
continueButtonText: 'Enter the 0x Ecosystem',
@@ -221,7 +229,7 @@ class PlainPortalOnboardingFlow extends React.Component<PortalOnboardingFlowProp
}
private _autoStartOnboardingIfShould(): void {
if (
(this.props.stepIndex === 0 && !this.props.isRunning) ||
(this.props.stepIndex === 0 && !this.props.isRunning && this.props.blockchainIsLoaded) ||
(!this.props.isRunning && !this.props.hasBeenClosed && this.props.blockchainIsLoaded)
) {
const networkName = sharedConstants.NETWORK_NAME_BY_ID[this.props.networkId];

View File

@@ -10,7 +10,7 @@ export const UnlockWalletOnboardingStep: React.StatelessComponent<UnlockWalletOn
<Container marginTop="15px" marginBottom="15px">
<img src="/images/metamask_icon.png" height="50px" width="50px" />
</Container>
<Text center={true}>Unlock your metamask extension to get started.</Text>
<Text center={true}>Unlock your MetaMask extension to get started.</Text>
</div>
</div>
);

View File

@@ -9,6 +9,7 @@ import { Container } from 'ts/components/ui/container';
import { Image } from 'ts/components/ui/image';
import { Island } from 'ts/components/ui/island';
import { colors } from 'ts/style/colors';
import { media } from 'ts/style/media';
import { styled } from 'ts/style/theme';
import { WebsiteBackendRelayerInfo } from 'ts/types';
import { utils } from 'ts/utils/utils';
@@ -111,6 +112,9 @@ const GridTile = styled(PlainGridTile)`
&:hover {
transform: translate(0px, -3px);
}
${media.small`
transform: none;
`};
`;
interface SectionProps {

View File

@@ -1,23 +1,26 @@
import { Styles } from '@0xproject/react-shared';
import * as _ from 'lodash';
import CircularProgress from 'material-ui/CircularProgress';
import RaisedButton from 'material-ui/RaisedButton';
import ActionAccountBalanceWallet from 'material-ui/svg-icons/action/account-balance-wallet';
import * as React from 'react';
import { Blockchain } from 'ts/blockchain';
import { ProviderPicker } from 'ts/components/top_bar/provider_picker';
import { AccountConnection } from 'ts/components/ui/account_connection';
import { Container } from 'ts/components/ui/container';
import { DropDown } from 'ts/components/ui/drop_down';
import { Identicon } from 'ts/components/ui/identicon';
import { Image } from 'ts/components/ui/image';
import { Island } from 'ts/components/ui/island';
import {
CopyAddressSimpleMenuItem,
DifferentWalletSimpleMenuItem,
GoToAccountManagementSimpleMenuItem,
SimpleMenu,
} from 'ts/components/ui/simple_menu';
import { Text } from 'ts/components/ui/text';
import { Dispatcher } from 'ts/redux/dispatcher';
import { colors } from 'ts/style/colors';
import { AccountState, ProviderType } from 'ts/types';
import { constants } from 'ts/utils/constants';
import { utils } from 'ts/utils/utils';
const ROOT_HEIGHT = 24;
@@ -44,11 +47,7 @@ const styles: Styles = {
export class ProviderDisplay extends React.Component<ProviderDisplayProps, ProviderDisplayState> {
public render(): React.ReactNode {
const isExternallyInjectedProvider = utils.isExternallyInjected(
this.props.providerType,
this.props.injectedProviderName,
);
const hoverActiveNode = (
const activeNode = (
<Island className="flex items-center py1 px2" style={styles.root}>
{this._renderIcon()}
<Container marginLeft="12px" marginRight="12px">
@@ -57,93 +56,34 @@ export class ProviderDisplay extends React.Component<ProviderDisplayProps, Provi
{this._renderInjectedProvider()}
</Island>
);
const hasLedgerProvider = this.props.providerType === ProviderType.Ledger;
const horizontalPosition = isExternallyInjectedProvider || hasLedgerProvider ? 'left' : 'middle';
return (
<div style={{ width: 'fit-content', height: 48, float: 'right' }}>
<DropDown
hoverActiveNode={hoverActiveNode}
popoverContent={this.renderPopoverContent(isExternallyInjectedProvider, hasLedgerProvider)}
anchorOrigin={{ horizontal: horizontalPosition, vertical: 'bottom' }}
targetOrigin={{ horizontal: horizontalPosition, vertical: 'top' }}
activeNode={activeNode}
popoverContent={this._renderPopoverContent()}
anchorOrigin={{ horizontal: 'middle', vertical: 'bottom' }}
targetOrigin={{ horizontal: 'middle', vertical: 'top' }}
zDepth={1}
/>
</div>
);
}
public renderPopoverContent(hasInjectedProvider: boolean, hasLedgerProvider: boolean): React.ReactNode {
if (!this._isBlockchainReady()) {
return null;
} else 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>
);
private _renderPopoverContent(): React.ReactNode {
const accountState = this._getAccountState();
switch (accountState) {
case AccountState.Ready:
return (
<SimpleMenu>
<CopyAddressSimpleMenuItem userAddress={this.props.userAddress} />
<DifferentWalletSimpleMenuItem onClick={this.props.onToggleLedgerDialog} />
<GoToAccountManagementSimpleMenuItem />
</SimpleMenu>
);
case AccountState.Disconnected:
case AccountState.Locked:
case AccountState.Loading:
default:
return null;
}
}
private _renderIcon(): React.ReactNode {

View File

@@ -1,79 +0,0 @@
import { colors, constants as sharedConstants } from '@0xproject/react-shared';
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';
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(): React.ReactNode {
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): React.ReactNode {
const label = (
<div className="flex">
<div style={{ fontSize: 14 }}>{title}</div>
{shouldShowNetwork && this._renderNetwork()}
</div>
);
return label;
}
private _renderNetwork(): React.ReactNode {
const networkName = sharedConstants.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): void {
if (value === ProviderType.Ledger) {
this.props.onToggleLedgerDialog();
} else {
// tslint:disable-next-line:no-floating-promises
this.props.blockchain.updateProviderToInjectedAsync();
}
}
}

View File

@@ -199,7 +199,7 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> {
cursor: 'pointer',
paddingTop: 16,
};
const hoverActiveNode = (
const activeNode = (
<div className="flex relative" style={{ color: menuIconStyle.color }}>
<div style={{ paddingRight: 10 }}>{this.props.translate.get(Key.Developers, Deco.Cap)}</div>
<div className="absolute" style={{ paddingLeft: 3, right: 3, top: -2 }}>
@@ -224,7 +224,7 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> {
<div className={menuClasses}>
<div className="flex justify-between">
<DropDown
hoverActiveNode={hoverActiveNode}
activeNode={activeNode}
popoverContent={popoverContent}
anchorOrigin={{ horizontal: 'middle', vertical: 'bottom' }}
targetOrigin={{ horizontal: 'middle', vertical: 'top' }}

View File

@@ -17,6 +17,7 @@ export interface ContainerProps {
maxHeight?: StringOrNum;
width?: StringOrNum;
height?: StringOrNum;
minWidth?: StringOrNum;
minHeight?: StringOrNum;
isHidden?: boolean;
className?: string;

View File

@@ -1,4 +1,4 @@
import Popover, { PopoverAnimationVertical } from 'material-ui/Popover';
import Popover from 'material-ui/Popover';
import * as React from 'react';
import { MaterialUIPosition } from 'ts/types';
@@ -7,13 +7,20 @@ const DEFAULT_STYLE = {
fontSize: 14,
};
interface DropDownProps {
hoverActiveNode: React.ReactNode;
export enum DropdownMouseEvent {
Hover = 'hover',
Click = 'click',
}
export interface DropDownProps {
activeNode: React.ReactNode;
popoverContent: React.ReactNode;
anchorOrigin: MaterialUIPosition;
targetOrigin: MaterialUIPosition;
style?: React.CSSProperties;
zDepth?: number;
activateEvent?: DropdownMouseEvent;
closeEvent?: DropdownMouseEvent;
}
interface DropDownState {
@@ -25,6 +32,8 @@ export class DropDown extends React.Component<DropDownProps, DropDownState> {
public static defaultProps: Partial<DropDownProps> = {
style: DEFAULT_STYLE,
zDepth: 1,
activateEvent: DropdownMouseEvent.Hover,
closeEvent: DropdownMouseEvent.Hover,
};
private _isHovering: boolean;
private _popoverCloseCheckIntervalId: number;
@@ -58,46 +67,61 @@ export class DropDown extends React.Component<DropDownProps, DropDownState> {
onMouseEnter={this._onHover.bind(this)}
onMouseLeave={this._onHoverOff.bind(this)}
>
{this.props.hoverActiveNode}
<div onClick={this._onActiveNodeClick.bind(this)}>{this.props.activeNode}</div>
<Popover
open={this.state.isDropDownOpen}
anchorEl={this.state.anchorEl}
anchorOrigin={this.props.anchorOrigin}
targetOrigin={this.props.targetOrigin}
onRequestClose={this._closePopover.bind(this)}
useLayerForClickAway={false}
animation={PopoverAnimationVertical}
useLayerForClickAway={this.props.closeEvent === DropdownMouseEvent.Click}
animated={false}
zDepth={this.props.zDepth}
>
<div onMouseEnter={this._onHover.bind(this)} onMouseLeave={this._onHoverOff.bind(this)}>
<div
onMouseEnter={this._onHover.bind(this)}
onMouseLeave={this._onHoverOff.bind(this)}
onClick={this._closePopover.bind(this)}
>
{this.props.popoverContent}
</div>
</Popover>
</div>
);
}
private _onActiveNodeClick(event: React.FormEvent<HTMLInputElement>): void {
if (this.props.activateEvent === DropdownMouseEvent.Click) {
this.setState({
isDropDownOpen: true,
anchorEl: event.currentTarget,
});
}
}
private _onHover(event: React.FormEvent<HTMLInputElement>): void {
this._isHovering = true;
this._checkIfShouldOpenPopover(event);
if (this.props.activateEvent === DropdownMouseEvent.Hover) {
this._checkIfShouldOpenPopover(event);
}
}
private _onHoverOff(): void {
this._isHovering = false;
}
private _checkIfShouldOpenPopover(event: React.FormEvent<HTMLInputElement>): void {
if (this.state.isDropDownOpen) {
return; // noop
}
this.setState({
isDropDownOpen: true,
anchorEl: event.currentTarget,
});
}
private _onHoverOff(): void {
this._isHovering = false;
}
private _checkIfShouldClosePopover(): void {
if (!this.state.isDropDownOpen || this._isHovering) {
if (!this.state.isDropDownOpen) {
return; // noop
}
this._closePopover();
if (this.props.closeEvent === DropdownMouseEvent.Hover && !this._isHovering) {
this._closePopover();
}
}
private _closePopover(): void {
this.setState({

View File

@@ -0,0 +1,88 @@
import * as _ from 'lodash';
import * as React from 'react';
import * as CopyToClipboard from 'react-copy-to-clipboard';
import { Link } from 'react-router-dom';
import { Container } from 'ts/components/ui/container';
import { Text } from 'ts/components/ui/text';
import { colors } from 'ts/style/colors';
import { WebsitePaths } from 'ts/types';
export interface SimpleMenuProps {
minWidth?: number | string;
}
export const SimpleMenu: React.StatelessComponent<SimpleMenuProps> = ({ children, minWidth }) => {
return (
<Container
marginLeft="16px"
marginRight="16px"
marginBottom="16px"
minWidth={minWidth}
className="flex flex-column"
>
{children}
</Container>
);
};
SimpleMenu.defaultProps = {
minWidth: '220px',
};
export interface SimpleMenuItemProps {
displayText: string;
onClick?: () => void;
}
export const SimpleMenuItem: React.StatelessComponent<SimpleMenuItemProps> = ({ displayText, onClick }) => {
// Falling back to _.noop for onclick retains the hovering effect
return (
<Container marginTop="16px" className="flex flex-column">
<Text
fontSize="14px"
fontColor={colors.darkGrey}
onClick={onClick || _.noop}
hoverColor={colors.mediumBlue}
>
{displayText}
</Text>
</Container>
);
};
export interface CopyAddressSimpleMenuItemProps {
userAddress: string;
onClick?: () => void;
}
export const CopyAddressSimpleMenuItem: React.StatelessComponent<CopyAddressSimpleMenuItemProps> = ({
userAddress,
onClick,
}) => {
return (
<CopyToClipboard text={userAddress}>
<SimpleMenuItem displayText="Copy Address to Clipboard" onClick={onClick} />
</CopyToClipboard>
);
};
export interface GoToAccountManagementSimpleMenuItemProps {
onClick?: () => void;
}
export const GoToAccountManagementSimpleMenuItem: React.StatelessComponent<
GoToAccountManagementSimpleMenuItemProps
> = ({ onClick }) => {
return (
<Link to={`${WebsitePaths.Portal}/account`} style={{ textDecoration: 'none' }}>
<SimpleMenuItem displayText="Manage Account..." onClick={onClick} />
</Link>
);
};
export interface DifferentWalletSimpleMenuItemProps {
onClick?: () => void;
}
export const DifferentWalletSimpleMenuItem: React.StatelessComponent<DifferentWalletSimpleMenuItemProps> = ({
onClick,
}) => {
return <SimpleMenuItem displayText="Use a Different Wallet..." onClick={onClick} />;
};

View File

@@ -3,7 +3,7 @@ import { darken } from 'polished';
import * as React from 'react';
import { styled } from 'ts/style/theme';
export type TextTag = 'p' | 'div' | 'span' | 'label' | 'h1' | 'h2' | 'h3' | 'h4';
export type TextTag = 'p' | 'div' | 'span' | 'label' | 'h1' | 'h2' | 'h3' | 'h4' | 'i';
export interface TextProps {
className?: string;
@@ -17,6 +17,7 @@ export interface TextProps {
fontWeight?: number | string;
textDecorationLine?: string;
onClick?: (event: React.MouseEvent<HTMLElement>) => void;
hoverColor?: string;
}
const PlainText: React.StatelessComponent<TextProps> = ({ children, className, onClick, Tag }) => (
@@ -37,7 +38,7 @@ export const Text = styled(PlainText)`
${props => (props.onClick ? 'cursor: pointer' : '')};
transition: color 0.5s ease;
&:hover {
${props => (props.onClick ? `color: ${darken(0.3, props.fontColor)}` : '')};
${props => (props.onClick ? `color: ${props.hoverColor || darken(0.3, props.fontColor)}` : '')};
}
`;

View File

@@ -4,15 +4,22 @@ import * as _ from 'lodash';
import ActionAccountBalanceWallet from 'material-ui/svg-icons/action/account-balance-wallet';
import * as React from 'react';
import { Link } from 'react-router-dom';
import firstBy = require('thenby');
import { Blockchain } from 'ts/blockchain';
import { AccountConnection } from 'ts/components/ui/account_connection';
import { Container } from 'ts/components/ui/container';
import { DropDown, DropdownMouseEvent } from 'ts/components/ui/drop_down';
import { IconButton } from 'ts/components/ui/icon_button';
import { Identicon } from 'ts/components/ui/identicon';
import { Island } from 'ts/components/ui/island';
import {
CopyAddressSimpleMenuItem,
DifferentWalletSimpleMenuItem,
GoToAccountManagementSimpleMenuItem,
SimpleMenu,
SimpleMenuItem,
} from 'ts/components/ui/simple_menu';
import { Text } from 'ts/components/ui/text';
import { TokenIcon } from 'ts/components/ui/token_icon';
import { BodyOverlay } from 'ts/components/wallet/body_overlay';
@@ -33,7 +40,6 @@ import {
TokenByAddress,
TokenState,
TokenStateByAddress,
WebsitePaths,
} from 'ts/types';
import { analytics } from 'ts/utils/analytics';
import { constants } from 'ts/utils/constants';
@@ -83,7 +89,6 @@ const BODY_ITEM_KEY = 'BODY';
const HEADER_ITEM_KEY = 'HEADER';
const ETHER_ITEM_KEY = 'ETHER';
const NO_ALLOWANCE_TOGGLE_SPACE_WIDTH = 56;
const ACCOUNT_PATH = `${WebsitePaths.Portal}/account`;
const PLACEHOLDER_COLOR = colors.grey300;
const LOADING_ROWS_COUNT = 6;
@@ -187,6 +192,7 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
);
}
private _renderConnectedHeaderRows(): React.ReactElement<{}> {
const isMobile = this.props.screenWidth === ScreenWidths.Sm;
const userAddress = this.props.userAddress;
const accountState = this._getAccountState();
const main = (
@@ -197,15 +203,49 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
<AccountConnection accountState={accountState} injectedProviderName={this.props.injectedProviderName} />
</div>
);
const onClick = _.noop;
const accessory = (
<DropDown
activeNode={
// this container gives the menu button more of a hover target for the drop down
// it prevents accidentally closing the menu by moving off of the button
<Container paddingLeft="100px" paddingRight="15px">
<Text
className="zmdi zmdi-more-horiz"
Tag="i"
fontSize="32px"
fontFamily="Material-Design-Iconic-Font"
fontColor={colors.darkGrey}
onClick={onClick}
hoverColor={colors.mediumBlue}
/>
</Container>
}
popoverContent={
<SimpleMenu minWidth="150px">
<CopyAddressSimpleMenuItem userAddress={this.props.userAddress} />
<DifferentWalletSimpleMenuItem onClick={this.props.onToggleLedgerDialog} />
<SimpleMenuItem displayText="Add Tokens..." onClick={this.props.onAddToken} />
<SimpleMenuItem displayText="Remove Tokens..." onClick={this.props.onRemoveToken} />
<GoToAccountManagementSimpleMenuItem />
</SimpleMenu>
}
anchorOrigin={{ horizontal: 'right', vertical: 'bottom' }}
targetOrigin={{ horizontal: 'right', vertical: 'top' }}
zDepth={1}
activateEvent={DropdownMouseEvent.Click}
closeEvent={isMobile ? DropdownMouseEvent.Click : DropdownMouseEvent.Hover}
/>
);
return (
<Link key={HEADER_ITEM_KEY} to={ACCOUNT_PATH} style={{ textDecoration: 'none' }}>
<StandardIconRow
icon={<Identicon address={userAddress} diameter={ICON_DIMENSION} />}
main={main}
minHeight="60px"
backgroundColor={colors.white}
/>
</Link>
<StandardIconRow
key={HEADER_ITEM_KEY}
icon={<Identicon address={userAddress} diameter={ICON_DIMENSION} />}
main={main}
accessory={accessory}
minHeight="60px"
backgroundColor={colors.white}
/>
);
}
private _renderBody(): React.ReactElement<{}> {

View File

@@ -13,9 +13,11 @@ export const store: ReduxStore<State> = createStore(
);
store.subscribe(
_.throttle(() => {
const state = store.getState();
// Persisted state
stateStorage.saveState({
hasPortalOnboardingBeenClosed: store.getState().hasPortalOnboardingBeenClosed,
hasPortalOnboardingBeenClosed: state.hasPortalOnboardingBeenClosed,
isPortalOnboardingShowing: state.isPortalOnboardingShowing,
});
}, ONE_SECOND),
);

View File

@@ -0,0 +1,14 @@
import { css } from 'ts/style/theme';
import { ScreenWidths } from 'ts/types';
const generateMediaWrapper = (screenWidth: ScreenWidths) => (...args: any[]) => css`
@media (max-width: ${screenWidth}) {
${css.apply(css, args)};
}
`;
export const media = {
small: generateMediaWrapper(ScreenWidths.Sm),
medium: generateMediaWrapper(ScreenWidths.Md),
large: generateMediaWrapper(ScreenWidths.Lg),
};

View File

@@ -215,10 +215,11 @@ export interface ContractEvent {
}
export type ValidatedBigNumberCallback = (isValid: boolean, amount?: BigNumber) => void;
// Associated values are in `em` units
export enum ScreenWidths {
Sm = 'SM',
Md = 'MD',
Lg = 'LG',
Sm = 40,
Md = 52,
Lg = 64,
}
export enum AlertTypes {

View File

@@ -29,9 +29,6 @@ import { configs } from 'ts/utils/configs';
import { constants } from 'ts/utils/constants';
import * as u2f from 'ts/vendor/u2f_api';
const LG_MIN_EM = 64;
const MD_MIN_EM = 52;
const isDogfood = (): boolean => _.includes(window.location.href, configs.DOMAIN_DOGFOOD);
export const utils = {
@@ -136,9 +133,9 @@ export const utils = {
// This logic mirrors the CSS media queries in BassCSS for the `lg-`, `md-` and `sm-` CSS
// class prefixes. Do not edit these.
if (widthInEm > LG_MIN_EM) {
if (widthInEm > ScreenWidths.Lg) {
return ScreenWidths.Lg;
} else if (widthInEm > MD_MIN_EM) {
} else if (widthInEm > ScreenWidths.Md) {
return ScreenWidths.Md;
} else {
return ScreenWidths.Sm;

120
yarn.lock
View File

@@ -4005,7 +4005,7 @@ etag@~1.8.1:
version "1.8.1"
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
eth-block-tracker@^2.2.2, eth-block-tracker@^2.3.0:
eth-block-tracker@^2.2.2:
version "2.3.0"
resolved "https://registry.yarnpkg.com/eth-block-tracker/-/eth-block-tracker-2.3.0.tgz#4cb782c8ef8fde2f5dc894921ae1f5c1077c35a4"
dependencies:
@@ -4172,13 +4172,13 @@ ethereumjs-block@~1.2.2:
ethereumjs-util "^4.0.1"
merkle-patricia-tree "^2.1.2"
ethereumjs-blockstream@^2.0.6:
version "2.0.7"
resolved "https://registry.yarnpkg.com/ethereumjs-blockstream/-/ethereumjs-blockstream-2.0.7.tgz#8e791d18d517f13e0ba928892dda545dc930936b"
ethereumjs-blockstream@5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/ethereumjs-blockstream/-/ethereumjs-blockstream-5.0.0.tgz#63bfe9185757329a32822d5815562eb1dcd75d71"
dependencies:
immutable "3.8.1"
source-map-support "0.4.14"
uuid "3.0.1"
immutable "3.8.2"
source-map-support "0.5.6"
uuid "3.2.1"
ethereumjs-tx@0xProject/ethereumjs-tx#fake-tx-include-signature-by-default:
version "1.3.4"
@@ -5863,9 +5863,9 @@ immediate@^3.2.3:
version "3.2.3"
resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.2.3.tgz#d140fa8f614659bd6541233097ddaac25cdd991c"
immutable@3.8.1:
version "3.8.1"
resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.8.1.tgz#200807f11ab0f72710ea485542de088075f68cd2"
immutable@3.8.2:
version "3.8.2"
resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.8.2.tgz#c2439951455bb39913daf281376f1530e104adf3"
import-lazy@^2.1.0:
version "2.1.0"
@@ -10824,11 +10824,12 @@ source-map-resolve@^0.5.0:
source-map-url "^0.4.0"
urix "^0.1.0"
source-map-support@0.4.14:
version "0.4.14"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.14.tgz#9d4463772598b86271b4f523f6c1f4e02a7d6aef"
source-map-support@0.5.6, source-map-support@^0.5.6:
version "0.5.6"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.6.tgz#4435cee46b1aab62b8e8610ce60f788091c51c13"
dependencies:
source-map "^0.5.6"
buffer-from "^1.0.0"
source-map "^0.6.0"
source-map-support@^0.4.15:
version "0.4.18"
@@ -10842,13 +10843,6 @@ source-map-support@^0.5.0, source-map-support@^0.5.3:
dependencies:
source-map "^0.6.0"
source-map-support@^0.5.6:
version "0.5.6"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.6.tgz#4435cee46b1aab62b8e8610ce60f788091c51c13"
dependencies:
buffer-from "^1.0.0"
source-map "^0.6.0"
source-map-url@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3"
@@ -12259,14 +12253,14 @@ uuid@3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04"
uuid@3.2.1, uuid@^3.0.0, uuid@^3.0.1, uuid@^3.1.0:
version "3.2.1"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.2.1.tgz#12c528bb9d58d0b9265d9a2f6f0fe8be17ff1f14"
uuid@^2.0.1:
version "2.0.3"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a"
uuid@^3.0.0, uuid@^3.0.1, uuid@^3.1.0:
version "3.2.1"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.2.1.tgz#12c528bb9d58d0b9265d9a2f6f0fe8be17ff1f14"
uvm@1.7.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/uvm/-/uvm-1.7.0.tgz#685d3a149ec7118fb73a73dfdc158ab46b0f0634"
@@ -12607,57 +12601,7 @@ web3-net@1.0.0-beta.34:
web3-core-method "1.0.0-beta.34"
web3-utils "1.0.0-beta.34"
web3-provider-engine@^13.3.2:
version "13.8.0"
resolved "https://registry.yarnpkg.com/web3-provider-engine/-/web3-provider-engine-13.8.0.tgz#4c7c1ad2af5f1fe10343b8a65495879a2f9c00df"
dependencies:
async "^2.5.0"
clone "^2.0.0"
eth-block-tracker "^2.2.2"
eth-sig-util "^1.4.2"
ethereumjs-block "^1.2.2"
ethereumjs-tx "^1.2.0"
ethereumjs-util "^5.1.1"
ethereumjs-vm "^2.0.2"
fetch-ponyfill "^4.0.0"
json-rpc-error "^2.0.0"
json-stable-stringify "^1.0.1"
promise-to-callback "^1.0.0"
readable-stream "^2.2.9"
request "^2.67.0"
semaphore "^1.0.3"
solc "^0.4.2"
tape "^4.4.0"
xhr "^2.2.0"
xtend "^4.0.1"
web3-provider-engine@^14.0.4:
version "14.0.4"
resolved "https://registry.yarnpkg.com/web3-provider-engine/-/web3-provider-engine-14.0.4.tgz#6f96b71ea1b3a76cc67cd52007116c8d4b64465b"
dependencies:
async "^2.5.0"
backoff "^2.5.0"
clone "^2.0.0"
cross-fetch "^2.1.0"
eth-block-tracker "^2.3.0"
eth-json-rpc-infura "^3.1.0"
eth-sig-util "^1.4.2"
ethereumjs-block "^1.2.2"
ethereumjs-tx "^1.2.0"
ethereumjs-util "^5.1.5"
ethereumjs-vm "^2.3.4"
json-rpc-error "^2.0.0"
json-stable-stringify "^1.0.1"
promise-to-callback "^1.0.0"
readable-stream "^2.2.9"
request "^2.67.0"
semaphore "^1.0.3"
tape "^4.4.0"
ws "^5.1.1"
xhr "^2.2.0"
xtend "^4.0.1"
web3-provider-engine@^14.0.6:
web3-provider-engine@14.0.6, web3-provider-engine@^14.0.6:
version "14.0.6"
resolved "https://registry.yarnpkg.com/web3-provider-engine/-/web3-provider-engine-14.0.6.tgz#cbdd66fe20c0136a3a495cbe40d18b6c4160d5f0"
dependencies:
@@ -12683,6 +12627,30 @@ web3-provider-engine@^14.0.6:
xhr "^2.2.0"
xtend "^4.0.1"
web3-provider-engine@^13.3.2:
version "13.8.0"
resolved "https://registry.yarnpkg.com/web3-provider-engine/-/web3-provider-engine-13.8.0.tgz#4c7c1ad2af5f1fe10343b8a65495879a2f9c00df"
dependencies:
async "^2.5.0"
clone "^2.0.0"
eth-block-tracker "^2.2.2"
eth-sig-util "^1.4.2"
ethereumjs-block "^1.2.2"
ethereumjs-tx "^1.2.0"
ethereumjs-util "^5.1.1"
ethereumjs-vm "^2.0.2"
fetch-ponyfill "^4.0.0"
json-rpc-error "^2.0.0"
json-stable-stringify "^1.0.1"
promise-to-callback "^1.0.0"
readable-stream "^2.2.9"
request "^2.67.0"
semaphore "^1.0.3"
solc "^0.4.2"
tape "^4.4.0"
xhr "^2.2.0"
xtend "^4.0.1"
web3-providers-http@1.0.0-beta.34:
version "1.0.0-beta.34"
resolved "https://registry.yarnpkg.com/web3-providers-http/-/web3-providers-http-1.0.0-beta.34.tgz#e561b52bbb43766282007d40285bfe3550c27e7a"