Merge branch 'development' into refactor/website

* development:
  Add additional public changes introduced to changelog
  Update CHANGELOG
  Add a comment
  Introduce a variable for true
  Remove redundant template string
  Implement the address derivations
  Add hdnode dependency
  Move web3 import after subprovider imports in test web3_factory
  Fixed https://github.com/0xProject/wiki/issues/19 by disabling re-rendering of markdownCodeBlock renderer if props haven't updated
  Add convenience `rebuild` command
  Update website calls to deposit/withdraw
  Add entry to CHANGELOG
  Fix tests in contracts
  Modify the etherToken wrapper methods to accept an etherTokenAddress as the first arg. Since it is becoming apparent we will be updating the canonical WETH contract, we want users of 0x.js to be able to interact with n number of etherTokens without re-instantiating for each one.
  Fix documentation issue where `unsubscribeAll` shown as method on every contractWrapper instance even though it's only used by Exchange and Token wrappers.

# Conflicts:
#	packages/website/ts/components/eth_weth_conversion_button.tsx
This commit is contained in:
Fabio Berger
2017-12-19 11:04:25 +01:00
27 changed files with 142 additions and 105 deletions

View File

@@ -7,6 +7,7 @@
"scripts": { "scripts": {
"testrpc": "testrpc -p 8545 --networkId 50 -m \"${npm_package_config_mnemonic}\"", "testrpc": "testrpc -p 8545 --networkId 50 -m \"${npm_package_config_mnemonic}\"",
"lerna:run": "lerna run", "lerna:run": "lerna run",
"lerna:rebuild": "lerna run clean; lerna run build;",
"lerna:publish": "yarn install; lerna run clean; lerna run build; lerna publish --registry=https://registry.npmjs.org/" "lerna:publish": "yarn install; lerna run clean; lerna run build; lerna publish --registry=https://registry.npmjs.org/"
}, },
"config": { "config": {

View File

@@ -1,5 +1,11 @@
# CHANGELOG # CHANGELOG
v0.28.0 - _TBD_
------------------------
* Add `etherTokenAddress` arg to `depositAsync` and `withdrawAsync` methods on `zeroEx.etherToken` (#267)
* Removed accidentally included `unsubscribeAll` method from `zeroEx.proxy`, `zeroEx.etherToken` and `zeroEx.tokenRegistry` (#267)
* Removed `etherTokenContractAddress` from `ZeroEx` constructor arg `ZeroExConfig` (#267)
v0.27.1 - _November 28, 2017_ v0.27.1 - _November 28, 2017_
------------------------ ------------------------
* Export `TransactionOpts` type * Export `TransactionOpts` type

View File

@@ -199,7 +199,7 @@ export class ZeroEx {
this._web3Wrapper, config.networkId, config.tokenRegistryContractAddress, this._web3Wrapper, config.networkId, config.tokenRegistryContractAddress,
); );
this.etherToken = new EtherTokenWrapper( this.etherToken = new EtherTokenWrapper(
this._web3Wrapper, config.networkId, this.token, config.etherTokenContractAddress, this._web3Wrapper, config.networkId, this.token,
); );
this.orderStateWatcher = new OrderStateWatcher( this.orderStateWatcher = new OrderStateWatcher(
this._web3Wrapper, this._abiDecoder, this.token, this.exchange, config.orderWatcherConfig, this._web3Wrapper, this._abiDecoder, this.token, this.exchange, config.orderWatcherConfig,

View File

@@ -50,10 +50,7 @@ export class ContractWrapper {
this._onLogAddedSubscriptionToken = undefined; this._onLogAddedSubscriptionToken = undefined;
this._onLogRemovedSubscriptionToken = undefined; this._onLogRemovedSubscriptionToken = undefined;
} }
/** protected unsubscribeAll(): void {
* Cancels all existing subscriptions
*/
public unsubscribeAll(): void {
const filterTokens = _.keys(this._filterCallbacks); const filterTokens = _.keys(this._filterCallbacks);
_.each(filterTokens, filterToken => { _.each(filterTokens, filterToken => {
this._unsubscribe(filterToken); this._unsubscribe(filterToken);

View File

@@ -17,24 +17,22 @@ import {TokenWrapper} from './token_wrapper';
export class EtherTokenWrapper extends ContractWrapper { export class EtherTokenWrapper extends ContractWrapper {
private _etherTokenContractIfExists?: EtherTokenContract; private _etherTokenContractIfExists?: EtherTokenContract;
private _tokenWrapper: TokenWrapper; private _tokenWrapper: TokenWrapper;
private _contractAddressIfExists?: string; constructor(web3Wrapper: Web3Wrapper, networkId: number, tokenWrapper: TokenWrapper) {
constructor(web3Wrapper: Web3Wrapper, networkId: number, tokenWrapper: TokenWrapper,
contractAddressIfExists?: string) {
super(web3Wrapper, networkId); super(web3Wrapper, networkId);
this._tokenWrapper = tokenWrapper; this._tokenWrapper = tokenWrapper;
this._contractAddressIfExists = contractAddressIfExists;
} }
/** /**
* Deposit ETH into the Wrapped ETH smart contract and issues the equivalent number of wrapped ETH tokens * Deposit ETH into the Wrapped ETH smart contract and issues the equivalent number of wrapped ETH tokens
* to the depositor address. These wrapped ETH tokens can be used in 0x trades and are redeemable for 1-to-1 * to the depositor address. These wrapped ETH tokens can be used in 0x trades and are redeemable for 1-to-1
* for ETH. * for ETH.
* @param amountInWei Amount of ETH in Wei the caller wishes to deposit. * @param etherTokenAddress EtherToken address you wish to deposit into.
* @param depositor The hex encoded user Ethereum address that would like to make the deposit. * @param amountInWei Amount of ETH in Wei the caller wishes to deposit.
* @param txOpts Transaction parameters. * @param depositor The hex encoded user Ethereum address that would like to make the deposit.
* @param txOpts Transaction parameters.
* @return Transaction hash. * @return Transaction hash.
*/ */
public async depositAsync( public async depositAsync(
amountInWei: BigNumber, depositor: string, txOpts: TransactionOpts = {}, etherTokenAddress: string, amountInWei: BigNumber, depositor: string, txOpts: TransactionOpts = {},
): Promise<string> { ): Promise<string> {
assert.isValidBaseUnitAmount('amountInWei', amountInWei); assert.isValidBaseUnitAmount('amountInWei', amountInWei);
await assert.isSenderAddressAsync('depositor', depositor, this._web3Wrapper); await assert.isSenderAddressAsync('depositor', depositor, this._web3Wrapper);
@@ -42,7 +40,7 @@ export class EtherTokenWrapper extends ContractWrapper {
const ethBalanceInWei = await this._web3Wrapper.getBalanceInWeiAsync(depositor); const ethBalanceInWei = await this._web3Wrapper.getBalanceInWeiAsync(depositor);
assert.assert(ethBalanceInWei.gte(amountInWei), ZeroExError.InsufficientEthBalanceForDeposit); assert.assert(ethBalanceInWei.gte(amountInWei), ZeroExError.InsufficientEthBalanceForDeposit);
const wethContract = await this._getEtherTokenContractAsync(); const wethContract = await this._getEtherTokenContractAsync(etherTokenAddress);
const txHash = await wethContract.deposit.sendTransactionAsync({ const txHash = await wethContract.deposit.sendTransactionAsync({
from: depositor, from: depositor,
value: amountInWei, value: amountInWei,
@@ -54,22 +52,22 @@ export class EtherTokenWrapper extends ContractWrapper {
/** /**
* Withdraw ETH to the withdrawer's address from the wrapped ETH smart contract in exchange for the * Withdraw ETH to the withdrawer's address from the wrapped ETH smart contract in exchange for the
* equivalent number of wrapped ETH tokens. * equivalent number of wrapped ETH tokens.
* @param etherTokenAddress EtherToken address you wish to withdraw from.
* @param amountInWei Amount of ETH in Wei the caller wishes to withdraw. * @param amountInWei Amount of ETH in Wei the caller wishes to withdraw.
* @param withdrawer The hex encoded user Ethereum address that would like to make the withdrawl. * @param withdrawer The hex encoded user Ethereum address that would like to make the withdrawl.
* @param txOpts Transaction parameters. * @param txOpts Transaction parameters.
* @return Transaction hash. * @return Transaction hash.
*/ */
public async withdrawAsync( public async withdrawAsync(
amountInWei: BigNumber, withdrawer: string, txOpts: TransactionOpts = {}, etherTokenAddress: string, amountInWei: BigNumber, withdrawer: string, txOpts: TransactionOpts = {},
): Promise<string> { ): Promise<string> {
assert.isValidBaseUnitAmount('amountInWei', amountInWei); assert.isValidBaseUnitAmount('amountInWei', amountInWei);
await assert.isSenderAddressAsync('withdrawer', withdrawer, this._web3Wrapper); await assert.isSenderAddressAsync('withdrawer', withdrawer, this._web3Wrapper);
const wethContractAddress = this.getContractAddress(); const WETHBalanceInBaseUnits = await this._tokenWrapper.getBalanceAsync(etherTokenAddress, withdrawer);
const WETHBalanceInBaseUnits = await this._tokenWrapper.getBalanceAsync(wethContractAddress, withdrawer);
assert.assert(WETHBalanceInBaseUnits.gte(amountInWei), ZeroExError.InsufficientWEthBalanceForWithdrawal); assert.assert(WETHBalanceInBaseUnits.gte(amountInWei), ZeroExError.InsufficientWEthBalanceForWithdrawal);
const wethContract = await this._getEtherTokenContractAsync(); const wethContract = await this._getEtherTokenContractAsync(etherTokenAddress);
const txHash = await wethContract.withdraw.sendTransactionAsync(amountInWei, { const txHash = await wethContract.withdraw.sendTransactionAsync(amountInWei, {
from: withdrawer, from: withdrawer,
gas: txOpts.gasLimit, gas: txOpts.gasLimit,
@@ -77,25 +75,12 @@ export class EtherTokenWrapper extends ContractWrapper {
}); });
return txHash; return txHash;
} }
/**
* Retrieves the Wrapped Ether token contract address
* @return The Wrapped Ether token contract address
*/
public getContractAddress(): string {
const contractAddress = this._getContractAddress(
artifacts.EtherTokenArtifact, this._contractAddressIfExists,
);
return contractAddress;
}
private _invalidateContractInstance(): void { private _invalidateContractInstance(): void {
delete this._etherTokenContractIfExists; delete this._etherTokenContractIfExists;
} }
private async _getEtherTokenContractAsync(): Promise<EtherTokenContract> { private async _getEtherTokenContractAsync(etherTokenAddress: string): Promise<EtherTokenContract> {
if (!_.isUndefined(this._etherTokenContractIfExists)) {
return this._etherTokenContractIfExists;
}
const web3ContractInstance = await this._instantiateContractIfExistsAsync( const web3ContractInstance = await this._instantiateContractIfExistsAsync(
artifacts.EtherTokenArtifact, this._contractAddressIfExists, artifacts.EtherTokenArtifact, etherTokenAddress,
); );
const contractInstance = new EtherTokenContract(web3ContractInstance, this._web3Wrapper.getContractDefaults()); const contractInstance = new EtherTokenContract(web3ContractInstance, this._web3Wrapper.getContractDefaults());
this._etherTokenContractIfExists = contractInstance; this._etherTokenContractIfExists = contractInstance;

View File

@@ -607,6 +607,12 @@ export class ExchangeWrapper extends ContractWrapper {
public unsubscribe(subscriptionToken: string): void { public unsubscribe(subscriptionToken: string): void {
this._unsubscribe(subscriptionToken); this._unsubscribe(subscriptionToken);
} }
/**
* Cancels all existing subscriptions
*/
public unsubscribeAll(): void {
super.unsubscribeAll();
}
/** /**
* Gets historical logs without creating a subscription * Gets historical logs without creating a subscription
* @param eventName The exchange contract event you would like to subscribe to. * @param eventName The exchange contract event you would like to subscribe to.

View File

@@ -281,6 +281,12 @@ export class TokenWrapper extends ContractWrapper {
public unsubscribe(subscriptionToken: string): void { public unsubscribe(subscriptionToken: string): void {
this._unsubscribe(subscriptionToken); this._unsubscribe(subscriptionToken);
} }
/**
* Cancels all existing subscriptions
*/
public unsubscribeAll(): void {
super.unsubscribeAll();
}
/** /**
* Gets historical logs without creating a subscription * Gets historical logs without creating a subscription
* @param tokenAddress An address of the token that emmited the logs. * @param tokenAddress An address of the token that emmited the logs.

View File

@@ -8,7 +8,6 @@ export const zeroExConfigSchema = {
gasPrice: {$ref: '/Number'}, gasPrice: {$ref: '/Number'},
exchangeContractAddress: {$ref: '/Address'}, exchangeContractAddress: {$ref: '/Address'},
tokenRegistryContractAddress: {$ref: '/Address'}, tokenRegistryContractAddress: {$ref: '/Address'},
etherTokenContractAddress: {$ref: '/Address'},
orderWatcherConfig: { orderWatcherConfig: {
type: 'object', type: 'object',
properties: { properties: {

View File

@@ -268,7 +268,6 @@ export interface OrderStateWatcherConfig {
* gasPrice: Gas price to use with every transaction * gasPrice: Gas price to use with every transaction
* exchangeContractAddress: The address of an exchange contract to use * exchangeContractAddress: The address of an exchange contract to use
* tokenRegistryContractAddress: The address of a token registry contract to use * tokenRegistryContractAddress: The address of a token registry contract to use
* etherTokenContractAddress: The address of an ether token contract to use
* tokenTransferProxyContractAddress: The address of the token transfer proxy contract to use * tokenTransferProxyContractAddress: The address of the token transfer proxy contract to use
* orderWatcherConfig: All the configs related to the orderWatcher * orderWatcherConfig: All the configs related to the orderWatcher
*/ */
@@ -277,7 +276,6 @@ export interface ZeroExConfig {
gasPrice?: BigNumber; gasPrice?: BigNumber;
exchangeContractAddress?: string; exchangeContractAddress?: string;
tokenRegistryContractAddress?: string; tokenRegistryContractAddress?: string;
etherTokenContractAddress?: string;
tokenTransferProxyContractAddress?: string; tokenTransferProxyContractAddress?: string;
orderWatcherConfig?: OrderStateWatcherConfig; orderWatcherConfig?: OrderStateWatcherConfig;
} }

View File

@@ -82,9 +82,9 @@ describe('ZeroEx library', () => {
it('should return true if the signature does pertain to the dataHex & address', async () => { it('should return true if the signature does pertain to the dataHex & address', async () => {
const isValidSignatureLocal = ZeroEx.isValidSignature(dataHex, signature, address); const isValidSignatureLocal = ZeroEx.isValidSignature(dataHex, signature, address);
expect(isValidSignatureLocal).to.be.true(); expect(isValidSignatureLocal).to.be.true();
const isValidSignatureOnContract = await (zeroEx.exchange as any) return expect(
._isValidSignatureUsingContractCallAsync(dataHex, signature, address); (zeroEx.exchange as any)._isValidSignatureUsingContractCallAsync(dataHex, signature, address),
return expect(isValidSignatureOnContract).to.be.true(); ).to.become(true);
}); });
}); });
describe('#generateSalt', () => { describe('#generateSalt', () => {
@@ -244,14 +244,6 @@ describe('ZeroEx library', () => {
const zeroExWithWrongExchangeAddress = new ZeroEx(web3.currentProvider, zeroExConfig); const zeroExWithWrongExchangeAddress = new ZeroEx(web3.currentProvider, zeroExConfig);
expect(zeroExWithWrongExchangeAddress.exchange.getContractAddress()).to.be.equal(ZeroEx.NULL_ADDRESS); expect(zeroExWithWrongExchangeAddress.exchange.getContractAddress()).to.be.equal(ZeroEx.NULL_ADDRESS);
}); });
it('allows to specify ether token contract address', async () => {
const zeroExConfig = {
etherTokenContractAddress: ZeroEx.NULL_ADDRESS,
networkId: constants.TESTRPC_NETWORK_ID,
};
const zeroExWithWrongEtherTokenAddress = new ZeroEx(web3.currentProvider, zeroExConfig);
expect(zeroExWithWrongEtherTokenAddress.etherToken.getContractAddress()).to.be.equal(ZeroEx.NULL_ADDRESS);
});
it('allows to specify token registry token contract address', async () => { it('allows to specify token registry token contract address', async () => {
const zeroExConfig = { const zeroExConfig = {
tokenRegistryContractAddress: ZeroEx.NULL_ADDRESS, tokenRegistryContractAddress: ZeroEx.NULL_ADDRESS,

View File

@@ -5,6 +5,7 @@ import 'mocha';
import * as Web3 from 'web3'; import * as Web3 from 'web3';
import {ZeroEx, ZeroExError} from '../src'; import {ZeroEx, ZeroExError} from '../src';
import {artifacts} from '../src/artifacts';
import {chaiSetup} from './utils/chai_setup'; import {chaiSetup} from './utils/chai_setup';
import {constants} from './utils/constants'; import {constants} from './utils/constants';
@@ -38,7 +39,7 @@ describe('EtherTokenWrapper', () => {
zeroEx = new ZeroEx(web3.currentProvider, zeroExConfig); zeroEx = new ZeroEx(web3.currentProvider, zeroExConfig);
userAddresses = await zeroEx.getAvailableAddressesAsync(); userAddresses = await zeroEx.getAvailableAddressesAsync();
addressWithETH = userAddresses[0]; addressWithETH = userAddresses[0];
wethContractAddress = zeroEx.etherToken.getContractAddress(); wethContractAddress = (zeroEx.etherToken as any)._getContractAddress(artifacts.EtherTokenArtifact);
depositWeiAmount = (zeroEx as any)._web3Wrapper.toWei(new BigNumber(5)); depositWeiAmount = (zeroEx as any)._web3Wrapper.toWei(new BigNumber(5));
decimalPlaces = 7; decimalPlaces = 7;
}); });
@@ -55,7 +56,7 @@ describe('EtherTokenWrapper', () => {
expect(preETHBalance).to.be.bignumber.gt(0); expect(preETHBalance).to.be.bignumber.gt(0);
expect(preWETHBalance).to.be.bignumber.equal(0); expect(preWETHBalance).to.be.bignumber.equal(0);
const txHash = await zeroEx.etherToken.depositAsync(depositWeiAmount, addressWithETH); const txHash = await zeroEx.etherToken.depositAsync(wethContractAddress, depositWeiAmount, addressWithETH);
await zeroEx.awaitTransactionMinedAsync(txHash); await zeroEx.awaitTransactionMinedAsync(txHash);
const postETHBalanceInWei = await (zeroEx as any)._web3Wrapper.getBalanceInWeiAsync(addressWithETH); const postETHBalanceInWei = await (zeroEx as any)._web3Wrapper.getBalanceInWeiAsync(addressWithETH);
@@ -73,7 +74,7 @@ describe('EtherTokenWrapper', () => {
const overETHBalanceinWei = preETHBalance.add(extraETHBalance); const overETHBalanceinWei = preETHBalance.add(extraETHBalance);
return expect( return expect(
zeroEx.etherToken.depositAsync(overETHBalanceinWei, addressWithETH), zeroEx.etherToken.depositAsync(wethContractAddress, overETHBalanceinWei, addressWithETH),
).to.be.rejectedWith(ZeroExError.InsufficientEthBalanceForDeposit); ).to.be.rejectedWith(ZeroExError.InsufficientEthBalanceForDeposit);
}); });
}); });
@@ -81,7 +82,7 @@ describe('EtherTokenWrapper', () => {
it('should successfully withdraw ETH in return for Wrapped ETH tokens', async () => { it('should successfully withdraw ETH in return for Wrapped ETH tokens', async () => {
const ETHBalanceInWei = await (zeroEx as any)._web3Wrapper.getBalanceInWeiAsync(addressWithETH); const ETHBalanceInWei = await (zeroEx as any)._web3Wrapper.getBalanceInWeiAsync(addressWithETH);
await zeroEx.etherToken.depositAsync(depositWeiAmount, addressWithETH); await zeroEx.etherToken.depositAsync(wethContractAddress, depositWeiAmount, addressWithETH);
const expectedPreETHBalance = ETHBalanceInWei.minus(depositWeiAmount); const expectedPreETHBalance = ETHBalanceInWei.minus(depositWeiAmount);
const preETHBalance = await (zeroEx as any)._web3Wrapper.getBalanceInWeiAsync(addressWithETH); const preETHBalance = await (zeroEx as any)._web3Wrapper.getBalanceInWeiAsync(addressWithETH);
@@ -90,7 +91,7 @@ describe('EtherTokenWrapper', () => {
expect(gasCost).to.be.bignumber.lte(MAX_REASONABLE_GAS_COST_IN_WEI); expect(gasCost).to.be.bignumber.lte(MAX_REASONABLE_GAS_COST_IN_WEI);
expect(preWETHBalance).to.be.bignumber.equal(depositWeiAmount); expect(preWETHBalance).to.be.bignumber.equal(depositWeiAmount);
const txHash = await zeroEx.etherToken.withdrawAsync(depositWeiAmount, addressWithETH); const txHash = await zeroEx.etherToken.withdrawAsync(wethContractAddress, depositWeiAmount, addressWithETH);
await zeroEx.awaitTransactionMinedAsync(txHash); await zeroEx.awaitTransactionMinedAsync(txHash);
const postETHBalance = await (zeroEx as any)._web3Wrapper.getBalanceInWeiAsync(addressWithETH); const postETHBalance = await (zeroEx as any)._web3Wrapper.getBalanceInWeiAsync(addressWithETH);
@@ -108,7 +109,7 @@ describe('EtherTokenWrapper', () => {
const overWETHBalance = preWETHBalance.add(999999999); const overWETHBalance = preWETHBalance.add(999999999);
return expect( return expect(
zeroEx.etherToken.withdrawAsync(overWETHBalance, addressWithETH), zeroEx.etherToken.withdrawAsync(wethContractAddress, overWETHBalance, addressWithETH),
).to.be.rejectedWith(ZeroExError.InsufficientWEthBalanceForWithdrawal); ).to.be.rejectedWith(ZeroExError.InsufficientWEthBalanceForWithdrawal);
}); });
}); });

View File

@@ -3,7 +3,6 @@
// we are not running in a browser env. // we are not running in a browser env.
// Filed issue: https://github.com/ethereum/web3.js/issues/844 // Filed issue: https://github.com/ethereum/web3.js/issues/844
(global as any).XMLHttpRequest = undefined; (global as any).XMLHttpRequest = undefined;
import * as Web3 from 'web3';
import ProviderEngine = require('web3-provider-engine'); import ProviderEngine = require('web3-provider-engine');
import RpcSubprovider = require('web3-provider-engine/subproviders/rpc'); import RpcSubprovider = require('web3-provider-engine/subproviders/rpc');
@@ -12,6 +11,13 @@ import {FakeGasEstimateSubprovider} from './subproviders/fake_gas_estimate_subpr
import {constants} from './constants'; import {constants} from './constants';
// HACK: web3 leaks XMLHttpRequest into the global scope and causes requests to hang
// because they are using the wrong XHR package.
// importing web3 after subproviders fixes this issue
// Filed issue: https://github.com/ethereum/web3.js/issues/844
// tslint:disable-next-line:ordered-imports
import * as Web3 from 'web3';
export const web3Factory = { export const web3Factory = {
create(hasAddresses: boolean = true): Web3 { create(hasAddresses: boolean = true): Web3 {
const provider = this.getRpcProvider(hasAddresses); const provider = this.getRpcProvider(hasAddresses);

View File

@@ -28,7 +28,6 @@ contract('EtherToken', (accounts: string[]) => {
etherTokenAddress = EtherToken.address; etherTokenAddress = EtherToken.address;
zeroEx = new ZeroEx(web3.currentProvider, { zeroEx = new ZeroEx(web3.currentProvider, {
gasPrice, gasPrice,
etherTokenContractAddress: etherTokenAddress,
networkId: constants.TESTRPC_NETWORK_ID, networkId: constants.TESTRPC_NETWORK_ID,
}); });
}); });
@@ -45,7 +44,7 @@ contract('EtherToken', (accounts: string[]) => {
const initEthBalance = await getEthBalanceAsync(account); const initEthBalance = await getEthBalanceAsync(account);
const ethToDeposit = initEthBalance.plus(1); const ethToDeposit = initEthBalance.plus(1);
return expect(zeroEx.etherToken.depositAsync(ethToDeposit, account)) return expect(zeroEx.etherToken.depositAsync(etherTokenAddress, ethToDeposit, account))
.to.be.rejectedWith(ZeroExError.InsufficientEthBalanceForDeposit); .to.be.rejectedWith(ZeroExError.InsufficientEthBalanceForDeposit);
}); });
@@ -55,7 +54,7 @@ contract('EtherToken', (accounts: string[]) => {
const ethToDeposit = new BigNumber(web3.toWei(1, 'ether')); const ethToDeposit = new BigNumber(web3.toWei(1, 'ether'));
const txHash = await zeroEx.etherToken.depositAsync(ethToDeposit, account); const txHash = await zeroEx.etherToken.depositAsync(etherTokenAddress, ethToDeposit, account);
const receipt = await zeroEx.awaitTransactionMinedAsync(txHash); const receipt = await zeroEx.awaitTransactionMinedAsync(txHash);
const ethSpentOnGas = gasPrice.times(receipt.gasUsed); const ethSpentOnGas = gasPrice.times(receipt.gasUsed);
@@ -72,7 +71,7 @@ contract('EtherToken', (accounts: string[]) => {
const initEthTokenBalance = await zeroEx.token.getBalanceAsync(etherTokenAddress, account); const initEthTokenBalance = await zeroEx.token.getBalanceAsync(etherTokenAddress, account);
const ethTokensToWithdraw = initEthTokenBalance.plus(1); const ethTokensToWithdraw = initEthTokenBalance.plus(1);
return expect(zeroEx.etherToken.withdrawAsync(ethTokensToWithdraw, account)) return expect(zeroEx.etherToken.withdrawAsync(etherTokenAddress, ethTokensToWithdraw, account))
.to.be.rejectedWith(ZeroExError.InsufficientWEthBalanceForWithdrawal); .to.be.rejectedWith(ZeroExError.InsufficientWEthBalanceForWithdrawal);
}); });
@@ -81,7 +80,7 @@ contract('EtherToken', (accounts: string[]) => {
const initEthBalance = await getEthBalanceAsync(account); const initEthBalance = await getEthBalanceAsync(account);
const ethTokensToWithdraw = initEthTokenBalance; const ethTokensToWithdraw = initEthTokenBalance;
expect(ethTokensToWithdraw).to.not.be.bignumber.equal(0); expect(ethTokensToWithdraw).to.not.be.bignumber.equal(0);
const txHash = await zeroEx.etherToken.withdrawAsync(ethTokensToWithdraw, account, { const txHash = await zeroEx.etherToken.withdrawAsync(etherTokenAddress, ethTokensToWithdraw, account, {
gasLimit: constants.MAX_ETHERTOKEN_WITHDRAW_GAS, gasLimit: constants.MAX_ETHERTOKEN_WITHDRAW_GAS,
}); });
const receipt = await zeroEx.awaitTransactionMinedAsync(txHash); const receipt = await zeroEx.awaitTransactionMinedAsync(txHash);

View File

@@ -28,7 +28,6 @@ contract('EtherTokenV2', (accounts: string[]) => {
etherTokenAddress = etherToken.address; etherTokenAddress = etherToken.address;
zeroEx = new ZeroEx(web3.currentProvider, { zeroEx = new ZeroEx(web3.currentProvider, {
gasPrice, gasPrice,
etherTokenContractAddress: etherTokenAddress,
networkId: constants.TESTRPC_NETWORK_ID, networkId: constants.TESTRPC_NETWORK_ID,
}); });
}); });
@@ -45,7 +44,7 @@ contract('EtherTokenV2', (accounts: string[]) => {
const initEthBalance = await getEthBalanceAsync(account); const initEthBalance = await getEthBalanceAsync(account);
const ethToDeposit = initEthBalance.plus(1); const ethToDeposit = initEthBalance.plus(1);
return expect(zeroEx.etherToken.depositAsync(ethToDeposit, account)) return expect(zeroEx.etherToken.depositAsync(etherTokenAddress, ethToDeposit, account))
.to.be.rejectedWith(ZeroExError.InsufficientEthBalanceForDeposit); .to.be.rejectedWith(ZeroExError.InsufficientEthBalanceForDeposit);
}); });
@@ -55,7 +54,7 @@ contract('EtherTokenV2', (accounts: string[]) => {
const ethToDeposit = new BigNumber(web3.toWei(1, 'ether')); const ethToDeposit = new BigNumber(web3.toWei(1, 'ether'));
const txHash = await zeroEx.etherToken.depositAsync(ethToDeposit, account); const txHash = await zeroEx.etherToken.depositAsync(etherTokenAddress, ethToDeposit, account);
const receipt = await zeroEx.awaitTransactionMinedAsync(txHash); const receipt = await zeroEx.awaitTransactionMinedAsync(txHash);
const ethSpentOnGas = gasPrice.times(receipt.gasUsed); const ethSpentOnGas = gasPrice.times(receipt.gasUsed);
@@ -69,7 +68,7 @@ contract('EtherTokenV2', (accounts: string[]) => {
it('should log 1 event with correct arguments', async () => { it('should log 1 event with correct arguments', async () => {
const ethToDeposit = new BigNumber(web3.toWei(1, 'ether')); const ethToDeposit = new BigNumber(web3.toWei(1, 'ether'));
const txHash = await zeroEx.etherToken.depositAsync(ethToDeposit, account); const txHash = await zeroEx.etherToken.depositAsync(etherTokenAddress, ethToDeposit, account);
const receipt = await zeroEx.awaitTransactionMinedAsync(txHash); const receipt = await zeroEx.awaitTransactionMinedAsync(txHash);
const logs = receipt.logs; const logs = receipt.logs;
@@ -88,14 +87,14 @@ contract('EtherTokenV2', (accounts: string[]) => {
describe('withdraw', () => { describe('withdraw', () => {
beforeEach(async () => { beforeEach(async () => {
const ethToDeposit = new BigNumber(web3.toWei(1, 'ether')); const ethToDeposit = new BigNumber(web3.toWei(1, 'ether'));
await zeroEx.etherToken.depositAsync(ethToDeposit, account); await zeroEx.etherToken.depositAsync(etherTokenAddress, ethToDeposit, account);
}); });
it('should throw if caller attempts to withdraw greater than caller balance', async () => { it('should throw if caller attempts to withdraw greater than caller balance', async () => {
const initEthTokenBalance = await zeroEx.token.getBalanceAsync(etherTokenAddress, account); const initEthTokenBalance = await zeroEx.token.getBalanceAsync(etherTokenAddress, account);
const ethTokensToWithdraw = initEthTokenBalance.plus(1); const ethTokensToWithdraw = initEthTokenBalance.plus(1);
return expect(zeroEx.etherToken.withdrawAsync(ethTokensToWithdraw, account)) return expect(zeroEx.etherToken.withdrawAsync(etherTokenAddress, ethTokensToWithdraw, account))
.to.be.rejectedWith(ZeroExError.InsufficientWEthBalanceForWithdrawal); .to.be.rejectedWith(ZeroExError.InsufficientWEthBalanceForWithdrawal);
}); });
@@ -104,7 +103,7 @@ contract('EtherTokenV2', (accounts: string[]) => {
const initEthBalance = await getEthBalanceAsync(account); const initEthBalance = await getEthBalanceAsync(account);
const ethTokensToWithdraw = initEthTokenBalance; const ethTokensToWithdraw = initEthTokenBalance;
expect(ethTokensToWithdraw).to.not.be.bignumber.equal(0); expect(ethTokensToWithdraw).to.not.be.bignumber.equal(0);
const txHash = await zeroEx.etherToken.withdrawAsync(ethTokensToWithdraw, account, { const txHash = await zeroEx.etherToken.withdrawAsync(etherTokenAddress, ethTokensToWithdraw, account, {
gasLimit: constants.MAX_ETHERTOKEN_WITHDRAW_GAS, gasLimit: constants.MAX_ETHERTOKEN_WITHDRAW_GAS,
}); });
const receipt = await zeroEx.awaitTransactionMinedAsync(txHash); const receipt = await zeroEx.awaitTransactionMinedAsync(txHash);
@@ -122,7 +121,7 @@ contract('EtherTokenV2', (accounts: string[]) => {
const initEthTokenBalance = await zeroEx.token.getBalanceAsync(etherTokenAddress, account); const initEthTokenBalance = await zeroEx.token.getBalanceAsync(etherTokenAddress, account);
const ethTokensToWithdraw = initEthTokenBalance; const ethTokensToWithdraw = initEthTokenBalance;
expect(ethTokensToWithdraw).to.not.be.bignumber.equal(0); expect(ethTokensToWithdraw).to.not.be.bignumber.equal(0);
const txHash = await zeroEx.etherToken.withdrawAsync(ethTokensToWithdraw, account, { const txHash = await zeroEx.etherToken.withdrawAsync(etherTokenAddress, ethTokensToWithdraw, account, {
gasLimit: constants.MAX_ETHERTOKEN_WITHDRAW_GAS, gasLimit: constants.MAX_ETHERTOKEN_WITHDRAW_GAS,
}); });
const receipt = await zeroEx.awaitTransactionMinedAsync(txHash); const receipt = await zeroEx.awaitTransactionMinedAsync(txHash);

View File

@@ -13,7 +13,7 @@ import {ZRXRequestQueue} from './zrx_request_queue';
// HACK: web3 leaks XMLHttpRequest into the global scope and causes requests to hang // HACK: web3 leaks XMLHttpRequest into the global scope and causes requests to hang
// because they are using the wrong XHR package. // because they are using the wrong XHR package.
// Issue: https://github.com/trufflesuite/truffle-contract/issues/14 // Filed issue: https://github.com/ethereum/web3.js/issues/844
// tslint:disable-next-line:ordered-imports // tslint:disable-next-line:ordered-imports
import * as Web3 from 'web3'; import * as Web3 from 'web3';

View File

@@ -3,7 +3,7 @@ import * as timers from 'timers';
// HACK: web3 leaks XMLHttpRequest into the global scope and causes requests to hang // HACK: web3 leaks XMLHttpRequest into the global scope and causes requests to hang
// because they are using the wrong XHR package. // because they are using the wrong XHR package.
// Issue: https://github.com/trufflesuite/truffle-contract/issues/14 // Filed issue: https://github.com/ethereum/web3.js/issues/844
// tslint:disable-next-line:ordered-imports // tslint:disable-next-line:ordered-imports
import * as Web3 from 'web3'; import * as Web3 from 'web3';

View File

@@ -9,7 +9,7 @@ import {utils} from './utils';
// HACK: web3 leaks XMLHttpRequest into the global scope and causes requests to hang // HACK: web3 leaks XMLHttpRequest into the global scope and causes requests to hang
// because they are using the wrong XHR package. // because they are using the wrong XHR package.
// Issue: https://github.com/trufflesuite/truffle-contract/issues/14 // Filed issue: https://github.com/ethereum/web3.js/issues/844
// tslint:disable-next-line:ordered-imports // tslint:disable-next-line:ordered-imports
import * as Web3 from 'web3'; import * as Web3 from 'web3';

View File

@@ -1,4 +1,5 @@
# CHANGELOG # CHANGELOG
vx.x.x v0.x.x - _TBD, 2017_
------------------------ ------------------------
* Improve the performance of address fetching (#271)

View File

@@ -23,6 +23,7 @@
"es6-promisify": "^5.0.0", "es6-promisify": "^5.0.0",
"ethereumjs-tx": "^1.3.3", "ethereumjs-tx": "^1.3.3",
"ethereumjs-util": "^5.1.1", "ethereumjs-util": "^5.1.1",
"hdkey": "^0.7.1",
"ledgerco": "0xProject/ledger-node-js-api", "ledgerco": "0xProject/ledger-node-js-api",
"lodash": "^4.17.4", "lodash": "^4.17.4",
"semaphore-async-await": "^1.5.1", "semaphore-async-await": "^1.5.1",

View File

@@ -48,7 +48,7 @@ declare module 'ledgerco' {
public comm: comm; public comm: comm;
constructor(comm: comm); constructor(comm: comm);
public getAddress_async(path: string, display?: boolean, chaincode?: boolean): public getAddress_async(path: string, display?: boolean, chaincode?: boolean):
Promise<{publicKey: string; address: string}>; Promise<{publicKey: string; address: string; chainCode: string}>;
public signTransaction_async(path: string, rawTxHex: string): Promise<ECSignatureString>; public signTransaction_async(path: string, rawTxHex: string): Promise<ECSignatureString>;
public getAppConfiguration_async(): Promise<{ arbitraryDataEnabled: number; version: string }>; public getAppConfiguration_async(): Promise<{ arbitraryDataEnabled: number; version: string }>;
public signPersonalMessage_async(path: string, messageHex: string): Promise<ECSignature>; public signPersonalMessage_async(path: string, messageHex: string): Promise<ECSignature>;
@@ -91,3 +91,14 @@ declare module 'web3-provider-engine' {
} }
export = Web3ProviderEngine; export = Web3ProviderEngine;
} }
// hdkey declarations
declare module 'hdkey' {
class HDNode {
public publicKey: Buffer;
public chainCode: Buffer;
public constructor();
public derive(path: string): HDNode;
}
export = HDNode;
}

View File

@@ -2,6 +2,7 @@ import {assert} from '@0xproject/assert';
import {addressUtils} from '@0xproject/utils'; import {addressUtils} from '@0xproject/utils';
import EthereumTx = require('ethereumjs-tx'); import EthereumTx = require('ethereumjs-tx');
import ethUtil = require('ethereumjs-util'); import ethUtil = require('ethereumjs-util');
import HDNode = require('hdkey');
import * as _ from 'lodash'; import * as _ from 'lodash';
import Semaphore from 'semaphore-async-await'; import Semaphore from 'semaphore-async-await';
import Web3 = require('web3'); import Web3 = require('web3');
@@ -20,7 +21,7 @@ import {Subprovider} from './subprovider';
const DEFAULT_DERIVATION_PATH = `44'/60'/0'`; const DEFAULT_DERIVATION_PATH = `44'/60'/0'`;
const NUM_ADDRESSES_TO_FETCH = 10; const NUM_ADDRESSES_TO_FETCH = 10;
const ASK_FOR_ON_DEVICE_CONFIRMATION = false; const ASK_FOR_ON_DEVICE_CONFIRMATION = false;
const SHOULD_GET_CHAIN_CODE = false; const SHOULD_GET_CHAIN_CODE = true;
export class LedgerSubprovider extends Subprovider { export class LedgerSubprovider extends Subprovider {
private _nonceLock: Semaphore; private _nonceLock: Semaphore;
@@ -127,21 +128,30 @@ export class LedgerSubprovider extends Subprovider {
public async getAccountsAsync(): Promise<string[]> { public async getAccountsAsync(): Promise<string[]> {
this._ledgerClientIfExists = await this.createLedgerClientAsync(); this._ledgerClientIfExists = await this.createLedgerClientAsync();
// TODO: replace with generating addresses without hitting Ledger let ledgerResponse;
try {
ledgerResponse = await this._ledgerClientIfExists.getAddress_async(
this._derivationPath, this._shouldAlwaysAskForConfirmation, SHOULD_GET_CHAIN_CODE,
);
} finally {
await this.destoryLedgerClientAsync();
}
const hdKey = new HDNode();
hdKey.publicKey = new Buffer(ledgerResponse.publicKey, 'hex');
hdKey.chainCode = new Buffer(ledgerResponse.chainCode, 'hex');
const accounts = []; const accounts = [];
for (let i = 0; i < NUM_ADDRESSES_TO_FETCH; i++) { for (let i = 0; i < NUM_ADDRESSES_TO_FETCH; i++) {
try { const derivedHDNode = hdKey.derive(`m/${i + this._derivationPathIndex}`);
const derivationPath = `${this._derivationPath}/${i + this._derivationPathIndex}`; const derivedPublicKey = derivedHDNode.publicKey;
const result = await this._ledgerClientIfExists.getAddress_async( const shouldSanitizePublicKey = true;
derivationPath, this._shouldAlwaysAskForConfirmation, SHOULD_GET_CHAIN_CODE, const ethereumAddressUnprefixed = ethUtil.publicToAddress(
); derivedPublicKey, shouldSanitizePublicKey,
accounts.push(result.address.toLowerCase()); ).toString('hex');
} catch (err) { const ethereumAddressPrefixed = ethUtil.addHexPrefix(ethereumAddressUnprefixed);
await this.destoryLedgerClientAsync(); accounts.push(ethereumAddressPrefixed.toLowerCase());
throw err;
}
} }
await this.destoryLedgerClientAsync();
return accounts; return accounts;
} }
public async signTransactionAsync(txParams: PartialTxParams): Promise<string> { public async signTransactionAsync(txParams: PartialTxParams): Promise<string> {

View File

@@ -10,8 +10,10 @@ export interface LedgerCommunicationClient {
* NodeJs and Browser communication are supported. * NodeJs and Browser communication are supported.
*/ */
export interface LedgerEthereumClient { export interface LedgerEthereumClient {
// shouldGetChainCode is defined as `true` instead of `boolean` because other types rely on the assumption
// that we get back the chain code and we don't have dependent types to express it properly
getAddress_async: (derivationPath: string, askForDeviceConfirmation: boolean, getAddress_async: (derivationPath: string, askForDeviceConfirmation: boolean,
shouldGetChainCode: boolean) => Promise<LedgerGetAddressResult>; shouldGetChainCode: true) => Promise<LedgerGetAddressResult>;
signPersonalMessage_async: (derivationPath: string, messageHex: string) => Promise<ECSignature>; signPersonalMessage_async: (derivationPath: string, messageHex: string) => Promise<ECSignature>;
signTransaction_async: (derivationPath: string, txHex: string) => Promise<ECSignatureString>; signTransaction_async: (derivationPath: string, txHex: string) => Promise<ECSignatureString>;
comm: LedgerCommunicationClient; comm: LedgerCommunicationClient;
@@ -63,6 +65,8 @@ export interface SignatureData {
export interface LedgerGetAddressResult { export interface LedgerGetAddressResult {
address: string; address: string;
publicKey: string;
chainCode: string;
} }
export interface LedgerWalletSubprovider { export interface LedgerWalletSubprovider {

View File

@@ -18,7 +18,7 @@ import {reportCallbackErrors} from '../utils/report_callback_errors';
chaiSetup.configure(); chaiSetup.configure();
const expect = chai.expect; const expect = chai.expect;
const FAKE_ADDRESS = '0x9901c66f2d4b95f7074b553da78084d708beca70'; const FAKE_ADDRESS = '0xb088a3bc93f71b4de97b9de773e9647645983688';
describe('LedgerSubprovider', () => { describe('LedgerSubprovider', () => {
const networkId: number = 42; const networkId: number = 42;
@@ -28,8 +28,14 @@ describe('LedgerSubprovider', () => {
// tslint:disable:no-object-literal-type-assertion // tslint:disable:no-object-literal-type-assertion
const ledgerEthClient = { const ledgerEthClient = {
getAddress_async: async () => { getAddress_async: async () => {
// tslint:disable-next-line:max-line-length
const publicKey = '04f428290f4c5ed6a198f71b8205f488141dbb3f0840c923bbfa798ecbee6370986c03b5575d94d506772fb48a6a44e345e4ebd4f028a6f609c44b655d6d3e71a1';
const chainCode = 'ac055a5537c0c7e9e02d14a197cad6b857836da2a12043b46912a37d959b5ae8';
const address = '0xBa388BA5e5EEF2c6cE42d831c2B3A28D3c99bdB1';
return { return {
address: FAKE_ADDRESS, publicKey,
address,
chainCode,
}; };
}, },
signPersonalMessage_async: async () => { signPersonalMessage_async: async () => {

View File

@@ -388,18 +388,18 @@ export class Blockchain {
const balance = await this.web3Wrapper.getBalanceInEthAsync(owner); const balance = await this.web3Wrapper.getBalanceInEthAsync(owner);
return balance; return balance;
} }
public async convertEthToWrappedEthTokensAsync(amount: BigNumber): Promise<void> { public async convertEthToWrappedEthTokensAsync(etherTokenAddress: string, amount: BigNumber): Promise<void> {
utils.assert(!_.isUndefined(this.zeroEx), 'ZeroEx must be instantiated.'); utils.assert(!_.isUndefined(this.zeroEx), 'ZeroEx must be instantiated.');
utils.assert(this.doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses); utils.assert(this.doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses);
const txHash = await this.zeroEx.etherToken.depositAsync(amount, this.userAddress); const txHash = await this.zeroEx.etherToken.depositAsync(etherTokenAddress, amount, this.userAddress);
await this.showEtherScanLinkAndAwaitTransactionMinedAsync(txHash); await this.showEtherScanLinkAndAwaitTransactionMinedAsync(txHash);
} }
public async convertWrappedEthTokensToEthAsync(amount: BigNumber): Promise<void> { public async convertWrappedEthTokensToEthAsync(etherTokenAddress: string, amount: BigNumber): Promise<void> {
utils.assert(!_.isUndefined(this.zeroEx), 'ZeroEx must be instantiated.'); utils.assert(!_.isUndefined(this.zeroEx), 'ZeroEx must be instantiated.');
utils.assert(this.doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses); utils.assert(this.doesUserAddressExist(), BlockchainCallErrs.UserHasNoAssociatedAddresses);
const txHash = await this.zeroEx.etherToken.withdrawAsync(amount, this.userAddress); const txHash = await this.zeroEx.etherToken.withdrawAsync(etherTokenAddress, amount, this.userAddress);
await this.showEtherScanLinkAndAwaitTransactionMinedAsync(txHash); await this.showEtherScanLinkAndAwaitTransactionMinedAsync(txHash);
} }
public async doesContractExistAtAddressAsync(address: string) { public async doesContractExistAtAddressAsync(address: string) {

View File

@@ -88,12 +88,12 @@ export class EthWethConversionButton extends
let balance = tokenState.balance; let balance = tokenState.balance;
try { try {
if (direction === Side.Deposit) { if (direction === Side.Deposit) {
await this.props.blockchain.convertEthToWrappedEthTokensAsync(value); await this.props.blockchain.convertEthToWrappedEthTokensAsync(token.address, value);
const ethAmount = ZeroEx.toUnitAmount(value, constants.DECIMAL_PLACES_ETH); const ethAmount = ZeroEx.toUnitAmount(value, constants.DECIMAL_PLACES_ETH);
this.props.dispatcher.showFlashMessage(`Successfully wrapped ${ethAmount.toString()} ETH to WETH`); this.props.dispatcher.showFlashMessage(`Successfully wrapped ${ethAmount.toString()} ETH to WETH`);
balance = balance.plus(value); balance = balance.plus(value);
} else { } else {
await this.props.blockchain.convertWrappedEthTokensToEthAsync(value); await this.props.blockchain.convertWrappedEthTokensToEthAsync(token.address, value);
const tokenAmount = ZeroEx.toUnitAmount(value, token.decimals); const tokenAmount = ZeroEx.toUnitAmount(value, token.decimals);
this.props.dispatcher.showFlashMessage(`Successfully unwrapped ${tokenAmount.toString()} WETH to ETH`); this.props.dispatcher.showFlashMessage(`Successfully unwrapped ${tokenAmount.toString()} WETH to ETH`);
balance = balance.minus(value); balance = balance.minus(value);

View File

@@ -7,14 +7,23 @@ interface MarkdownCodeBlockProps {
language: string; language: string;
} }
export function MarkdownCodeBlock(props: MarkdownCodeBlockProps) { interface MarkdownCodeBlockState {}
return (
<span style={{fontSize: 16}}> export class MarkdownCodeBlock extends React.Component<MarkdownCodeBlockProps, MarkdownCodeBlockState> {
<HighLight // Re-rendering a codeblock causes any use selection to become de-selected. This is annoying when trying
className={props.language || 'js'} // to copy-paste code examples. We therefore noop re-renders on this component if it's props haven't changed.
> public shouldComponentUpdate(nextProps: MarkdownCodeBlockProps, nextState: MarkdownCodeBlockState) {
{props.literal} return nextProps.literal !== this.props.literal || nextProps.language !== this.props.language;
</HighLight> }
</span> public render() {
); return (
<span style={{fontSize: 16}}>
<HighLight
className={this.props.language || 'javascript'}
>
{this.props.literal}
</HighLight>
</span>
);
}
} }

View File

@@ -4199,7 +4199,7 @@ hawk@~6.0.2:
hoek "4.x.x" hoek "4.x.x"
sntp "2.x.x" sntp "2.x.x"
hdkey@^0.7.0: hdkey@^0.7.0, hdkey@^0.7.1:
version "0.7.1" version "0.7.1"
resolved "https://registry.yarnpkg.com/hdkey/-/hdkey-0.7.1.tgz#caee4be81aa77921e909b8d228dd0f29acaee632" resolved "https://registry.yarnpkg.com/hdkey/-/hdkey-0.7.1.tgz#caee4be81aa77921e909b8d228dd0f29acaee632"
dependencies: dependencies: