Merge pull request #594 from 0xProject/improvement/addCustomTslintRules

Add more tslint rules
This commit is contained in:
Fabio Berger 2018-05-22 18:04:50 +02:00 committed by GitHub
commit cc840a6911
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
59 changed files with 421 additions and 127 deletions

View File

@ -1,9 +1,23 @@
const networkNameToId: { [networkName: string]: number } = {
mainnet: 1,
ropsten: 3,
rinkeby: 4,
kovan: 42,
ganache: 50,
};
export const zeroExPublicNetworkConfigSchema = { export const zeroExPublicNetworkConfigSchema = {
id: '/ZeroExPublicNetworkConfig', id: '/ZeroExPublicNetworkConfig',
properties: { properties: {
networkId: { networkId: {
type: 'number', type: 'number',
enum: [1, 3, 4, 42, 50], enum: [
networkNameToId.mainnet,
networkNameToId.ropsten,
networkNameToId.rinkeby,
networkNameToId.kovan,
networkNameToId.ganache,
],
}, },
gasPrice: { $ref: '/Number' }, gasPrice: { $ref: '/Number' },
zrxContractAddress: { $ref: '/Address' }, zrxContractAddress: { $ref: '/Address' },

View File

@ -8,7 +8,8 @@ import { provider } from './utils/web3_wrapper';
before('migrate contracts', async function(): Promise<void> { before('migrate contracts', async function(): Promise<void> {
// HACK: Since the migrations take longer then our global mocha timeout limit // HACK: Since the migrations take longer then our global mocha timeout limit
// we manually increase it for this before hook. // we manually increase it for this before hook.
this.timeout(20000); const mochaTestTimeoutMs = 20000;
this.timeout(mochaTestTimeoutMs);
const txDefaults = { const txDefaults = {
gas: devConstants.GAS_ESTIMATE, gas: devConstants.GAS_ESTIMATE,
from: devConstants.TESTRPC_FIRST_ADDRESS, from: devConstants.TESTRPC_FIRST_ADDRESS,

View File

@ -11,6 +11,7 @@ chai.config.includeStack = true;
chai.use(dirtyChai); chai.use(dirtyChai);
const expect = chai.expect; const expect = chai.expect;
// tslint:disable:custom-no-magic-numbers
describe('Assertions', () => { describe('Assertions', () => {
const variableName = 'variable'; const variableName = 'variable';
describe('#isBigNumber', () => { describe('#isBigNumber', () => {
@ -252,3 +253,4 @@ describe('Assertions', () => {
}); });
}); });
}); });
// tslint:enable:custom-no-magic-numbers

View File

@ -1,9 +1,23 @@
const networkNameToId: { [networkName: string]: number } = {
mainnet: 1,
ropsten: 3,
rinkeby: 4,
kovan: 42,
ganache: 50,
};
export const contractWrappersPublicNetworkConfigSchema = { export const contractWrappersPublicNetworkConfigSchema = {
id: '/ZeroExContractPublicNetworkConfig', id: '/ZeroExContractPublicNetworkConfig',
properties: { properties: {
networkId: { networkId: {
type: 'number', type: 'number',
enum: [1, 3, 4, 42, 50], enum: [
networkNameToId.mainnet,
networkNameToId.kovan,
networkNameToId.ropsten,
networkNameToId.rinkeby,
networkNameToId.ganache,
],
}, },
gasPrice: { $ref: '/Number' }, gasPrice: { $ref: '/Number' },
zrxContractAddress: { $ref: '/Address' }, zrxContractAddress: { $ref: '/Address' },

View File

@ -6,6 +6,7 @@ export const constants = {
INVALID_JUMP_PATTERN: 'invalid JUMP at', INVALID_JUMP_PATTERN: 'invalid JUMP at',
OUT_OF_GAS_PATTERN: 'out of gas', OUT_OF_GAS_PATTERN: 'out of gas',
INVALID_TAKER_FORMAT: 'instance.taker is not of a type(s) string', INVALID_TAKER_FORMAT: 'instance.taker is not of a type(s) string',
// tslint:disable-next-line:custom-no-magic-numbers
UNLIMITED_ALLOWANCE_IN_BASE_UNITS: new BigNumber(2).pow(256).minus(1), UNLIMITED_ALLOWANCE_IN_BASE_UNITS: new BigNumber(2).pow(256).minus(1),
DEFAULT_BLOCK_POLLING_INTERVAL: 1000, DEFAULT_BLOCK_POLLING_INTERVAL: 1000,
}; };

View File

@ -73,14 +73,14 @@ export const filterUtils = {
return false; return false;
} }
if (!_.isUndefined(filter.topics)) { if (!_.isUndefined(filter.topics)) {
return filterUtils.matchesTopics(log.topics, filter.topics); return filterUtils.doesMatchTopics(log.topics, filter.topics);
} }
return true; return true;
}, },
matchesTopics(logTopics: string[], filterTopics: Array<string[] | string | null>): boolean { doesMatchTopics(logTopics: string[], filterTopics: Array<string[] | string | null>): boolean {
const matchesTopic = _.zipWith(logTopics, filterTopics, filterUtils.matchesTopic.bind(filterUtils)); const matchesTopic = _.zipWith(logTopics, filterTopics, filterUtils.matchesTopic.bind(filterUtils));
const matchesTopics = _.every(matchesTopic); const doesMatchTopics = _.every(matchesTopic);
return matchesTopics; return doesMatchTopics;
}, },
matchesTopic(logTopic: string, filterTopic: string[] | string | null): boolean { matchesTopic(logTopic: string, filterTopic: string[] | string | null): boolean {
if (_.isArray(filterTopic)) { if (_.isArray(filterTopic)) {

View File

@ -5,7 +5,8 @@ export const utils = {
return new Error(`Unexpected switch value: ${value} encountered for ${name}`); return new Error(`Unexpected switch value: ${value} encountered for ${name}`);
}, },
getCurrentUnixTimestampSec(): BigNumber { getCurrentUnixTimestampSec(): BigNumber {
return new BigNumber(Date.now() / 1000).round(); const milisecondsInSecond = 1000;
return new BigNumber(Date.now() / milisecondsInSecond).round();
}, },
getCurrentUnixTimestampMs(): BigNumber { getCurrentUnixTimestampMs(): BigNumber {
return new BigNumber(Date.now()); return new BigNumber(Date.now());

View File

@ -42,7 +42,7 @@ describe('EtherTokenWrapper', () => {
let addressWithETH: string; let addressWithETH: string;
let wethContractAddress: string; let wethContractAddress: string;
let depositWeiAmount: BigNumber; let depositWeiAmount: BigNumber;
let decimalPlaces: number; const decimalPlaces = 7;
let addressWithoutFunds: string; let addressWithoutFunds: string;
const gasPrice = new BigNumber(1); const gasPrice = new BigNumber(1);
const zeroExConfig = { const zeroExConfig = {
@ -60,7 +60,6 @@ describe('EtherTokenWrapper', () => {
addressWithETH = userAddresses[0]; addressWithETH = userAddresses[0];
wethContractAddress = contractWrappers.etherToken.getContractAddressIfExists() as string; wethContractAddress = contractWrappers.etherToken.getContractAddressIfExists() as string;
depositWeiAmount = Web3Wrapper.toWei(new BigNumber(5)); depositWeiAmount = Web3Wrapper.toWei(new BigNumber(5));
decimalPlaces = 7;
addressWithoutFunds = userAddresses[1]; addressWithoutFunds = userAddresses[1];
}); });
beforeEach(async () => { beforeEach(async () => {
@ -155,6 +154,7 @@ describe('EtherTokenWrapper', () => {
const preWETHBalance = await contractWrappers.token.getBalanceAsync(wethContractAddress, addressWithETH); const preWETHBalance = await contractWrappers.token.getBalanceAsync(wethContractAddress, addressWithETH);
expect(preWETHBalance).to.be.bignumber.equal(0); expect(preWETHBalance).to.be.bignumber.equal(0);
// tslint:disable-next-line:custom-no-magic-numbers
const overWETHBalance = preWETHBalance.add(999999999); const overWETHBalance = preWETHBalance.add(999999999);
return expect( return expect(

View File

@ -8,7 +8,8 @@ import { provider } from './utils/web3_wrapper';
before('migrate contracts', async function(): Promise<void> { before('migrate contracts', async function(): Promise<void> {
// HACK: Since the migrations take longer then our global mocha timeout limit // HACK: Since the migrations take longer then our global mocha timeout limit
// we manually increase it for this before hook. // we manually increase it for this before hook.
this.timeout(20000); const mochaTestTimeoutMs = 20000;
this.timeout(mochaTestTimeoutMs);
const txDefaults = { const txDefaults = {
gas: devConstants.GAS_ESTIMATE, gas: devConstants.GAS_ESTIMATE,
from: devConstants.TESTRPC_FIRST_ADDRESS, from: devConstants.TESTRPC_FIRST_ADDRESS,

View File

@ -106,6 +106,7 @@ describe('OrderValidation', () => {
}); });
it('should succeed if the order is asymmetric and fillable', async () => { it('should succeed if the order is asymmetric and fillable', async () => {
const makerFillableAmount = fillableAmount; const makerFillableAmount = fillableAmount;
// tslint:disable-next-line:custom-no-magic-numbers
const takerFillableAmount = fillableAmount.minus(4); const takerFillableAmount = fillableAmount.minus(4);
const signedOrder = await fillScenarios.createAsymmetricFillableSignedOrderAsync( const signedOrder = await fillScenarios.createAsymmetricFillableSignedOrderAsync(
makerTokenAddress, makerTokenAddress,
@ -172,6 +173,7 @@ describe('OrderValidation', () => {
fillableAmount, fillableAmount,
); );
// 27 <--> 28 // 27 <--> 28
// tslint:disable-next-line:custom-no-magic-numbers
signedOrder.ecSignature.v = 28 - signedOrder.ecSignature.v + 27; signedOrder.ecSignature.v = 28 - signedOrder.ecSignature.v + 27;
return expect( return expect(
contractWrappers.exchange.validateFillOrderThrowIfInvalidAsync( contractWrappers.exchange.validateFillOrderThrowIfInvalidAsync(
@ -206,6 +208,7 @@ describe('OrderValidation', () => {
takerAddress, takerAddress,
fillableAmount, fillableAmount,
); );
// tslint:disable-next-line:custom-no-magic-numbers
const nonTakerAddress = userAddresses[6]; const nonTakerAddress = userAddresses[6];
return expect( return expect(
contractWrappers.exchange.validateFillOrderThrowIfInvalidAsync( contractWrappers.exchange.validateFillOrderThrowIfInvalidAsync(
@ -353,6 +356,7 @@ describe('OrderValidation', () => {
takerAddress, takerAddress,
zrxTokenAddress, zrxTokenAddress,
); );
// tslint:disable-next-line:custom-no-magic-numbers
expect(transferFromAsync.callCount).to.be.equal(4); expect(transferFromAsync.callCount).to.be.equal(4);
expect( expect(
transferFromAsync transferFromAsync
@ -423,6 +427,7 @@ describe('OrderValidation', () => {
takerAddress, takerAddress,
zrxTokenAddress, zrxTokenAddress,
); );
// tslint:disable-next-line:custom-no-magic-numbers
expect(transferFromAsync.callCount).to.be.equal(4); expect(transferFromAsync.callCount).to.be.equal(4);
expect( expect(
transferFromAsync transferFromAsync
@ -491,6 +496,7 @@ describe('OrderValidation', () => {
takerAddress, takerAddress,
zrxTokenAddress, zrxTokenAddress,
); );
// tslint:disable-next-line:custom-no-magic-numbers
expect(transferFromAsync.callCount).to.be.equal(4); expect(transferFromAsync.callCount).to.be.equal(4);
const makerFillAmount = transferFromAsync.getCall(0).args[3]; const makerFillAmount = transferFromAsync.getCall(0).args[3];
expect(makerFillAmount).to.be.bignumber.equal(makerTokenAmount); expect(makerFillAmount).to.be.bignumber.equal(makerTokenAmount);
@ -518,6 +524,7 @@ describe('OrderValidation', () => {
); );
const makerPartialFee = makerFee.div(2); const makerPartialFee = makerFee.div(2);
const takerPartialFee = takerFee.div(2); const takerPartialFee = takerFee.div(2);
// tslint:disable-next-line:custom-no-magic-numbers
expect(transferFromAsync.callCount).to.be.equal(4); expect(transferFromAsync.callCount).to.be.equal(4);
const partialMakerFee = transferFromAsync.getCall(2).args[3]; const partialMakerFee = transferFromAsync.getCall(2).args[3];
expect(partialMakerFee).to.be.bignumber.equal(makerPartialFee); expect(partialMakerFee).to.be.bignumber.equal(makerPartialFee);

View File

@ -124,21 +124,21 @@ describe('Exchange', () => {
}); });
it('should return true with a valid signature', async () => { it('should return true with a valid signature', async () => {
const success = await exchangeWrapper.isValidSignatureAsync(signedOrder); const isValidSignatureForContract = await exchangeWrapper.isValidSignatureAsync(signedOrder);
const orderHashHex = ZeroEx.getOrderHashHex(signedOrder); const orderHashHex = ZeroEx.getOrderHashHex(signedOrder);
const isValidSignature = ZeroEx.isValidSignature(orderHashHex, signedOrder.ecSignature, signedOrder.maker); const isValidSignature = ZeroEx.isValidSignature(orderHashHex, signedOrder.ecSignature, signedOrder.maker);
expect(isValidSignature).to.be.true(); expect(isValidSignature).to.be.true();
expect(success).to.be.true(); expect(isValidSignatureForContract).to.be.true();
}); });
it('should return false with an invalid signature', async () => { it('should return false with an invalid signature', async () => {
signedOrder.ecSignature.r = ethUtil.bufferToHex(ethUtil.sha3('invalidR')); signedOrder.ecSignature.r = ethUtil.bufferToHex(ethUtil.sha3('invalidR'));
signedOrder.ecSignature.s = ethUtil.bufferToHex(ethUtil.sha3('invalidS')); signedOrder.ecSignature.s = ethUtil.bufferToHex(ethUtil.sha3('invalidS'));
const success = await exchangeWrapper.isValidSignatureAsync(signedOrder); const isValidSignatureForContract = await exchangeWrapper.isValidSignatureAsync(signedOrder);
const orderHashHex = ZeroEx.getOrderHashHex(signedOrder); const orderHashHex = ZeroEx.getOrderHashHex(signedOrder);
const isValidSignature = ZeroEx.isValidSignature(orderHashHex, signedOrder.ecSignature, signedOrder.maker); const isValidSignature = ZeroEx.isValidSignature(orderHashHex, signedOrder.ecSignature, signedOrder.maker);
expect(isValidSignature).to.be.false(); expect(isValidSignature).to.be.false();
expect(success).to.be.false(); expect(isValidSignatureForContract).to.be.false();
}); });
}); });

View File

@ -100,8 +100,8 @@ describe('UnlimitedAllowanceToken', () => {
const amountToTransfer = ownerBalance; const amountToTransfer = ownerBalance;
const spenderAllowance = await zeroEx.token.getAllowanceAsync(tokenAddress, owner, spender); const spenderAllowance = await zeroEx.token.getAllowanceAsync(tokenAddress, owner, spender);
const spenderAllowanceIsInsufficient = spenderAllowance.cmp(amountToTransfer) < 0; const isSpenderAllowanceInsufficient = spenderAllowance.cmp(amountToTransfer) < 0;
expect(spenderAllowanceIsInsufficient).to.be.true(); expect(isSpenderAllowanceInsufficient).to.be.true();
return expect( return expect(
token.transferFrom.callAsync(owner, spender, amountToTransfer, { token.transferFrom.callAsync(owner, spender, amountToTransfer, {

View File

@ -118,8 +118,8 @@ describe('ZRXToken', () => {
const amountToTransfer = ownerBalance; const amountToTransfer = ownerBalance;
const spenderAllowance = await zeroEx.token.getAllowanceAsync(zrxAddress, owner, spender); const spenderAllowance = await zeroEx.token.getAllowanceAsync(zrxAddress, owner, spender);
const spenderAllowanceIsInsufficient = spenderAllowance.cmp(amountToTransfer) < 0; const isSpenderAllowanceInsufficient = spenderAllowance.cmp(amountToTransfer) < 0;
expect(spenderAllowanceIsInsufficient).to.be.true(); expect(isSpenderAllowanceInsufficient).to.be.true();
const didReturnTrue = await zrx.transferFrom.callAsync(owner, spender, amountToTransfer, { from: spender }); const didReturnTrue = await zrx.transferFrom.callAsync(owner, spender, amountToTransfer, { from: spender });
expect(didReturnTrue).to.be.false(); expect(didReturnTrue).to.be.false();

View File

@ -1,3 +1,6 @@
{ {
"extends": ["@0xproject/tslint-config"] "extends": ["@0xproject/tslint-config"],
"rules": {
"custom-no-magic-numbers": false
}
} }

View File

@ -149,7 +149,8 @@ describe('Schema', () => {
}); });
describe('#blockParamSchema', () => { describe('#blockParamSchema', () => {
it('should validate valid block param', () => { it('should validate valid block param', () => {
const testCases = [42, 'latest', 'pending', 'earliest']; const blockNumber = 42;
const testCases = [blockNumber, 'latest', 'pending', 'earliest'];
validateAgainstSchema(testCases, blockParamSchema); validateAgainstSchema(testCases, blockParamSchema);
}); });
it('should fail for invalid block param', () => { it('should fail for invalid block param', () => {
@ -182,6 +183,7 @@ describe('Schema', () => {
validateAgainstSchema(testCases, tokenSchema); validateAgainstSchema(testCases, tokenSchema);
}); });
it('should fail for invalid token', () => { it('should fail for invalid token', () => {
const num = 4;
const testCases = [ const testCases = [
{ {
...token, ...token,
@ -192,7 +194,7 @@ describe('Schema', () => {
decimals: undefined, decimals: undefined,
}, },
[], [],
4, num,
]; ];
const shouldFail = true; const shouldFail = true;
validateAgainstSchema(testCases, tokenSchema, shouldFail); validateAgainstSchema(testCases, tokenSchema, shouldFail);
@ -871,10 +873,12 @@ describe('Schema', () => {
}); });
describe('#jsNumberSchema', () => { describe('#jsNumberSchema', () => {
it('should validate valid js number', () => { it('should validate valid js number', () => {
// tslint:disable-next-line:custom-no-magic-numbers
const testCases = [1, 42]; const testCases = [1, 42];
validateAgainstSchema(testCases, jsNumber); validateAgainstSchema(testCases, jsNumber);
}); });
it('should fail for invalid js number', () => { it('should fail for invalid js number', () => {
// tslint:disable-next-line:custom-no-magic-numbers
const testCases = [NaN, -1, new BigNumber(1)]; const testCases = [NaN, -1, new BigNumber(1)];
const shouldFail = true; const shouldFail = true;
validateAgainstSchema(testCases, jsNumber, shouldFail); validateAgainstSchema(testCases, jsNumber, shouldFail);
@ -882,13 +886,14 @@ describe('Schema', () => {
}); });
describe('#txDataSchema', () => { describe('#txDataSchema', () => {
it('should validate valid txData', () => { it('should validate valid txData', () => {
const bigNumGasAmount = new BigNumber(42);
const testCases = [ const testCases = [
{ {
from: NULL_ADDRESS, from: NULL_ADDRESS,
}, },
{ {
from: NULL_ADDRESS, from: NULL_ADDRESS,
gas: new BigNumber(42), gas: bigNumGasAmount,
}, },
{ {
from: NULL_ADDRESS, from: NULL_ADDRESS,

View File

@ -80,11 +80,12 @@ export const runMigrationsAsync = async (provider: Provider, artifactsDir: strin
tokenInfo[0].swarmHash, tokenInfo[0].swarmHash,
{ from: owner }, { from: owner },
); );
const decimals = 18;
await tokenReg.addToken.sendTransactionAsync( await tokenReg.addToken.sendTransactionAsync(
zrxToken.address, zrxToken.address,
'0x Protocol Token', '0x Protocol Token',
'ZRX', 'ZRX',
18, decimals,
NULL_BYTES, NULL_BYTES,
NULL_BYTES, NULL_BYTES,
{ {
@ -96,7 +97,7 @@ export const runMigrationsAsync = async (provider: Provider, artifactsDir: strin
etherToken.address, etherToken.address,
'Ether Token', 'Ether Token',
'WETH', 'WETH',
18, decimals,
NULL_BYTES, NULL_BYTES,
NULL_BYTES, NULL_BYTES,
{ {

View File

@ -158,6 +158,7 @@ export const postpublishUtils = {
// HACK: tsconfig.json needs wildcard directory endings as `/**/*` // HACK: tsconfig.json needs wildcard directory endings as `/**/*`
// but TypeDoc needs it as `/**` in order to pick up files at the root // but TypeDoc needs it as `/**` in order to pick up files at the root
if (_.endsWith(includePath, '/**/*')) { if (_.endsWith(includePath, '/**/*')) {
// tslint:disable-next-line:custom-no-magic-numbers
includePath = includePath.slice(0, -2); includePath = includePath.slice(0, -2);
} }
return includePath; return includePath;

View File

@ -285,8 +285,8 @@ function shouldAddNewChangelogEntry(currentVersion: string, changelogs: Changelo
return true; return true;
} }
const lastEntry = changelogs[0]; const lastEntry = changelogs[0];
const lastEntryCurrentVersion = lastEntry.version === currentVersion; const isLastEntryCurrentVersion = lastEntry.version === currentVersion;
return lastEntryCurrentVersion; return isLastEntryCurrentVersion;
} }
function generateChangelogMd(changelogs: Changelog[]): string { function generateChangelogMd(changelogs: Changelog[]): string {

View File

@ -17,7 +17,8 @@ const INVALID_TAKER_FORMAT = 'instance.taker is not of a type(s) string';
* We do not use BN anywhere else in the codebase. * We do not use BN anywhere else in the codebase.
*/ */
function bigNumberToBN(value: BigNumber): BN { function bigNumberToBN(value: BigNumber): BN {
return new BN(value.toString(), 10); const base = 10;
return new BN(value.toString(), base);
} }
/** /**

View File

@ -72,6 +72,7 @@ export async function signOrderHashAsync(
// v + r + s OR r + s + v, and different clients (even different versions of the same client) // v + r + s OR r + s + v, and different clients (even different versions of the same client)
// return the signature params in different orders. In order to support all client implementations, // return the signature params in different orders. In order to support all client implementations,
// we parse the signature in both ways, and evaluate if either one is a valid signature. // we parse the signature in both ways, and evaluate if either one is a valid signature.
// tslint:disable-next-line:custom-no-magic-numbers
const validVParamValues = [27, 28]; const validVParamValues = [27, 28];
const ecSignatureVRS = parseSignatureHexAsVRS(signature); const ecSignatureVRS = parseSignatureHexAsVRS(signature);
if (_.includes(validVParamValues, ecSignatureVRS.v)) { if (_.includes(validVParamValues, ecSignatureVRS.v)) {
@ -95,11 +96,19 @@ export async function signOrderHashAsync(
function parseSignatureHexAsVRS(signatureHex: string): ECSignature { function parseSignatureHexAsVRS(signatureHex: string): ECSignature {
const signatureBuffer = ethUtil.toBuffer(signatureHex); const signatureBuffer = ethUtil.toBuffer(signatureHex);
let v = signatureBuffer[0]; let v = signatureBuffer[0];
if (v < 27) { // HACK: Sometimes v is returned as [0, 1] and sometimes as [27, 28]
v += 27; // If it is returned as [0, 1], add 27 to both so it becomes [27, 28]
const lowestValidV = 27;
const isProperlyFormattedV = v < lowestValidV;
if (!isProperlyFormattedV) {
v += lowestValidV;
} }
const r = signatureBuffer.slice(1, 33); // signatureBuffer contains vrs
const s = signatureBuffer.slice(33, 65); const vEndIndex = 1;
const rsIndex = 33;
const r = signatureBuffer.slice(vEndIndex, rsIndex);
const sEndIndex = 65;
const s = signatureBuffer.slice(rsIndex, sEndIndex);
const ecSignature: ECSignature = { const ecSignature: ECSignature = {
v, v,
r: ethUtil.bufferToHex(r), r: ethUtil.bufferToHex(r),

View File

@ -47,12 +47,13 @@ describe('Signature utils', () => {
}); });
describe('#generateSalt', () => { describe('#generateSalt', () => {
it('generates different salts', () => { it('generates different salts', () => {
const equal = generatePseudoRandomSalt().eq(generatePseudoRandomSalt()); const isEqual = generatePseudoRandomSalt().eq(generatePseudoRandomSalt());
expect(equal).to.be.false(); expect(isEqual).to.be.false();
}); });
it('generates salt in range [0..2^256)', () => { it('generates salt in range [0..2^256)', () => {
const salt = generatePseudoRandomSalt(); const salt = generatePseudoRandomSalt();
expect(salt.greaterThanOrEqualTo(0)).to.be.true(); expect(salt.greaterThanOrEqualTo(0)).to.be.true();
// tslint:disable-next-line:custom-no-magic-numbers
const twoPow256 = new BigNumber(2).pow(256); const twoPow256 = new BigNumber(2).pow(256);
expect(salt.lessThan(twoPow256)).to.be.true(); expect(salt.lessThan(twoPow256)).to.be.true();
}); });
@ -67,7 +68,8 @@ describe('Signature utils', () => {
expect(isValid).to.be.false(); expect(isValid).to.be.false();
}); });
it('returns true if order hash is correct', () => { it('returns true if order hash is correct', () => {
const isValid = isValidOrderHash('0x' + Array(65).join('0')); const orderHashLength = 65;
const isValid = isValidOrderHash('0x' + Array(orderHashLength).join('0'));
expect(isValid).to.be.true(); expect(isValid).to.be.true();
}); });
}); });
@ -111,10 +113,12 @@ describe('Signature utils', () => {
if (payload.method === 'eth_sign') { if (payload.method === 'eth_sign') {
const [address, message] = payload.params; const [address, message] = payload.params;
const signature = await web3Wrapper.signMessageAsync(address, message); const signature = await web3Wrapper.signMessageAsync(address, message);
// tslint:disable-next-line:custom-no-magic-numbers
const rsvHex = `0x${signature.substr(130)}${signature.substr(2, 128)}`;
callback(null, { callback(null, {
id: 42, id: 42,
jsonrpc: '2.0', jsonrpc: '2.0',
result: `0x${signature.substr(130)}${signature.substr(2, 128)}`, result: rsvHex,
}); });
} else { } else {
callback(null, { id: 42, jsonrpc: '2.0', result: [makerAddress] }); callback(null, { id: 42, jsonrpc: '2.0', result: [makerAddress] });

View File

@ -60,6 +60,7 @@ interface OrderStateByOrderHash {
[orderHash: string]: OrderState; [orderHash: string]: OrderState;
} }
// tslint:disable-next-line:custom-no-magic-numbers
const DEFAULT_CLEANUP_JOB_INTERVAL_MS = 1000 * 60 * 60; // 1h const DEFAULT_CLEANUP_JOB_INTERVAL_MS = 1000 * 60 * 60; // 1h
/** /**
@ -130,7 +131,8 @@ export class OrderWatcher {
assert.isValidSignature(orderHash, signedOrder.ecSignature, signedOrder.maker); assert.isValidSignature(orderHash, signedOrder.ecSignature, signedOrder.maker);
this._orderByOrderHash[orderHash] = signedOrder; this._orderByOrderHash[orderHash] = signedOrder;
this._addToDependentOrderHashes(signedOrder, orderHash); this._addToDependentOrderHashes(signedOrder, orderHash);
const expirationUnixTimestampMs = signedOrder.expirationUnixTimestampSec.times(1000); const milisecondsInASecond = 1000;
const expirationUnixTimestampMs = signedOrder.expirationUnixTimestampSec.times(milisecondsInASecond);
this._expirationWatcher.addOrder(orderHash, expirationUnixTimestampMs); this._expirationWatcher.addOrder(orderHash, expirationUnixTimestampMs);
} }
/** /**

View File

@ -5,7 +5,8 @@ export const utils = {
return new Error(`Unexpected switch value: ${value} encountered for ${name}`); return new Error(`Unexpected switch value: ${value} encountered for ${name}`);
}, },
getCurrentUnixTimestampSec(): BigNumber { getCurrentUnixTimestampSec(): BigNumber {
return new BigNumber(Date.now() / 1000).round(); const milisecondsInASecond = 1000;
return new BigNumber(Date.now() / milisecondsInASecond).round();
}, },
getCurrentUnixTimestampMs(): BigNumber { getCurrentUnixTimestampMs(): BigNumber {
return new BigNumber(Date.now()); return new BigNumber(Date.now());

View File

@ -22,6 +22,7 @@ import { provider, web3Wrapper } from './utils/web3_wrapper';
chaiSetup.configure(); chaiSetup.configure();
const expect = chai.expect; const expect = chai.expect;
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
const MILISECONDS_IN_SECOND = 1000;
describe('ExpirationWatcher', () => { describe('ExpirationWatcher', () => {
let contractWrappers: ContractWrappers; let contractWrappers: ContractWrappers;
@ -84,13 +85,13 @@ describe('ExpirationWatcher', () => {
expirationUnixTimestampSec, expirationUnixTimestampSec,
); );
const orderHash = getOrderHashHex(signedOrder); const orderHash = getOrderHashHex(signedOrder);
expirationWatcher.addOrder(orderHash, signedOrder.expirationUnixTimestampSec.times(1000)); expirationWatcher.addOrder(orderHash, signedOrder.expirationUnixTimestampSec.times(MILISECONDS_IN_SECOND));
const callbackAsync = callbackErrorReporter.reportNoErrorCallbackErrors(done)((hash: string) => { const callbackAsync = callbackErrorReporter.reportNoErrorCallbackErrors(done)((hash: string) => {
expect(hash).to.be.equal(orderHash); expect(hash).to.be.equal(orderHash);
expect(utils.getCurrentUnixTimestampSec()).to.be.bignumber.gte(expirationUnixTimestampSec); expect(utils.getCurrentUnixTimestampSec()).to.be.bignumber.gte(expirationUnixTimestampSec);
}); });
expirationWatcher.subscribe(callbackAsync); expirationWatcher.subscribe(callbackAsync);
timer.tick(orderLifetimeSec * 1000); timer.tick(orderLifetimeSec * MILISECONDS_IN_SECOND);
})().catch(done); })().catch(done);
}); });
it("doesn't emit events before order expires", (done: DoneCallback) => { it("doesn't emit events before order expires", (done: DoneCallback) => {
@ -106,13 +107,13 @@ describe('ExpirationWatcher', () => {
expirationUnixTimestampSec, expirationUnixTimestampSec,
); );
const orderHash = getOrderHashHex(signedOrder); const orderHash = getOrderHashHex(signedOrder);
expirationWatcher.addOrder(orderHash, signedOrder.expirationUnixTimestampSec.times(1000)); expirationWatcher.addOrder(orderHash, signedOrder.expirationUnixTimestampSec.times(MILISECONDS_IN_SECOND));
const callbackAsync = callbackErrorReporter.reportNoErrorCallbackErrors(done)(async (hash: string) => { const callbackAsync = callbackErrorReporter.reportNoErrorCallbackErrors(done)(async (hash: string) => {
done(new Error('Emitted expiration went before the order actually expired')); done(new Error('Emitted expiration went before the order actually expired'));
}); });
expirationWatcher.subscribe(callbackAsync); expirationWatcher.subscribe(callbackAsync);
const notEnoughTime = orderLifetimeSec - 1; const notEnoughTime = orderLifetimeSec - 1;
timer.tick(notEnoughTime * 1000); timer.tick(notEnoughTime * MILISECONDS_IN_SECOND);
done(); done();
})().catch(done); })().catch(done);
}); });
@ -140,8 +141,14 @@ describe('ExpirationWatcher', () => {
); );
const orderHash1 = getOrderHashHex(signedOrder1); const orderHash1 = getOrderHashHex(signedOrder1);
const orderHash2 = getOrderHashHex(signedOrder2); const orderHash2 = getOrderHashHex(signedOrder2);
expirationWatcher.addOrder(orderHash2, signedOrder2.expirationUnixTimestampSec.times(1000)); expirationWatcher.addOrder(
expirationWatcher.addOrder(orderHash1, signedOrder1.expirationUnixTimestampSec.times(1000)); orderHash2,
signedOrder2.expirationUnixTimestampSec.times(MILISECONDS_IN_SECOND),
);
expirationWatcher.addOrder(
orderHash1,
signedOrder1.expirationUnixTimestampSec.times(MILISECONDS_IN_SECOND),
);
const expirationOrder = [orderHash1, orderHash2]; const expirationOrder = [orderHash1, orderHash2];
const expectToBeCalledOnce = false; const expectToBeCalledOnce = false;
const callbackAsync = callbackErrorReporter.reportNoErrorCallbackErrors(done, expectToBeCalledOnce)( const callbackAsync = callbackErrorReporter.reportNoErrorCallbackErrors(done, expectToBeCalledOnce)(
@ -154,7 +161,7 @@ describe('ExpirationWatcher', () => {
}, },
); );
expirationWatcher.subscribe(callbackAsync); expirationWatcher.subscribe(callbackAsync);
timer.tick(order2Lifetime * 1000); timer.tick(order2Lifetime * MILISECONDS_IN_SECOND);
})().catch(done); })().catch(done);
}); });
it('emits events in correct order when expirations are equal', (done: DoneCallback) => { it('emits events in correct order when expirations are equal', (done: DoneCallback) => {
@ -181,8 +188,14 @@ describe('ExpirationWatcher', () => {
); );
const orderHash1 = getOrderHashHex(signedOrder1); const orderHash1 = getOrderHashHex(signedOrder1);
const orderHash2 = getOrderHashHex(signedOrder2); const orderHash2 = getOrderHashHex(signedOrder2);
expirationWatcher.addOrder(orderHash1, signedOrder1.expirationUnixTimestampSec.times(1000)); expirationWatcher.addOrder(
expirationWatcher.addOrder(orderHash2, signedOrder2.expirationUnixTimestampSec.times(1000)); orderHash1,
signedOrder1.expirationUnixTimestampSec.times(MILISECONDS_IN_SECOND),
);
expirationWatcher.addOrder(
orderHash2,
signedOrder2.expirationUnixTimestampSec.times(MILISECONDS_IN_SECOND),
);
const expirationOrder = orderHash1 < orderHash2 ? [orderHash1, orderHash2] : [orderHash2, orderHash1]; const expirationOrder = orderHash1 < orderHash2 ? [orderHash1, orderHash2] : [orderHash2, orderHash1];
const expectToBeCalledOnce = false; const expectToBeCalledOnce = false;
const callbackAsync = callbackErrorReporter.reportNoErrorCallbackErrors(done, expectToBeCalledOnce)( const callbackAsync = callbackErrorReporter.reportNoErrorCallbackErrors(done, expectToBeCalledOnce)(
@ -195,7 +208,7 @@ describe('ExpirationWatcher', () => {
}, },
); );
expirationWatcher.subscribe(callbackAsync); expirationWatcher.subscribe(callbackAsync);
timer.tick(order2Lifetime * 1000); timer.tick(order2Lifetime * MILISECONDS_IN_SECOND);
})().catch(done); })().catch(done);
}); });
}); });

View File

@ -9,7 +9,8 @@ import { provider } from './utils/web3_wrapper';
before('migrate contracts', async function(): Promise<void> { before('migrate contracts', async function(): Promise<void> {
// HACK: Since the migrations take longer then our global mocha timeout limit // HACK: Since the migrations take longer then our global mocha timeout limit
// we manually increase it for this before hook. // we manually increase it for this before hook.
this.timeout(20000); const mochaTestTimeoutMs = 20000;
this.timeout(mochaTestTimeoutMs);
const txDefaults = { const txDefaults = {
gas: devConstants.GAS_ESTIMATE, gas: devConstants.GAS_ESTIMATE,
from: devConstants.TESTRPC_FIRST_ADDRESS, from: devConstants.TESTRPC_FIRST_ADDRESS,

View File

@ -269,8 +269,8 @@ describe('OrderWatcher', () => {
}); });
it('should trigger the callback when orders backing ZRX allowance changes', (done: DoneCallback) => { it('should trigger the callback when orders backing ZRX allowance changes', (done: DoneCallback) => {
(async () => { (async () => {
const makerFee = Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18); const makerFee = Web3Wrapper.toBaseUnitAmount(new BigNumber(2), decimals);
const takerFee = Web3Wrapper.toBaseUnitAmount(new BigNumber(0), 18); const takerFee = Web3Wrapper.toBaseUnitAmount(new BigNumber(0), decimals);
signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync( signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync(
makerToken.address, makerToken.address,
takerToken.address, takerToken.address,

View File

@ -64,8 +64,8 @@ export class DocsInfo {
finalMenu.contracts = _.filter(finalMenu.contracts, (contractName: string) => { finalMenu.contracts = _.filter(finalMenu.contracts, (contractName: string) => {
const versionIntroducedIfExists = this._docsInfo.menuSubsectionToVersionWhenIntroduced[contractName]; const versionIntroducedIfExists = this._docsInfo.menuSubsectionToVersionWhenIntroduced[contractName];
if (!_.isUndefined(versionIntroducedIfExists)) { if (!_.isUndefined(versionIntroducedIfExists)) {
const existsInSelectedVersion = compareVersions(selectedVersion, versionIntroducedIfExists) >= 0; const doesExistInSelectedVersion = compareVersions(selectedVersion, versionIntroducedIfExists) >= 0;
return existsInSelectedVersion; return doesExistInSelectedVersion;
} else { } else {
return true; return true;
} }

View File

@ -3,6 +3,7 @@
"rules": { "rules": {
"no-object-literal-type-assertion": false, "no-object-literal-type-assertion": false,
"completed-docs": false, "completed-docs": false,
"prefer-function-over-method": false "prefer-function-over-method": false,
"custom-no-magic-numbers": false
} }
} }

View File

@ -3,6 +3,7 @@
"rules": { "rules": {
"no-object-literal-type-assertion": false, "no-object-literal-type-assertion": false,
"completed-docs": false, "completed-docs": false,
"prefer-function-over-method": false "prefer-function-over-method": false,
"custom-no-magic-numbers": false
} }
} }

View File

@ -152,7 +152,8 @@ export class Compiler {
logUtils.log(`Downloading ${fullSolcVersion}...`); logUtils.log(`Downloading ${fullSolcVersion}...`);
const url = `${constants.BASE_COMPILER_URL}${fullSolcVersion}`; const url = `${constants.BASE_COMPILER_URL}${fullSolcVersion}`;
const response = await fetch(url); const response = await fetch(url);
if (response.status !== 200) { const SUCCESS_STATUS = 200;
if (response.status !== SUCCESS_STATUS) {
throw new Error(`Failed to load ${fullSolcVersion}`); throw new Error(`Failed to load ${fullSolcVersion}`);
} }
solcjs = await response.text(); solcjs = await response.text();

View File

@ -39,8 +39,13 @@ describe('#Compiler', function(): void {
const exchangeArtifactString = await fsWrapper.readFileAsync(exchangeArtifactPath, opts); const exchangeArtifactString = await fsWrapper.readFileAsync(exchangeArtifactPath, opts);
const exchangeArtifact: ContractArtifact = JSON.parse(exchangeArtifactString); const exchangeArtifact: ContractArtifact = JSON.parse(exchangeArtifactString);
// The last 43 bytes of the binaries are metadata which may not be equivalent // The last 43 bytes of the binaries are metadata which may not be equivalent
const unlinkedBinaryWithoutMetadata = exchangeArtifact.compilerOutput.evm.bytecode.object.slice(2, -86); const metadataByteLength = 43;
const exchangeBinaryWithoutMetadata = exchange_binary.slice(0, -86); const metadataHexLength = metadataByteLength * 2;
const unlinkedBinaryWithoutMetadata = exchangeArtifact.compilerOutput.evm.bytecode.object.slice(
2,
-metadataHexLength,
);
const exchangeBinaryWithoutMetadata = exchange_binary.slice(0, -metadataHexLength);
expect(unlinkedBinaryWithoutMetadata).to.equal(exchangeBinaryWithoutMetadata); expect(unlinkedBinaryWithoutMetadata).to.equal(exchangeBinaryWithoutMetadata);
}); });
}); });

View File

@ -40,12 +40,14 @@ describe('Collect coverage entries', () => {
const coverageEntries = collectCoverageEntries(simpleStorageContract); const coverageEntries = collectCoverageEntries(simpleStorageContract);
const fnIds = _.keys(coverageEntries.fnMap); const fnIds = _.keys(coverageEntries.fnMap);
expect(coverageEntries.fnMap[fnIds[0]].name).to.be.equal('set'); expect(coverageEntries.fnMap[fnIds[0]].name).to.be.equal('set');
// tslint:disable-next-line:custom-no-magic-numbers
expect(coverageEntries.fnMap[fnIds[0]].line).to.be.equal(5); expect(coverageEntries.fnMap[fnIds[0]].line).to.be.equal(5);
const setFunction = `function set(uint x) { const setFunction = `function set(uint x) {
storedData = x; storedData = x;
}`; }`;
expect(getRange(simpleStorageContract, coverageEntries.fnMap[fnIds[0]].loc)).to.be.equal(setFunction); expect(getRange(simpleStorageContract, coverageEntries.fnMap[fnIds[0]].loc)).to.be.equal(setFunction);
expect(coverageEntries.fnMap[fnIds[1]].name).to.be.equal('get'); expect(coverageEntries.fnMap[fnIds[1]].name).to.be.equal('get');
// tslint:disable-next-line:custom-no-magic-numbers
expect(coverageEntries.fnMap[fnIds[1]].line).to.be.equal(8); expect(coverageEntries.fnMap[fnIds[1]].line).to.be.equal(8);
const getFunction = `function get() constant returns (uint retVal) { const getFunction = `function get() constant returns (uint retVal) {
return storedData; return storedData;
@ -122,6 +124,7 @@ describe('Collect coverage entries', () => {
const branchDescriptions = _.values(coverageEntries.branchMap); const branchDescriptions = _.values(coverageEntries.branchMap);
const branchLines = _.map(branchDescriptions, branchDescription => branchDescription.line); const branchLines = _.map(branchDescriptions, branchDescription => branchDescription.line);
// tslint:disable-next-line:custom-no-magic-numbers
expect(branchLines).to.be.deep.equal([94, 115, 119, 130, 151, 187]); expect(branchLines).to.be.deep.equal([94, 115, 119, 130, 151, 187]);
const branchTypes = _.map(branchDescriptions, branchDescription => branchDescription.type); const branchTypes = _.map(branchDescriptions, branchDescription => branchDescription.type);
expect(branchTypes).to.be.deep.equal(['if', 'if', 'if', 'if', 'binary-expr', 'if']); expect(branchTypes).to.be.deep.equal(['if', 'if', 'if', 'if', 'binary-expr', 'if']);

View File

@ -12,6 +12,7 @@ const expect = chai.expect;
describe('instructions', () => { describe('instructions', () => {
describe('#getPcToInstructionIndexMapping', () => { describe('#getPcToInstructionIndexMapping', () => {
it('correctly maps pcs to instruction indexed', () => { it('correctly maps pcs to instruction indexed', () => {
// tslint:disable-next-line:custom-no-magic-numbers
const bytecode = new Uint8Array([constants.PUSH1, 42, constants.PUSH2, 1, 2, constants.TIMESTAMP]); const bytecode = new Uint8Array([constants.PUSH1, 42, constants.PUSH2, 1, 2, constants.TIMESTAMP]);
const pcToInstruction = getPcToInstructionIndexMapping(bytecode); const pcToInstruction = getPcToInstructionIndexMapping(bytecode);
const expectedPcToInstruction = { '0': 0, '2': 1, '5': 2 }; const expectedPcToInstruction = { '0': 0, '2': 1, '5': 2 };

View File

@ -13,7 +13,18 @@ import { postmanEnvironmentFactory } from './postman_environment_factory';
import { utils } from './utils'; import { utils } from './utils';
const DEFAULT_NETWORK_ID = 1; const DEFAULT_NETWORK_ID = 1;
const SUPPORTED_NETWORK_IDS = [1, 3, 4, 42]; const networkNameToId: { [networkName: string]: number } = {
mainnet: 1,
ropsten: 3,
rinkeby: 4,
kovan: 42,
};
const SUPPORTED_NETWORK_IDS = [
networkNameToId.mainnet,
networkNameToId.ropsten,
networkNameToId.rinkeby,
networkNameToId.kovan,
];
// extract command line arguments // extract command line arguments
const args = yargs const args = yargs

View File

@ -11,6 +11,12 @@ import { addresses as rinkebyAddresses } from './contract_addresses/rinkeby_addr
import { addresses as ropstenAddresses } from './contract_addresses/ropsten_addresses'; import { addresses as ropstenAddresses } from './contract_addresses/ropsten_addresses';
const ENVIRONMENT_NAME = 'SRA Report'; const ENVIRONMENT_NAME = 'SRA Report';
const networkNameToId: { [networkName: string]: number } = {
mainnet: 1,
ropsten: 3,
rinkeby: 4,
kovan: 42,
};
export interface EnvironmentValue { export interface EnvironmentValue {
key: string; key: string;
@ -107,13 +113,13 @@ async function createOrderEnvironmentValuesAsync(url: string): Promise<Environme
} }
function getContractAddresses(networkId: number): Addresses { function getContractAddresses(networkId: number): Addresses {
switch (networkId) { switch (networkId) {
case 1: case networkNameToId.mainnet:
return mainnetAddresses; return mainnetAddresses;
case 3: case networkNameToId.ropsten:
return ropstenAddresses; return ropstenAddresses;
case 4: case networkNameToId.rinkeby:
return rinkebyAddresses; return rinkebyAddresses;
case 42: case networkNameToId.kovan:
return kovanAddresses; return kovanAddresses;
default: default:
throw new Error('Unsupported network id'); throw new Error('Unsupported network id');

View File

@ -24,6 +24,7 @@ const expect = chai.expect;
const CONTENT_TYPE_ASSERTION_NAME = 'Has Content-Type header with value application/json'; const CONTENT_TYPE_ASSERTION_NAME = 'Has Content-Type header with value application/json';
const SCHEMA_ASSERTION_NAME = 'Schema is valid'; const SCHEMA_ASSERTION_NAME = 'Schema is valid';
const SUCCESS_STATUS = 200;
const baseNewmanRunOptions = { const baseNewmanRunOptions = {
collection: sraReportCollectionJSON, collection: sraReportCollectionJSON,
environment: postmanEnvironmentJSON, environment: postmanEnvironmentJSON,
@ -46,7 +47,7 @@ export const testRunner = {
}; };
describe(CONTENT_TYPE_ASSERTION_NAME, () => { describe(CONTENT_TYPE_ASSERTION_NAME, () => {
it('fails when there are no headers', async () => { it('fails when there are no headers', async () => {
nockInterceptor.reply(200, {}); nockInterceptor.reply(SUCCESS_STATUS, {});
const summary = await utils.newmanRunAsync(newmanRunOptions); const summary = await utils.newmanRunAsync(newmanRunOptions);
const error = findAssertionErrorIfExists( const error = findAssertionErrorIfExists(
summary, summary,
@ -61,7 +62,7 @@ export const testRunner = {
const headers = { const headers = {
'Content-Type': 'text/html', 'Content-Type': 'text/html',
}; };
nockInterceptor.reply(200, {}, headers); nockInterceptor.reply(SUCCESS_STATUS, {}, headers);
const summary = await utils.newmanRunAsync(newmanRunOptions); const summary = await utils.newmanRunAsync(newmanRunOptions);
const error = findAssertionErrorIfExists( const error = findAssertionErrorIfExists(
summary, summary,
@ -76,7 +77,7 @@ export const testRunner = {
const headers = { const headers = {
'Content-Type': 'charset=utf-8; application/json', 'Content-Type': 'charset=utf-8; application/json',
}; };
nockInterceptor.reply(200, {}, headers); nockInterceptor.reply(SUCCESS_STATUS, {}, headers);
const summary = await utils.newmanRunAsync(newmanRunOptions); const summary = await utils.newmanRunAsync(newmanRunOptions);
const error = findAssertionErrorIfExists( const error = findAssertionErrorIfExists(
summary, summary,
@ -100,7 +101,7 @@ export const testRunner = {
}; };
describe(SCHEMA_ASSERTION_NAME, () => { describe(SCHEMA_ASSERTION_NAME, () => {
it('fails when schema is invalid', async () => { it('fails when schema is invalid', async () => {
nockInterceptor.reply(200, malformedJson); nockInterceptor.reply(SUCCESS_STATUS, malformedJson);
const summary = await utils.newmanRunAsync(newmanRunOptions); const summary = await utils.newmanRunAsync(newmanRunOptions);
const error = findAssertionErrorIfExists(summary, postmanCollectionRequestName, SCHEMA_ASSERTION_NAME); const error = findAssertionErrorIfExists(summary, postmanCollectionRequestName, SCHEMA_ASSERTION_NAME);
const errorMessage = _.get(error, 'message'); const errorMessage = _.get(error, 'message');
@ -108,7 +109,7 @@ export const testRunner = {
expect(errorMessage).to.equal('expected false to be true'); expect(errorMessage).to.equal('expected false to be true');
}); });
it('passes when schema is valid', async () => { it('passes when schema is valid', async () => {
nockInterceptor.reply(200, correctJson); nockInterceptor.reply(SUCCESS_STATUS, correctJson);
const summary = await utils.newmanRunAsync(newmanRunOptions); const summary = await utils.newmanRunAsync(newmanRunOptions);
const error = findAssertionErrorIfExists(summary, postmanCollectionRequestName, SCHEMA_ASSERTION_NAME); const error = findAssertionErrorIfExists(summary, postmanCollectionRequestName, SCHEMA_ASSERTION_NAME);
const errorMessage = _.get(error, 'message'); const errorMessage = _.get(error, 'message');

View File

@ -113,9 +113,12 @@ export class LedgerSubprovider extends BaseWalletSubprovider {
const tx = new EthereumTx(txParams); const tx = new EthereumTx(txParams);
// Set the EIP155 bits // Set the EIP155 bits
tx.raw[6] = Buffer.from([this._networkId]); // v const vIndex = 6;
tx.raw[7] = Buffer.from([]); // r tx.raw[vIndex] = Buffer.from([this._networkId]); // v
tx.raw[8] = Buffer.from([]); // s const rIndex = 7;
tx.raw[rIndex] = Buffer.from([]); // r
const sIndex = 8;
tx.raw[sIndex] = Buffer.from([]); // s
const txHex = tx.serialize().toString('hex'); const txHex = tx.serialize().toString('hex');
try { try {
@ -127,7 +130,8 @@ export class LedgerSubprovider extends BaseWalletSubprovider {
tx.v = Buffer.from(result.v, 'hex'); tx.v = Buffer.from(result.v, 'hex');
// EIP155: v should be chain_id * 2 + {35, 36} // EIP155: v should be chain_id * 2 + {35, 36}
const signedChainId = Math.floor((tx.v[0] - 35) / 2); const eip55Constant = 35;
const signedChainId = Math.floor((tx.v[0] - eip55Constant) / 2);
if (signedChainId !== this._networkId) { if (signedChainId !== this._networkId) {
await this._destroyLedgerClientAsync(); await this._destroyLedgerClientAsync();
const err = new Error(LedgerSubproviderErrors.TooOldLedgerFirmware); const err = new Error(LedgerSubproviderErrors.TooOldLedgerFirmware);
@ -169,8 +173,10 @@ export class LedgerSubprovider extends BaseWalletSubprovider {
fullDerivationPath, fullDerivationPath,
ethUtil.stripHexPrefix(data), ethUtil.stripHexPrefix(data),
); );
const v = result.v - 27; const lowestValidV = 27;
let vHex = v.toString(16); const v = result.v - lowestValidV;
const hexBase = 16;
let vHex = v.toString(hexBase);
if (vHex.length < 2) { if (vHex.length < 2) {
vHex = `0${v}`; vHex = `0${v}`;
} }

View File

@ -93,7 +93,8 @@ export class NonceTrackerSubprovider extends Subprovider {
// Increment the nonce from the previous successfully submitted transaction // Increment the nonce from the previous successfully submitted transaction
let nonce = ethUtil.bufferToInt(transaction.nonce); let nonce = ethUtil.bufferToInt(transaction.nonce);
nonce++; nonce++;
let nextHexNonce = nonce.toString(16); const hexBase = 16;
let nextHexNonce = nonce.toString(hexBase);
if (nextHexNonce.length % 2) { if (nextHexNonce.length % 2) {
nextHexNonce = `0${nextHexNonce}`; nextHexNonce = `0${nextHexNonce}`;
} }

View File

@ -13,10 +13,11 @@ export abstract class Subprovider {
// Ported from: https://github.com/MetaMask/provider-engine/blob/master/util/random-id.js // Ported from: https://github.com/MetaMask/provider-engine/blob/master/util/random-id.js
private static _getRandomId(): number { private static _getRandomId(): number {
const extraDigits = 3; const extraDigits = 3;
const baseTen = 10;
// 13 time digits // 13 time digits
const datePart = new Date().getTime() * Math.pow(10, extraDigits); const datePart = new Date().getTime() * Math.pow(baseTen, extraDigits);
// 3 random digits // 3 random digits
const extraPart = Math.floor(Math.random() * Math.pow(10, extraDigits)); const extraPart = Math.floor(Math.random() * Math.pow(baseTen, extraDigits));
// 16 digits // 16 digits
return datePart + extraPart; return datePart + extraPart;
} }

View File

@ -30,10 +30,10 @@ class DerivedHDKeyInfoIterator implements IterableIterator<DerivedHDKeyInfo> {
baseDerivationPath, baseDerivationPath,
derivationPath: fullDerivationPath, derivationPath: fullDerivationPath,
}; };
const done = this._index === this._searchLimit; const isDone = this._index === this._searchLimit;
this._index++; this._index++;
return { return {
done, done: isDone,
value: derivedKey, value: derivedKey,
}; };
} }

View File

@ -20,6 +20,8 @@ import { reportCallbackErrors } from '../utils/report_callback_errors';
chaiSetup.configure(); chaiSetup.configure();
const expect = chai.expect; const expect = chai.expect;
const DEFAULT_NUM_ACCOUNTS = 10;
const EXPECTED_SIGNATURE_LENGTH = 132;
async function ledgerEthereumNodeJsClientFactoryAsync(): Promise<LedgerEthereumClient> { async function ledgerEthereumNodeJsClientFactoryAsync(): Promise<LedgerEthereumClient> {
const ledgerConnection = await TransportNodeHid.create(); const ledgerConnection = await TransportNodeHid.create();
@ -41,7 +43,7 @@ describe('LedgerSubprovider', () => {
it('returns default number of accounts', async () => { it('returns default number of accounts', async () => {
const accounts = await ledgerSubprovider.getAccountsAsync(); const accounts = await ledgerSubprovider.getAccountsAsync();
expect(accounts[0]).to.not.be.an('undefined'); expect(accounts[0]).to.not.be.an('undefined');
expect(accounts.length).to.be.equal(10); expect(accounts.length).to.be.equal(DEFAULT_NUM_ACCOUNTS);
}); });
it('returns the expected accounts from a ledger set up with the test mnemonic', async () => { it('returns the expected accounts from a ledger set up with the test mnemonic', async () => {
const accounts = await ledgerSubprovider.getAccountsAsync(); const accounts = await ledgerSubprovider.getAccountsAsync();
@ -105,7 +107,7 @@ describe('LedgerSubprovider', () => {
}; };
const callback = reportCallbackErrors(done)((err: Error, response: JSONRPCResponsePayload) => { const callback = reportCallbackErrors(done)((err: Error, response: JSONRPCResponsePayload) => {
expect(err).to.be.a('null'); expect(err).to.be.a('null');
expect(response.result.length).to.be.equal(10); expect(response.result.length).to.be.equal(DEFAULT_NUM_ACCOUNTS);
done(); done();
}); });
ledgerProvider.sendAsync(payload, callback); ledgerProvider.sendAsync(payload, callback);
@ -123,7 +125,7 @@ describe('LedgerSubprovider', () => {
}; };
const callback = reportCallbackErrors(done)((err: Error, response: JSONRPCResponsePayload) => { const callback = reportCallbackErrors(done)((err: Error, response: JSONRPCResponsePayload) => {
expect(err).to.be.a('null'); expect(err).to.be.a('null');
expect(response.result.length).to.be.equal(132); expect(response.result.length).to.be.equal(EXPECTED_SIGNATURE_LENGTH);
expect(response.result.substr(0, 2)).to.be.equal('0x'); expect(response.result.substr(0, 2)).to.be.equal('0x');
done(); done();
}); });
@ -143,7 +145,7 @@ describe('LedgerSubprovider', () => {
}; };
const callback = reportCallbackErrors(done)((err: Error, response: JSONRPCResponsePayload) => { const callback = reportCallbackErrors(done)((err: Error, response: JSONRPCResponsePayload) => {
expect(err).to.be.a('null'); expect(err).to.be.a('null');
expect(response.result.length).to.be.equal(132); expect(response.result.length).to.be.equal(EXPECTED_SIGNATURE_LENGTH);
expect(response.result.substr(0, 2)).to.be.equal('0x'); expect(response.result.substr(0, 2)).to.be.equal('0x');
done(); done();
}); });
@ -197,7 +199,8 @@ describe('LedgerSubprovider', () => {
const callback = reportCallbackErrors(done)((err: Error, response: JSONRPCResponsePayload) => { const callback = reportCallbackErrors(done)((err: Error, response: JSONRPCResponsePayload) => {
expect(err).to.be.a('null'); expect(err).to.be.a('null');
const result = response.result; const result = response.result;
expect(result.length).to.be.equal(66); const signedTxLength = 66;
expect(result.length).to.be.equal(signedTxLength);
expect(result.substr(0, 2)).to.be.equal('0x'); expect(result.substr(0, 2)).to.be.equal('0x');
done(); done();
}); });

View File

@ -21,6 +21,7 @@ import { reportCallbackErrors } from '../utils/report_callback_errors';
chaiSetup.configure(); chaiSetup.configure();
const expect = chai.expect; const expect = chai.expect;
const FAKE_ADDRESS = '0xb088a3bc93f71b4de97b9de773e9647645983688'; const FAKE_ADDRESS = '0xb088a3bc93f71b4de97b9de773e9647645983688';
const DEFAULT_NUM_ACCOUNTS = 10;
describe('LedgerSubprovider', () => { describe('LedgerSubprovider', () => {
const networkId: number = 42; const networkId: number = 42;
@ -73,7 +74,7 @@ describe('LedgerSubprovider', () => {
it('returns default number of accounts', async () => { it('returns default number of accounts', async () => {
const accounts = await ledgerSubprovider.getAccountsAsync(); const accounts = await ledgerSubprovider.getAccountsAsync();
expect(accounts[0]).to.be.equal(FAKE_ADDRESS); expect(accounts[0]).to.be.equal(FAKE_ADDRESS);
expect(accounts.length).to.be.equal(10); expect(accounts.length).to.be.equal(DEFAULT_NUM_ACCOUNTS);
}); });
it('returns requested number of accounts', async () => { it('returns requested number of accounts', async () => {
const numberOfAccounts = 20; const numberOfAccounts = 20;
@ -119,7 +120,7 @@ describe('LedgerSubprovider', () => {
}; };
const callback = reportCallbackErrors(done)((err: Error, response: JSONRPCResponsePayload) => { const callback = reportCallbackErrors(done)((err: Error, response: JSONRPCResponsePayload) => {
expect(err).to.be.a('null'); expect(err).to.be.a('null');
expect(response.result.length).to.be.equal(10); expect(response.result.length).to.be.equal(DEFAULT_NUM_ACCOUNTS);
expect(response.result[0]).to.be.equal(FAKE_ADDRESS); expect(response.result[0]).to.be.equal(FAKE_ADDRESS);
done(); done();
}); });
@ -176,7 +177,8 @@ describe('LedgerSubprovider', () => {
}; };
const callback = reportCallbackErrors(done)((err: Error, response: JSONRPCResponsePayload) => { const callback = reportCallbackErrors(done)((err: Error, response: JSONRPCResponsePayload) => {
expect(err).to.be.a('null'); expect(err).to.be.a('null');
expect(response.result.raw.length).to.be.equal(192); const rawTxLength = 192;
expect(response.result.raw.length).to.be.equal(rawTxLength);
expect(response.result.raw.substr(0, 2)).to.be.equal('0x'); expect(response.result.raw.substr(0, 2)).to.be.equal('0x');
done(); done();
}); });

View File

@ -18,6 +18,7 @@ import { reportCallbackErrors } from '../utils/report_callback_errors';
chaiSetup.configure(); chaiSetup.configure();
const expect = chai.expect; const expect = chai.expect;
const DEFAULT_NUM_ACCOUNTS = 10;
describe('MnemonicWalletSubprovider', () => { describe('MnemonicWalletSubprovider', () => {
let subprovider: MnemonicWalletSubprovider; let subprovider: MnemonicWalletSubprovider;
@ -33,7 +34,7 @@ describe('MnemonicWalletSubprovider', () => {
const accounts = await subprovider.getAccountsAsync(); const accounts = await subprovider.getAccountsAsync();
expect(accounts[0]).to.be.equal(fixtureData.TEST_RPC_ACCOUNT_0); expect(accounts[0]).to.be.equal(fixtureData.TEST_RPC_ACCOUNT_0);
expect(accounts[1]).to.be.equal(fixtureData.TEST_RPC_ACCOUNT_1); expect(accounts[1]).to.be.equal(fixtureData.TEST_RPC_ACCOUNT_1);
expect(accounts.length).to.be.equal(10); expect(accounts.length).to.be.equal(DEFAULT_NUM_ACCOUNTS);
}); });
it('signs a personal message', async () => { it('signs a personal message', async () => {
const data = ethUtils.bufferToHex(ethUtils.toBuffer(fixtureData.PERSONAL_MESSAGE_STRING)); const data = ethUtils.bufferToHex(ethUtils.toBuffer(fixtureData.PERSONAL_MESSAGE_STRING));
@ -90,7 +91,7 @@ describe('MnemonicWalletSubprovider', () => {
const callback = reportCallbackErrors(done)((err: Error, response: JSONRPCResponsePayload) => { const callback = reportCallbackErrors(done)((err: Error, response: JSONRPCResponsePayload) => {
expect(err).to.be.a('null'); expect(err).to.be.a('null');
expect(response.result[0]).to.be.equal(fixtureData.TEST_RPC_ACCOUNT_0); expect(response.result[0]).to.be.equal(fixtureData.TEST_RPC_ACCOUNT_0);
expect(response.result.length).to.be.equal(10); expect(response.result.length).to.be.equal(DEFAULT_NUM_ACCOUNTS);
done(); done();
}); });
provider.sendAsync(payload, callback); provider.sendAsync(payload, callback);

View File

@ -14,6 +14,7 @@ import { reportCallbackErrors } from '../utils/report_callback_errors';
const expect = chai.expect; const expect = chai.expect;
chaiSetup.configure(); chaiSetup.configure();
const DEFAULT_NUM_ACCOUNTS = 10;
describe('RedundantSubprovider', () => { describe('RedundantSubprovider', () => {
let provider: Web3ProviderEngine; let provider: Web3ProviderEngine;
@ -32,7 +33,7 @@ describe('RedundantSubprovider', () => {
}; };
const callback = reportCallbackErrors(done)((err: Error, response: JSONRPCResponsePayload) => { const callback = reportCallbackErrors(done)((err: Error, response: JSONRPCResponsePayload) => {
expect(err).to.be.a('null'); expect(err).to.be.a('null');
expect(response.result.length).to.be.equal(10); expect(response.result.length).to.be.equal(DEFAULT_NUM_ACCOUNTS);
done(); done();
}); });
provider.sendAsync(payload, callback); provider.sendAsync(payload, callback);
@ -55,7 +56,7 @@ describe('RedundantSubprovider', () => {
}; };
const callback = reportCallbackErrors(done)((err: Error, response: JSONRPCResponsePayload) => { const callback = reportCallbackErrors(done)((err: Error, response: JSONRPCResponsePayload) => {
expect(err).to.be.a('null'); expect(err).to.be.a('null');
expect(response.result.length).to.be.equal(10); expect(response.result.length).to.be.equal(DEFAULT_NUM_ACCOUNTS);
done(); done();
}); });
provider.sendAsync(payload, callback); provider.sendAsync(payload, callback);

View File

@ -0,0 +1,5 @@
export const constants = {
SUCCESS_STATUS: 200,
SERVICE_UNAVAILABLE_STATUS: 503,
BAD_REQUEST_STATUS: 400,
};

View File

@ -15,6 +15,7 @@ import ProviderEngine = require('web3-provider-engine');
import RpcSubprovider = require('web3-provider-engine/subproviders/rpc'); import RpcSubprovider = require('web3-provider-engine/subproviders/rpc');
import { configs } from './configs'; import { configs } from './configs';
import { constants } from './constants';
import { DispatchQueue } from './dispatch_queue'; import { DispatchQueue } from './dispatch_queue';
import { dispenseAssetTasks } from './dispense_asset_tasks'; import { dispenseAssetTasks } from './dispense_asset_tasks';
import { rpcUrls } from './rpc_urls'; import { rpcUrls } from './rpc_urls';
@ -80,7 +81,7 @@ export class Handler {
}; };
}); });
const payload = JSON.stringify(queueInfo); const payload = JSON.stringify(queueInfo);
res.status(200).send(payload); res.status(constants.SUCCESS_STATUS).send(payload);
} }
public dispenseEther(req: express.Request, res: express.Response): void { public dispenseEther(req: express.Request, res: express.Response): void {
this._dispenseAsset(req, res, RequestedAssetType.ETH); this._dispenseAsset(req, res, RequestedAssetType.ETH);
@ -120,11 +121,11 @@ export class Handler {
} }
const didAddToQueue = networkConfig.dispatchQueue.add(dispenserTask); const didAddToQueue = networkConfig.dispatchQueue.add(dispenserTask);
if (!didAddToQueue) { if (!didAddToQueue) {
res.status(503).send('QUEUE_IS_FULL'); res.status(constants.SERVICE_UNAVAILABLE_STATUS).send('QUEUE_IS_FULL');
return; return;
} }
logUtils.log(`Added ${recipient} to queue: ${requestedAssetType} networkId: ${networkId}`); logUtils.log(`Added ${recipient} to queue: ${requestedAssetType} networkId: ${networkId}`);
res.status(200).end(); res.status(constants.SUCCESS_STATUS).end();
} }
private async _dispenseOrderAsync( private async _dispenseOrderAsync(
req: express.Request, req: express.Request,
@ -133,7 +134,7 @@ export class Handler {
): Promise<void> { ): Promise<void> {
const networkConfig = _.get(this._networkConfigByNetworkId, req.params.networkId); const networkConfig = _.get(this._networkConfigByNetworkId, req.params.networkId);
if (_.isUndefined(networkConfig)) { if (_.isUndefined(networkConfig)) {
res.status(400).send('UNSUPPORTED_NETWORK_ID'); res.status(constants.BAD_REQUEST_STATUS).send('UNSUPPORTED_NETWORK_ID');
return; return;
} }
const zeroEx = networkConfig.zeroEx; const zeroEx = networkConfig.zeroEx;
@ -173,6 +174,6 @@ export class Handler {
const signedOrderHash = ZeroEx.getOrderHashHex(signedOrder); const signedOrderHash = ZeroEx.getOrderHashHex(signedOrder);
const payload = JSON.stringify(signedOrder); const payload = JSON.stringify(signedOrder);
logUtils.log(`Dispensed signed order: ${payload}`); logUtils.log(`Dispensed signed order: ${payload}`);
res.status(200).send(payload); res.status(constants.SUCCESS_STATUS).send(payload);
} }
} }

View File

@ -3,6 +3,7 @@ import { NextFunction, Request, Response } from 'express';
import * as _ from 'lodash'; import * as _ from 'lodash';
import { configs } from './configs'; import { configs } from './configs';
import { constants } from './constants';
import { rpcUrls } from './rpc_urls'; import { rpcUrls } from './rpc_urls';
const DEFAULT_NETWORK_ID = 42; // kovan const DEFAULT_NETWORK_ID = 42; // kovan
@ -11,7 +12,7 @@ export const parameterTransformer = {
transform(req: Request, res: Response, next: NextFunction): void { transform(req: Request, res: Response, next: NextFunction): void {
const recipientAddress = req.params.recipient; const recipientAddress = req.params.recipient;
if (_.isUndefined(recipientAddress) || !addressUtils.isAddress(recipientAddress)) { if (_.isUndefined(recipientAddress) || !addressUtils.isAddress(recipientAddress)) {
res.status(400).send('INVALID_RECIPIENT_ADDRESS'); res.status(constants.BAD_REQUEST_STATUS).send('INVALID_RECIPIENT_ADDRESS');
return; return;
} }
const lowerCaseRecipientAddress = recipientAddress.toLowerCase(); const lowerCaseRecipientAddress = recipientAddress.toLowerCase();
@ -19,7 +20,7 @@ export const parameterTransformer = {
const networkId = _.get(req.query, 'networkId', DEFAULT_NETWORK_ID); const networkId = _.get(req.query, 'networkId', DEFAULT_NETWORK_ID);
const rpcUrlIfExists = _.get(rpcUrls, networkId); const rpcUrlIfExists = _.get(rpcUrls, networkId);
if (_.isUndefined(rpcUrlIfExists)) { if (_.isUndefined(rpcUrlIfExists)) {
res.status(400).send('UNSUPPORTED_NETWORK_ID'); res.status(constants.BAD_REQUEST_STATUS).send('UNSUPPORTED_NETWORK_ID');
return; return;
} }
req.params.networkId = networkId; req.params.networkId = networkId;

View File

@ -1,6 +1,7 @@
import * as bodyParser from 'body-parser'; import * as bodyParser from 'body-parser';
import * as express from 'express'; import * as express from 'express';
import { constants } from './constants';
import { errorReporter } from './error_reporter'; import { errorReporter } from './error_reporter';
import { Handler } from './handler'; import { Handler } from './handler';
import { parameterTransformer } from './parameter_transformer'; import { parameterTransformer } from './parameter_transformer';
@ -18,7 +19,7 @@ app.use((req, res, next) => {
const handler = new Handler(); const handler = new Handler();
app.get('/ping', (req: express.Request, res: express.Response) => { app.get('/ping', (req: express.Request, res: express.Response) => {
res.status(200).send('pong'); res.status(constants.SUCCESS_STATUS).send('pong');
}); });
app.get('/info', handler.getQueueInfo.bind(handler)); app.get('/info', handler.getQueueInfo.bind(handler));
app.get('/ether/:recipient', parameterTransformer.transform, handler.dispenseEther.bind(handler)); app.get('/ether/:recipient', parameterTransformer.transform, handler.dispenseEther.bind(handler));
@ -28,5 +29,6 @@ app.get('/order/zrx/:recipient', parameterTransformer.transform, handler.dispens
// Log to rollbar any errors unhandled by handlers // Log to rollbar any errors unhandled by handlers
app.use(errorReporter.errorHandler()); app.use(errorReporter.errorHandler());
const port = process.env.PORT || 3000; const DEFAULT_PORT = 3000;
const port = process.env.PORT || DEFAULT_PORT;
app.listen(port); app.listen(port);

View File

@ -45,7 +45,8 @@
"lodash": "^4.17.4", "lodash": "^4.17.4",
"tslint": "5.8.0", "tslint": "5.8.0",
"tslint-eslint-rules": "^4.1.1", "tslint-eslint-rules": "^4.1.1",
"tslint-react": "^3.2.0" "tslint-react": "^3.2.0",
"tsutils": "^2.12.1"
}, },
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"

View File

@ -0,0 +1,68 @@
import * as _ from 'lodash';
import * as Lint from 'tslint';
import * as ts from 'typescript';
const VALID_BOOLEAN_PREFIXES = ['is', 'does', 'should', 'was', 'has', 'can', 'did', 'would'];
export class Rule extends Lint.Rules.TypedRule {
public static FAILURE_STRING = `Boolean variable names should begin with: ${VALID_BOOLEAN_PREFIXES.join(', ')}`;
public applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): Lint.RuleFailure[] {
return this.applyWithFunction(sourceFile, walk, undefined, program.getTypeChecker());
}
}
function walk(ctx: Lint.WalkContext<void>, tc: ts.TypeChecker): void {
traverse(ctx.sourceFile);
function traverse(node: ts.Node): void {
checkNodeForViolations(ctx, node, tc);
return ts.forEachChild(node, traverse);
}
}
function checkNodeForViolations(ctx: Lint.WalkContext<void>, node: ts.Node, tc: ts.TypeChecker): void {
switch (node.kind) {
// Handle: const { timestamp } = ...
case ts.SyntaxKind.BindingElement: {
const bindingElementNode = node as ts.BindingElement;
if (bindingElementNode.name.kind === ts.SyntaxKind.Identifier) {
handleBooleanNaming(bindingElementNode, tc, ctx);
}
break;
}
// Handle regular assignments: const block = ...
case ts.SyntaxKind.VariableDeclaration:
const variableDeclarationNode = node as ts.VariableDeclaration;
if (variableDeclarationNode.name.kind === ts.SyntaxKind.Identifier) {
handleBooleanNaming(node as ts.VariableDeclaration, tc, ctx);
}
break;
default:
_.noop();
}
}
function handleBooleanNaming(
node: ts.VariableDeclaration | ts.BindingElement,
tc: ts.TypeChecker,
ctx: Lint.WalkContext<void>,
): void {
const nodeName = node.name;
const variableName = nodeName.getText();
const lowercasedName = _.toLower(variableName);
const typeNode = tc.getTypeAtLocation(node);
const typeName = (typeNode as any).intrinsicName;
if (typeName === 'boolean') {
const hasProperName = !_.isUndefined(
_.find(VALID_BOOLEAN_PREFIXES, prefix => {
return _.startsWith(lowercasedName, prefix);
}),
);
if (!hasProperName) {
ctx.addFailureAtNode(node, Rule.FAILURE_STRING);
}
}
}

View File

@ -0,0 +1,76 @@
import * as Lint from 'tslint';
import { isPrefixUnaryExpression } from 'tsutils';
import * as ts from 'typescript';
/**
* A modified version of the no-magic-numbers rule that allows for magic numbers
* when instantiating a BigNumber instance.
* E.g We want to be able to write:
* const amount = new BigNumber(5);
* Original source: https://github.com/palantir/tslint/blob/42b058a6baa688f8be8558b277eb056c3ff79818/src/rules/noMagicNumbersRule.ts
*/
export class Rule extends Lint.Rules.AbstractRule {
public static ALLOWED_NODES = new Set<ts.SyntaxKind>([
ts.SyntaxKind.ExportAssignment,
ts.SyntaxKind.FirstAssignment,
ts.SyntaxKind.LastAssignment,
ts.SyntaxKind.PropertyAssignment,
ts.SyntaxKind.ShorthandPropertyAssignment,
ts.SyntaxKind.VariableDeclaration,
ts.SyntaxKind.VariableDeclarationList,
ts.SyntaxKind.EnumMember,
ts.SyntaxKind.PropertyDeclaration,
ts.SyntaxKind.Parameter,
]);
public static DEFAULT_ALLOWED = [-1, 0, 1];
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
const allowedNumbers = this.ruleArguments.length > 0 ? this.ruleArguments : Rule.DEFAULT_ALLOWED;
return this.applyWithWalker(
new CustomNoMagicNumbersWalker(sourceFile, this.ruleName, new Set(allowedNumbers.map(String))),
);
}
}
// tslint:disable-next-line:max-classes-per-file
class CustomNoMagicNumbersWalker extends Lint.AbstractWalker<Set<string>> {
public static FAILURE_STRING = "'magic numbers' are not allowed";
private static _isNegativeNumberLiteral(
node: ts.Node,
): node is ts.PrefixUnaryExpression & { operand: ts.NumericLiteral } {
return (
isPrefixUnaryExpression(node) &&
node.operator === ts.SyntaxKind.MinusToken &&
node.operand.kind === ts.SyntaxKind.NumericLiteral
);
}
public walk(sourceFile: ts.SourceFile): void {
const cb = (node: ts.Node): void => {
if (node.kind === ts.SyntaxKind.NumericLiteral) {
return this.checkNumericLiteral(node, (node as ts.NumericLiteral).text);
}
if (CustomNoMagicNumbersWalker._isNegativeNumberLiteral(node)) {
return this.checkNumericLiteral(node, `-${(node.operand as ts.NumericLiteral).text}`);
}
return ts.forEachChild(node, cb);
};
return ts.forEachChild(sourceFile, cb);
}
// tslint:disable:no-non-null-assertion
// tslint:disable-next-line:underscore-private-and-protected
private checkNumericLiteral(node: ts.Node, num: string): void {
if (!Rule.ALLOWED_NODES.has(node.parent!.kind) && !this.options.has(num)) {
if (node.parent!.kind === ts.SyntaxKind.NewExpression) {
const className = (node.parent! as any).expression.escapedText;
const BIG_NUMBER_NEW_EXPRESSION = 'BigNumber';
if (className === BIG_NUMBER_NEW_EXPRESSION) {
return; // noop
}
}
this.addFailureAtNode(node, CustomNoMagicNumbersWalker.FAILURE_STRING);
}
}
// tslint:enable:no-non-null-assertion
}

View File

@ -5,7 +5,10 @@
"arrow-parens": [true, "ban-single-arg-parens"], "arrow-parens": [true, "ban-single-arg-parens"],
"arrow-return-shorthand": true, "arrow-return-shorthand": true,
"async-suffix": true, "async-suffix": true,
"boolean-naming": true,
"no-switch-case-fall-through": true,
"await-promise": true, "await-promise": true,
"custom-no-magic-numbers": [true, 0, 1, 2, 3, -1],
"binary-expression-operand-order": true, "binary-expression-operand-order": true,
"callable-types": true, "callable-types": true,
"class-name": true, "class-name": true,

View File

@ -23,7 +23,8 @@ export class AbiDecoder {
formatted = formatted.slice(2); formatted = formatted.slice(2);
} }
formatted = _.padStart(formatted, 40, '0'); const addressLength = 40;
formatted = _.padStart(formatted, addressLength, '0');
return `0x${formatted}`; return `0x${formatted}`;
} }
constructor(abiArrays: AbiDefinition[][]) { constructor(abiArrays: AbiDefinition[][]) {
@ -45,16 +46,17 @@ export class AbiDecoder {
const dataTypes = _.map(nonIndexedInputs, input => input.type); const dataTypes = _.map(nonIndexedInputs, input => input.type);
const decodedData = ethersInterface.events[event.name].parse(log.data); const decodedData = ethersInterface.events[event.name].parse(log.data);
let failedToDecode = false; let didFailToDecode = false;
_.forEach(event.inputs, (param: EventParameter, i: number) => { _.forEach(event.inputs, (param: EventParameter, i: number) => {
// Indexed parameters are stored in topics. Non-indexed ones in decodedData // Indexed parameters are stored in topics. Non-indexed ones in decodedData
let value: BigNumber | string | number = param.indexed ? log.topics[topicsIndex++] : decodedData[i]; let value: BigNumber | string | number = param.indexed ? log.topics[topicsIndex++] : decodedData[i];
if (_.isUndefined(value)) { if (_.isUndefined(value)) {
failedToDecode = true; didFailToDecode = true;
return; return;
} }
if (param.type === SolidityTypes.Address) { if (param.type === SolidityTypes.Address) {
value = AbiDecoder._padZeros(new BigNumber(value).toString(16)); const baseHex = 16;
value = AbiDecoder._padZeros(new BigNumber(value).toString(baseHex));
} else if (param.type === SolidityTypes.Uint256 || param.type === SolidityTypes.Uint) { } else if (param.type === SolidityTypes.Uint256 || param.type === SolidityTypes.Uint) {
value = new BigNumber(value); value = new BigNumber(value);
} else if (param.type === SolidityTypes.Uint8) { } else if (param.type === SolidityTypes.Uint8) {
@ -63,7 +65,7 @@ export class AbiDecoder {
decodedParams[param.name] = value; decodedParams[param.name] = value;
}); });
if (failedToDecode) { if (didFailToDecode) {
return log; return log;
} else { } else {
return { return {

View File

@ -9,11 +9,16 @@ export const addressUtils = {
const unprefixedAddress = address.replace('0x', ''); const unprefixedAddress = address.replace('0x', '');
const addressHash = jsSHA3.keccak256(unprefixedAddress.toLowerCase()); const addressHash = jsSHA3.keccak256(unprefixedAddress.toLowerCase());
for (let i = 0; i < 40; i++) { const addressLength = 40;
for (let i = 0; i < addressLength; i++) {
// The nth letter should be uppercase if the nth digit of casemap is 1 // The nth letter should be uppercase if the nth digit of casemap is 1
const hexBase = 16;
const lowercaseRange = 7;
if ( if (
(parseInt(addressHash[i], 16) > 7 && unprefixedAddress[i].toUpperCase() !== unprefixedAddress[i]) || (parseInt(addressHash[i], hexBase) > lowercaseRange &&
(parseInt(addressHash[i], 16) <= 7 && unprefixedAddress[i].toLowerCase() !== unprefixedAddress[i]) unprefixedAddress[i].toUpperCase() !== unprefixedAddress[i]) ||
(parseInt(addressHash[i], hexBase) <= lowercaseRange &&
unprefixedAddress[i].toLowerCase() !== unprefixedAddress[i])
) { ) {
return false; return false;
} }

View File

@ -6,18 +6,18 @@ export const intervalUtils = {
intervalMs: number, intervalMs: number,
onError: (err: Error) => void, onError: (err: Error) => void,
): NodeJS.Timer { ): NodeJS.Timer {
let locked = false; let isLocked = false;
const intervalId = setInterval(async () => { const intervalId = setInterval(async () => {
if (locked) { if (isLocked) {
return; return;
} else { } else {
locked = true; isLocked = true;
try { try {
await fn(); await fn();
} catch (err) { } catch (err) {
onError(err); onError(err);
} }
locked = false; isLocked = false;
} }
}, intervalMs); }, intervalMs);
return intervalId; return intervalId;

View File

@ -19,6 +19,8 @@ import * as Web3 from 'web3';
import { Web3WrapperErrors } from './types'; import { Web3WrapperErrors } from './types';
const BASE_TEN = 10;
/** /**
* A wrapper around the Web3.js 0.x library that provides a consistent, clean promise-based interface. * A wrapper around the Web3.js 0.x library that provides a consistent, clean promise-based interface.
*/ */
@ -48,7 +50,7 @@ export class Web3Wrapper {
* @return The amount in units. * @return The amount in units.
*/ */
public static toUnitAmount(amount: BigNumber, decimals: number): BigNumber { public static toUnitAmount(amount: BigNumber, decimals: number): BigNumber {
const aUnit = new BigNumber(10).pow(decimals); const aUnit = new BigNumber(BASE_TEN).pow(decimals);
const unit = amount.div(aUnit); const unit = amount.div(aUnit);
return unit; return unit;
} }
@ -61,7 +63,7 @@ export class Web3Wrapper {
* @return The amount in baseUnits. * @return The amount in baseUnits.
*/ */
public static toBaseUnitAmount(amount: BigNumber, decimals: number): BigNumber { public static toBaseUnitAmount(amount: BigNumber, decimals: number): BigNumber {
const unit = new BigNumber(10).pow(decimals); const unit = new BigNumber(BASE_TEN).pow(decimals);
const baseUnitAmount = amount.times(unit); const baseUnitAmount = amount.times(unit);
const hasDecimals = baseUnitAmount.decimalPlaces() !== 0; const hasDecimals = baseUnitAmount.decimalPlaces() !== 0;
if (hasDecimals) { if (hasDecimals) {
@ -180,8 +182,8 @@ export class Web3Wrapper {
public async doesContractExistAtAddressAsync(address: string): Promise<boolean> { public async doesContractExistAtAddressAsync(address: string): Promise<boolean> {
const code = await promisify<string>(this._web3.eth.getCode)(address); const code = await promisify<string>(this._web3.eth.getCode)(address);
// Regex matches 0x0, 0x00, 0x in order to accommodate poorly implemented clients // Regex matches 0x0, 0x00, 0x in order to accommodate poorly implemented clients
const codeIsEmpty = /^0x0{0,40}$/i.test(code); const isCodeEmpty = /^0x0{0,40}$/i.test(code);
return !codeIsEmpty; return !isCodeEmpty;
} }
/** /**
* Sign a message with a specific address's private key (`eth_sign`) * Sign a message with a specific address's private key (`eth_sign`)
@ -336,16 +338,16 @@ export class Web3Wrapper {
pollingIntervalMs: number = 1000, pollingIntervalMs: number = 1000,
timeoutMs?: number, timeoutMs?: number,
): Promise<TransactionReceiptWithDecodedLogs> { ): Promise<TransactionReceiptWithDecodedLogs> {
let timeoutExceeded = false; let wasTimeoutExceeded = false;
if (timeoutMs) { if (timeoutMs) {
setTimeout(() => (timeoutExceeded = true), timeoutMs); setTimeout(() => (wasTimeoutExceeded = true), timeoutMs);
} }
const txReceiptPromise = new Promise( const txReceiptPromise = new Promise(
(resolve: (receipt: TransactionReceiptWithDecodedLogs) => void, reject) => { (resolve: (receipt: TransactionReceiptWithDecodedLogs) => void, reject) => {
const intervalId = intervalUtils.setAsyncExcludingInterval( const intervalId = intervalUtils.setAsyncExcludingInterval(
async () => { async () => {
if (timeoutExceeded) { if (wasTimeoutExceeded) {
intervalUtils.clearAsyncExcludingInterval(intervalId); intervalUtils.clearAsyncExcludingInterval(intervalId);
return reject(Web3WrapperErrors.TransactionMiningTimeout); return reject(Web3WrapperErrors.TransactionMiningTimeout);
} }

View File

@ -157,14 +157,14 @@ export class NewTokenForm extends React.Component<NewTokenFormProps, NewTokenFor
const maxLength = 30; const maxLength = 30;
const tokens = _.values(this.props.tokenByAddress); const tokens = _.values(this.props.tokenByAddress);
const tokenWithNameIfExists = _.find(tokens, { name }); const tokenWithNameIfExists = _.find(tokens, { name });
const tokenWithNameExists = !_.isUndefined(tokenWithNameIfExists); const doesTokenWithNameExists = !_.isUndefined(tokenWithNameIfExists);
if (name === '') { if (name === '') {
nameErrText = 'Name is required'; nameErrText = 'Name is required';
} else if (!this._isValidName(name)) { } else if (!this._isValidName(name)) {
nameErrText = 'Name should only contain letters, digits and spaces'; nameErrText = 'Name should only contain letters, digits and spaces';
} else if (name.length > maxLength) { } else if (name.length > maxLength) {
nameErrText = `Max length is ${maxLength}`; nameErrText = `Max length is ${maxLength}`;
} else if (tokenWithNameExists) { } else if (doesTokenWithNameExists) {
nameErrText = 'Token with this name already exists'; nameErrText = 'Token with this name already exists';
} }
@ -177,14 +177,14 @@ export class NewTokenForm extends React.Component<NewTokenFormProps, NewTokenFor
let symbolErrText = ''; let symbolErrText = '';
const maxLength = 5; const maxLength = 5;
const tokens = _.values(this.props.tokenByAddress); const tokens = _.values(this.props.tokenByAddress);
const tokenWithSymbolExists = !_.isUndefined(_.find(tokens, { symbol })); const doesTokenWithSymbolExists = !_.isUndefined(_.find(tokens, { symbol }));
if (symbol === '') { if (symbol === '') {
symbolErrText = 'Symbol is required'; symbolErrText = 'Symbol is required';
} else if (!this._isAlphanumeric(symbol)) { } else if (!this._isAlphanumeric(symbol)) {
symbolErrText = 'Can only include alphanumeric characters'; symbolErrText = 'Can only include alphanumeric characters';
} else if (symbol.length > maxLength) { } else if (symbol.length > maxLength) {
symbolErrText = `Max length is ${maxLength}`; symbolErrText = `Max length is ${maxLength}`;
} else if (tokenWithSymbolExists) { } else if (doesTokenWithSymbolExists) {
symbolErrText = 'Token with symbol already exists'; symbolErrText = 'Token with symbol already exists';
} }

View File

@ -56,8 +56,8 @@ export class RelayerIndex extends React.Component<RelayerIndexProps, RelayerInde
this._isUnmounted = true; this._isUnmounted = true;
} }
public render(): React.ReactNode { public render(): React.ReactNode {
const readyToRender = _.isUndefined(this.state.error) && !_.isUndefined(this.state.relayerInfos); const isReadyToRender = _.isUndefined(this.state.error) && !_.isUndefined(this.state.relayerInfos);
if (!readyToRender) { if (!isReadyToRender) {
return ( return (
<div className="col col-12" style={{ ...styles.root, height: '100%' }}> <div className="col col-12" style={{ ...styles.root, height: '100%' }}>
<div <div

View File

@ -4,6 +4,7 @@
"no-implicit-dependencies": false, "no-implicit-dependencies": false,
"no-object-literal-type-assertion": false, "no-object-literal-type-assertion": false,
"completed-docs": false, "completed-docs": false,
"prefer-function-over-method": false "prefer-function-over-method": false,
"custom-no-magic-numbers": false
} }
} }