Merge branch 'development' into feature/separate-deployer
This commit is contained in:
@@ -27,12 +27,13 @@ This repository contains all the 0x developer tools written in TypeScript. Our h
|
||||
| [`@0xproject/abi-gen`](/packages/abi-gen) | [](https://www.npmjs.com/package/@0xproject/abi-gen) | Tool to generate TS wrappers from smart contract ABIs |
|
||||
| [`@0xproject/assert`](/packages/assert) | [](https://www.npmjs.com/package/@0xproject/assert) | Type and schema assertions used by our packages |
|
||||
| [`@0xproject/connect`](/packages/connect) | [](https://www.npmjs.com/package/@0xproject/connect) | A Javascript library for interacting with the standard relayer api |
|
||||
| [`@0xproject/dev-utils`](/packages/dev-utils) | [](https://www.npmjs.com/package/@0xproject/dev-utils) | Dev utils to be shared across 0x projects and packages |
|
||||
| [`@0xproject/json-schemas`](/packages/json-schemas) | [](https://www.npmjs.com/package/@0xproject/json-schemas) | 0x-related json schemas |
|
||||
| [`@0xproject/subproviders`](/packages/subproviders) | [](https://www.npmjs.com/package/@0xproject/subproviders) | Useful web3 subproviders (e.g LedgerSubprovider) |
|
||||
| [`@0xproject/tslint-config`](/packages/tslint-config) | [](https://www.npmjs.com/package/@0xproject/tslint-config) | Custom 0x development TSLint rules |
|
||||
| [`@0xproject/types`](/packages/types) | [](https://www.npmjs.com/package/@0xproject/types) | Shared type declarations |
|
||||
| [`@0xproject/utils`](/packages/utils) | [](https://www.npmjs.com/package/@0xproject/utils) | Shared utilities |
|
||||
| [`@0xproject/web3-wrapper`](/packages/web3-wrapper) | [](https://www.npmjs.com/package/@0xproject/web3-wrapper) | Web3 wrapper | |
|
||||
| [`@0xproject/web3-wrapper`](/packages/web3-wrapper) | [](https://www.npmjs.com/package/@0xproject/web3-wrapper) | Web3 wrapper |
|
||||
|
||||
### Private Packages
|
||||
|
||||
|
@@ -15,7 +15,7 @@
|
||||
"mnemonic": "concert load couple harbor equip island argue ramp clarify fence smart topic"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@0xproject/utils": "^0.1.0",
|
||||
"@0xproject/utils": "^0.2.0",
|
||||
"async-child-process": "^1.1.1",
|
||||
"ethereumjs-testrpc": "^6.0.3",
|
||||
"lerna": "^2.5.1",
|
||||
|
@@ -1,5 +1,9 @@
|
||||
# CHANGELOG
|
||||
|
||||
## v0.30.1 - _January 18, 2018_
|
||||
|
||||
* Fix a bug allowing negative fill values (#212)
|
||||
|
||||
## v0.30.0 - _January 17, 2018_
|
||||
|
||||
* Add an error parameter to the order watcher callback (#312)
|
||||
|
@@ -390,6 +390,29 @@ describe('ExchangeWrapper', () => {
|
||||
).to.not.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero);
|
||||
});
|
||||
});
|
||||
describe('negative fill amount', async () => {
|
||||
let signedOrder: SignedOrder;
|
||||
const negativeFillTakerAmount = new BigNumber(-100);
|
||||
beforeEach(async () => {
|
||||
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress,
|
||||
takerTokenAddress,
|
||||
makerAddress,
|
||||
takerAddress,
|
||||
fillableAmount,
|
||||
);
|
||||
});
|
||||
it('should not allow the exchange wrapper to fill if amount is negative', async () => {
|
||||
return expect(
|
||||
zeroEx.exchange.fillOrderAsync(
|
||||
signedOrder,
|
||||
negativeFillTakerAmount,
|
||||
shouldThrowOnInsufficientBalanceOrAllowance,
|
||||
takerAddress,
|
||||
),
|
||||
).to.be.rejected();
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('#batchFillOrdersAsync', () => {
|
||||
let signedOrder: SignedOrder;
|
||||
@@ -498,6 +521,30 @@ describe('ExchangeWrapper', () => {
|
||||
).to.not.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero);
|
||||
});
|
||||
});
|
||||
describe('negative batch fill amount', async () => {
|
||||
beforeEach(async () => {
|
||||
const negativeFillTakerAmount = new BigNumber(-100);
|
||||
orderFillBatch = [
|
||||
{
|
||||
signedOrder,
|
||||
takerTokenFillAmount,
|
||||
},
|
||||
{
|
||||
signedOrder: anotherSignedOrder,
|
||||
takerTokenFillAmount: negativeFillTakerAmount,
|
||||
},
|
||||
];
|
||||
});
|
||||
it('should not allow the exchange wrapper to batch fill if any amount is negative', async () => {
|
||||
return expect(
|
||||
zeroEx.exchange.batchFillOrdersAsync(
|
||||
orderFillBatch,
|
||||
shouldThrowOnInsufficientBalanceOrAllowance,
|
||||
takerAddress,
|
||||
),
|
||||
).to.be.rejected();
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('#fillOrdersUpTo', () => {
|
||||
let signedOrder: SignedOrder;
|
||||
|
@@ -12,6 +12,8 @@ export const assert = {
|
||||
},
|
||||
isValidBaseUnitAmount(variableName: string, value: BigNumber) {
|
||||
assert.isBigNumber(variableName, value);
|
||||
const isNegative = value.lessThan(0);
|
||||
this.assert(!isNegative, `${variableName} cannot be a negative number, found value: ${value.toNumber()}`);
|
||||
const hasDecimals = value.decimalPlaces() !== 0;
|
||||
this.assert(
|
||||
!hasDecimals,
|
||||
|
@@ -22,6 +22,20 @@ describe('Assertions', () => {
|
||||
invalidInputs.forEach(input => expect(assert.isBigNumber.bind(assert, variableName, input)).to.throw());
|
||||
});
|
||||
});
|
||||
describe('#isValidBaseUnitAmount', () => {
|
||||
it('should not throw for valid input', () => {
|
||||
const validInputs = [new BigNumber(23), new BigNumber('45000000')];
|
||||
validInputs.forEach(input =>
|
||||
expect(assert.isValidBaseUnitAmount.bind(assert, variableName, input)).to.not.throw(),
|
||||
);
|
||||
});
|
||||
it('should throw for invalid input', () => {
|
||||
const invalidInputs = [0, undefined, new BigNumber(3.145), 3.145, new BigNumber(-400)];
|
||||
invalidInputs.forEach(input =>
|
||||
expect(assert.isValidBaseUnitAmount.bind(assert, variableName, input)).to.throw(),
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('#isString', () => {
|
||||
it('should not throw for valid input', () => {
|
||||
const validInputs = ['hello', 'goodbye'];
|
||||
|
BIN
packages/website/public/images/social/discourse.png
Normal file
BIN
packages/website/public/images/social/discourse.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
@@ -368,14 +368,22 @@ export class Blockchain {
|
||||
|
||||
const [currBalance] = await this.getTokenBalanceAndAllowanceAsync(this._userAddress, token.address);
|
||||
|
||||
this._zrxPollIntervalId = intervalUtils.setAsyncExcludingInterval(async () => {
|
||||
const [balance] = await this.getTokenBalanceAndAllowanceAsync(this._userAddress, token.address);
|
||||
if (!balance.eq(currBalance)) {
|
||||
this._dispatcher.replaceTokenBalanceByAddress(token.address, balance);
|
||||
clearInterval(this._zrxPollIntervalId);
|
||||
this._zrxPollIntervalId = intervalUtils.setAsyncExcludingInterval(
|
||||
async () => {
|
||||
const [balance] = await this.getTokenBalanceAndAllowanceAsync(this._userAddress, token.address);
|
||||
if (!balance.eq(currBalance)) {
|
||||
this._dispatcher.replaceTokenBalanceByAddress(token.address, balance);
|
||||
intervalUtils.clearAsyncExcludingInterval(this._zrxPollIntervalId);
|
||||
delete this._zrxPollIntervalId;
|
||||
}
|
||||
},
|
||||
5000,
|
||||
(err: Error) => {
|
||||
utils.consoleLog(`Polling tokenBalance failed: ${err}`);
|
||||
intervalUtils.clearAsyncExcludingInterval(this._zrxPollIntervalId);
|
||||
delete this._zrxPollIntervalId;
|
||||
}
|
||||
}, 5000);
|
||||
},
|
||||
);
|
||||
}
|
||||
public async signOrderHashAsync(orderHash: string): Promise<SignatureData> {
|
||||
utils.assert(!_.isUndefined(this._zeroEx), 'ZeroEx must be instantiated.');
|
||||
@@ -471,7 +479,7 @@ export class Blockchain {
|
||||
this._web3Wrapper.updatePrevUserAddress(newUserAddress);
|
||||
}
|
||||
public destroy() {
|
||||
clearInterval(this._zrxPollIntervalId);
|
||||
intervalUtils.clearAsyncExcludingInterval(this._zrxPollIntervalId);
|
||||
this._web3Wrapper.destroy();
|
||||
this._stopWatchingExchangeLogFillEvents();
|
||||
}
|
||||
|
@@ -13,7 +13,6 @@ interface FooterMenuItem {
|
||||
title: string;
|
||||
path?: string;
|
||||
isExternal?: boolean;
|
||||
fileName?: string;
|
||||
}
|
||||
|
||||
enum Sections {
|
||||
@@ -56,25 +55,26 @@ const menuItemsBySection: MenuItemsBySection = {
|
||||
title: 'Rocket.chat',
|
||||
isExternal: true,
|
||||
path: constants.URL_ZEROEX_CHAT,
|
||||
fileName: 'rocketchat.png',
|
||||
},
|
||||
{
|
||||
title: 'Blog',
|
||||
isExternal: true,
|
||||
path: constants.URL_BLOG,
|
||||
fileName: 'medium.png',
|
||||
},
|
||||
{
|
||||
title: 'Twitter',
|
||||
isExternal: true,
|
||||
path: constants.URL_TWITTER,
|
||||
fileName: 'twitter.png',
|
||||
},
|
||||
{
|
||||
title: 'Reddit',
|
||||
isExternal: true,
|
||||
path: constants.URL_REDDIT,
|
||||
fileName: 'reddit.png',
|
||||
},
|
||||
{
|
||||
title: 'Forum',
|
||||
isExternal: true,
|
||||
path: constants.URL_DISCOURSE_FORUM,
|
||||
},
|
||||
],
|
||||
Organization: [
|
||||
@@ -105,6 +105,7 @@ const titleToIcon: { [title: string]: string } = {
|
||||
Blog: 'medium.png',
|
||||
Twitter: 'twitter.png',
|
||||
Reddit: 'reddit.png',
|
||||
Forum: 'discourse.png',
|
||||
};
|
||||
|
||||
export interface FooterProps {}
|
||||
|
@@ -189,8 +189,8 @@ export class NewTokenForm extends React.Component<NewTokenFormProps, NewTokenFor
|
||||
const tokenWithSymbolExists = !_.isUndefined(_.find(tokens, { symbol }));
|
||||
if (symbol === '') {
|
||||
symbolErrText = 'Symbol is required';
|
||||
} else if (!this._isLetters(symbol)) {
|
||||
symbolErrText = 'Can only include letters';
|
||||
} else if (!this._isAlphanumeric(symbol)) {
|
||||
symbolErrText = 'Can only include alphanumeric characters';
|
||||
} else if (symbol.length > maxLength) {
|
||||
symbolErrText = `Max length is ${maxLength}`;
|
||||
} else if (tokenWithSymbolExists) {
|
||||
@@ -231,7 +231,7 @@ export class NewTokenForm extends React.Component<NewTokenFormProps, NewTokenFor
|
||||
private _isInteger(input: string) {
|
||||
return /^[0-9]+$/i.test(input);
|
||||
}
|
||||
private _isLetters(input: string) {
|
||||
return /^[a-zA-Z]+$/i.test(input);
|
||||
private _isAlphanumeric(input: string) {
|
||||
return /^[a-zA-Z0-9]+$/i.test(input);
|
||||
}
|
||||
}
|
||||
|
@@ -169,7 +169,7 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala
|
||||
? 'In order to try out the 0x Portal Dapp, request some test ether to pay for \
|
||||
gas costs. It might take a bit of time for the test ether to show up.'
|
||||
: 'Ether must be converted to Ether Tokens in order to be tradable via 0x. \
|
||||
You can convert between Ether and Ether Tokens by clicking the "convert" button below.'}
|
||||
You can convert between Ether and Ether Tokens from the "Wrap ETH" tab.'}
|
||||
</div>
|
||||
<Table selectable={false} style={styles.bgColor}>
|
||||
<TableHeader displaySelectAll={false} adjustForCheckbox={false}>
|
||||
|
@@ -168,14 +168,14 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> {
|
||||
</div>
|
||||
)}
|
||||
{this.props.blockchainIsLoaded &&
|
||||
!_.isEmpty(this.props.userAddress) && <div className="col col-5">{this._renderUser()}</div>}
|
||||
{!this._isViewingPortal() && (
|
||||
<div className={`col ${isFullWidthPage ? 'col-2 pl2' : 'col-1'} md-hide lg-hide`}>
|
||||
<div style={menuIconStyle}>
|
||||
<i className="zmdi zmdi-menu" onClick={this._onMenuButtonClick.bind(this)} />
|
||||
</div>
|
||||
!_.isEmpty(this.props.userAddress) && (
|
||||
<div className="col col-5 sm-hide xs-hide">{this._renderUser()}</div>
|
||||
)}
|
||||
<div className={`col ${isFullWidthPage ? 'col-2 pl2' : 'col-1'} md-hide lg-hide`}>
|
||||
<div style={menuIconStyle}>
|
||||
<i className="zmdi zmdi-menu" onClick={this._onMenuButtonClick.bind(this)} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{this._renderDrawer()}
|
||||
</div>
|
||||
|
@@ -118,8 +118,8 @@ export const configs = {
|
||||
] as OutdatedWrappedEtherByNetworkId[],
|
||||
// The order matters. We first try first node and only then fall back to others.
|
||||
PUBLIC_NODE_URLS_BY_NETWORK_ID: {
|
||||
[1]: [`https://mainnet.infura.io/${INFURA_API_KEY}`],
|
||||
[42]: [`https://kovan.infura.io/${INFURA_API_KEY}`],
|
||||
[1]: [`https://mainnet.infura.io/${INFURA_API_KEY}`, 'https://mainnet.0xproject.com'],
|
||||
[42]: [`https://kovan.infura.io/${INFURA_API_KEY}`, 'https://kovan.0xproject.com'],
|
||||
} as PublicNodeUrlsByNetworkId,
|
||||
SHOULD_DEPRECATE_OLD_WETH_TOKEN: true,
|
||||
SYMBOLS_OF_MINTABLE_TOKENS: ['MKR', 'MLN', 'GNT', 'DGD', 'REP'],
|
||||
|
@@ -65,6 +65,7 @@ export const constants = {
|
||||
URL_BIGNUMBERJS_GITHUB: 'http://mikemcl.github.io/bignumber.js',
|
||||
URL_BITLY_API: 'https://api-ssl.bitly.com',
|
||||
URL_BLOG: 'https://blog.0xproject.com/latest',
|
||||
URL_DISCOURSE_FORUM: 'https://forum.0xproject.com',
|
||||
URL_FIREFOX_U2F_ADDON: 'https://addons.mozilla.org/en-US/firefox/addon/u2f-support-add-on/',
|
||||
URL_ETHER_FAUCET: 'https://faucet.0xproject.com',
|
||||
URL_GITHUB_ORG: 'https://github.com/0xProject',
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { BigNumber, intervalUtils, promisify } from '@0xproject/utils';
|
||||
import * as _ from 'lodash';
|
||||
import { Dispatcher } from 'ts/redux/dispatcher';
|
||||
import { utils } from 'ts/utils/utils';
|
||||
import * as Web3 from 'web3';
|
||||
|
||||
export class Web3Wrapper {
|
||||
@@ -101,41 +102,48 @@ export class Web3Wrapper {
|
||||
let prevNodeVersion: string;
|
||||
this._prevUserEtherBalanceInEth = new BigNumber(0);
|
||||
this._dispatcher.updateNetworkId(this._prevNetworkId);
|
||||
this._watchNetworkAndBalanceIntervalId = intervalUtils.setAsyncExcludingInterval(async () => {
|
||||
// Check for network state changes
|
||||
const currentNetworkId = await this.getNetworkIdIfExists();
|
||||
if (currentNetworkId !== this._prevNetworkId) {
|
||||
this._prevNetworkId = currentNetworkId;
|
||||
this._dispatcher.updateNetworkId(currentNetworkId);
|
||||
}
|
||||
|
||||
// Check for node version changes
|
||||
const currentNodeVersion = await this.getNodeVersionAsync();
|
||||
if (currentNodeVersion !== prevNodeVersion) {
|
||||
prevNodeVersion = currentNodeVersion;
|
||||
this._dispatcher.updateNodeVersion(currentNodeVersion);
|
||||
}
|
||||
|
||||
if (this._shouldPollUserAddress) {
|
||||
const userAddressIfExists = await this.getFirstAccountIfExistsAsync();
|
||||
// Update makerAddress on network change
|
||||
if (this._prevUserAddress !== userAddressIfExists) {
|
||||
this._prevUserAddress = userAddressIfExists;
|
||||
this._dispatcher.updateUserAddress(userAddressIfExists);
|
||||
this._watchNetworkAndBalanceIntervalId = intervalUtils.setAsyncExcludingInterval(
|
||||
async () => {
|
||||
// Check for network state changes
|
||||
const currentNetworkId = await this.getNetworkIdIfExists();
|
||||
if (currentNetworkId !== this._prevNetworkId) {
|
||||
this._prevNetworkId = currentNetworkId;
|
||||
this._dispatcher.updateNetworkId(currentNetworkId);
|
||||
}
|
||||
|
||||
// Check for user ether balance changes
|
||||
if (userAddressIfExists !== '') {
|
||||
await this._updateUserEtherBalanceAsync(userAddressIfExists);
|
||||
// Check for node version changes
|
||||
const currentNodeVersion = await this.getNodeVersionAsync();
|
||||
if (currentNodeVersion !== prevNodeVersion) {
|
||||
prevNodeVersion = currentNodeVersion;
|
||||
this._dispatcher.updateNodeVersion(currentNodeVersion);
|
||||
}
|
||||
} else {
|
||||
// This logic is primarily for the Ledger, since we don't regularly poll for the address
|
||||
// we simply update the balance for the last fetched address.
|
||||
if (!_.isEmpty(this._prevUserAddress)) {
|
||||
await this._updateUserEtherBalanceAsync(this._prevUserAddress);
|
||||
|
||||
if (this._shouldPollUserAddress) {
|
||||
const userAddressIfExists = await this.getFirstAccountIfExistsAsync();
|
||||
// Update makerAddress on network change
|
||||
if (this._prevUserAddress !== userAddressIfExists) {
|
||||
this._prevUserAddress = userAddressIfExists;
|
||||
this._dispatcher.updateUserAddress(userAddressIfExists);
|
||||
}
|
||||
|
||||
// Check for user ether balance changes
|
||||
if (userAddressIfExists !== '') {
|
||||
await this._updateUserEtherBalanceAsync(userAddressIfExists);
|
||||
}
|
||||
} else {
|
||||
// This logic is primarily for the Ledger, since we don't regularly poll for the address
|
||||
// we simply update the balance for the last fetched address.
|
||||
if (!_.isEmpty(this._prevUserAddress)) {
|
||||
await this._updateUserEtherBalanceAsync(this._prevUserAddress);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 5000);
|
||||
},
|
||||
5000,
|
||||
(err: Error) => {
|
||||
utils.consoleLog(`Watching network and balances failed: ${err}`);
|
||||
this._stopEmittingNetworkConnectionAndUserBalanceStateAsync();
|
||||
},
|
||||
);
|
||||
}
|
||||
private async _updateUserEtherBalanceAsync(userAddress: string) {
|
||||
const balance = await this.getBalanceInEthAsync(userAddress);
|
||||
@@ -145,6 +153,6 @@ export class Web3Wrapper {
|
||||
}
|
||||
}
|
||||
private _stopEmittingNetworkConnectionAndUserBalanceStateAsync() {
|
||||
clearInterval(this._watchNetworkAndBalanceIntervalId);
|
||||
intervalUtils.clearAsyncExcludingInterval(this._watchNetworkAndBalanceIntervalId);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user