diff options
-rw-r--r-- | packages/abi-gen/CHANGELOG.md | 1 | ||||
-rw-r--r-- | packages/abi-gen/package.json | 2 | ||||
-rw-r--r-- | packages/abi-gen/src/index.ts | 7 | ||||
-rw-r--r-- | packages/abi-gen/src/utils.ts | 9 | ||||
-rw-r--r-- | packages/connect/CHANGELOG.md | 4 | ||||
-rw-r--r-- | packages/connect/src/http_client.ts | 42 | ||||
-rw-r--r-- | packages/connect/src/utils/orderbook_channel_message_parser.ts (renamed from packages/connect/src/utils/orderbook_channel_message_parsers.ts) | 18 | ||||
-rw-r--r-- | packages/connect/src/utils/relayer_response_json_parsers.ts | 37 | ||||
-rw-r--r-- | packages/connect/src/utils/type_converters.ts | 27 | ||||
-rw-r--r-- | packages/connect/src/ws_orderbook_channel.ts | 4 | ||||
-rw-r--r-- | packages/connect/test/http_client_test.ts | 12 | ||||
-rw-r--r-- | packages/connect/test/orderbook_channel_message_parsers_test.ts | 21 | ||||
-rw-r--r-- | yarn.lock | 6 |
13 files changed, 124 insertions, 66 deletions
diff --git a/packages/abi-gen/CHANGELOG.md b/packages/abi-gen/CHANGELOG.md index 700cfb549..983f7a3d1 100644 --- a/packages/abi-gen/CHANGELOG.md +++ b/packages/abi-gen/CHANGELOG.md @@ -4,3 +4,4 @@ * Fixed array typings with union types (#295) * Add event ABIs to context data passed to templates (#302) +* Add constructor ABIs to context data passed to templates (#304)
\ No newline at end of file diff --git a/packages/abi-gen/package.json b/packages/abi-gen/package.json index e2307bccd..b42a7fbce 100644 --- a/packages/abi-gen/package.json +++ b/packages/abi-gen/package.json @@ -43,6 +43,6 @@ "shx": "^0.2.2", "tslint": "5.8.0", "typescript": "~2.6.1", - "web3-typescript-typings": "^0.7.2" + "web3-typescript-typings": "^0.9.0" } } diff --git a/packages/abi-gen/src/index.ts b/packages/abi-gen/src/index.ts index ed71087b2..527af32b1 100644 --- a/packages/abi-gen/src/index.ts +++ b/packages/abi-gen/src/index.ts @@ -14,6 +14,7 @@ import * as Web3 from 'web3'; import { ContextData, ParamKind } from './types'; import { utils } from './utils'; +const ABI_TYPE_CONSTRUCTOR = 'constructor'; const ABI_TYPE_METHOD = 'function'; const ABI_TYPE_EVENT = 'event'; const MAIN_TEMPLATE_NAME = 'contract.mustache'; @@ -75,6 +76,11 @@ for (const abiFileName of abiFileNames) { process.exit(1); } + let ctor = ABI.find((abi: Web3.AbiDefinition) => abi.type === ABI_TYPE_CONSTRUCTOR) as Web3.ConstructorAbi; + if (_.isUndefined(ctor)) { + ctor = utils.getEmptyConstructor(); // The constructor exists, but it's implicit in JSON's ABI definition + } + const methodAbis = ABI.filter((abi: Web3.AbiDefinition) => abi.type === ABI_TYPE_METHOD) as Web3.MethodAbi[]; const methodsData = _.map(methodAbis, methodAbi => { _.map(methodAbi.inputs, input => { @@ -95,6 +101,7 @@ for (const abiFileName of abiFileNames) { const contextData = { contractName: namedContent.name, + ctor, methods: methodsData, events: eventAbis, }; diff --git a/packages/abi-gen/src/utils.ts b/packages/abi-gen/src/utils.ts index edda016b5..f6291d98d 100644 --- a/packages/abi-gen/src/utils.ts +++ b/packages/abi-gen/src/utils.ts @@ -1,6 +1,7 @@ import * as fs from 'fs'; import * as _ from 'lodash'; import * as path from 'path'; +import * as Web3 from 'web3'; import { ParamKind } from './types'; @@ -61,4 +62,12 @@ export const utils = { throw new Error(`Failed to read ${filename}: ${err}`); } }, + getEmptyConstructor(): Web3.ConstructorAbi { + return { + type: 'constructor', + stateMutability: 'nonpayable', + payable: false, + inputs: [], + }; + }, }; diff --git a/packages/connect/CHANGELOG.md b/packages/connect/CHANGELOG.md index 87e9090d5..a0bc68a0b 100644 --- a/packages/connect/CHANGELOG.md +++ b/packages/connect/CHANGELOG.md @@ -1,5 +1,9 @@ # CHANGELOG +## vx.x.x + + * Prevent getFeesAsync method on HttpClient from mutating input (#296) + ## v0.3.0 - _December 8, 2017_ * Expose WebSocketOrderbookChannel and associated types to public interface (#251) diff --git a/packages/connect/src/http_client.ts b/packages/connect/src/http_client.ts index 06b5d8ace..5604a9607 100644 --- a/packages/connect/src/http_client.ts +++ b/packages/connect/src/http_client.ts @@ -18,7 +18,7 @@ import { TokenPairsItem, TokenPairsRequest, } from './types'; -import { typeConverters } from './utils/type_converters'; +import { relayerResponseJsonParsers } from './utils/relayer_response_json_parsers'; /** * This class includes all the functionality related to interacting with a set of HTTP endpoints @@ -48,16 +48,8 @@ export class HttpClient implements Client { const requestOpts = { params: request, }; - const tokenPairs = await this._requestAsync('/token_pairs', HttpRequestType.Get, requestOpts); - assert.doesConformToSchema('tokenPairs', tokenPairs, schemas.relayerApiTokenPairsResponseSchema); - _.each(tokenPairs, (tokenPair: object) => { - typeConverters.convertStringsFieldsToBigNumbers(tokenPair, [ - 'tokenA.minAmount', - 'tokenA.maxAmount', - 'tokenB.minAmount', - 'tokenB.maxAmount', - ]); - }); + const responseJson = await this._requestAsync('/token_pairs', HttpRequestType.Get, requestOpts); + const tokenPairs = relayerResponseJsonParsers.parseTokenPairsJson(responseJson); return tokenPairs; } /** @@ -72,9 +64,8 @@ export class HttpClient implements Client { const requestOpts = { params: request, }; - const orders = await this._requestAsync(`/orders`, HttpRequestType.Get, requestOpts); - assert.doesConformToSchema('orders', orders, schemas.signedOrdersSchema); - _.each(orders, (order: object) => typeConverters.convertOrderStringFieldsToBigNumber(order)); + const responseJson = await this._requestAsync(`/orders`, HttpRequestType.Get, requestOpts); + const orders = relayerResponseJsonParsers.parseOrdersJson(responseJson); return orders; } /** @@ -84,9 +75,8 @@ export class HttpClient implements Client { */ public async getOrderAsync(orderHash: string): Promise<SignedOrder> { assert.doesConformToSchema('orderHash', orderHash, schemas.orderHashSchema); - const order = await this._requestAsync(`/order/${orderHash}`, HttpRequestType.Get); - assert.doesConformToSchema('order', order, schemas.signedOrderSchema); - typeConverters.convertOrderStringFieldsToBigNumber(order); + const responseJson = await this._requestAsync(`/order/${orderHash}`, HttpRequestType.Get); + const order = relayerResponseJsonParsers.parseOrderJson(responseJson); return order; } /** @@ -99,10 +89,9 @@ export class HttpClient implements Client { const requestOpts = { params: request, }; - const orderBook = await this._requestAsync('/orderbook', HttpRequestType.Get, requestOpts); - assert.doesConformToSchema('orderBook', orderBook, schemas.relayerApiOrderBookResponseSchema); - typeConverters.convertOrderbookStringFieldsToBigNumber(orderBook); - return orderBook; + const responseJson = await this._requestAsync('/orderbook', HttpRequestType.Get, requestOpts); + const orderbook = relayerResponseJsonParsers.parseOrderbookResponseJson(responseJson); + return orderbook; } /** * Retrieve fee information from the API @@ -111,18 +100,11 @@ export class HttpClient implements Client { */ public async getFeesAsync(request: FeesRequest): Promise<FeesResponse> { assert.doesConformToSchema('request', request, schemas.relayerApiFeesPayloadSchema); - typeConverters.convertBigNumberFieldsToStrings(request, [ - 'makerTokenAmount', - 'takerTokenAmount', - 'expirationUnixTimestampSec', - 'salt', - ]); const requestOpts = { payload: request, }; - const fees = await this._requestAsync('/fees', HttpRequestType.Post, requestOpts); - assert.doesConformToSchema('fees', fees, schemas.relayerApiFeesResponseSchema); - typeConverters.convertStringsFieldsToBigNumbers(fees, ['makerFee', 'takerFee']); + const responseJson = await this._requestAsync('/fees', HttpRequestType.Post, requestOpts); + const fees = relayerResponseJsonParsers.parseFeesResponseJson(responseJson); return fees; } /** diff --git a/packages/connect/src/utils/orderbook_channel_message_parsers.ts b/packages/connect/src/utils/orderbook_channel_message_parser.ts index a4f22dc4b..9a9ca8901 100644 --- a/packages/connect/src/utils/orderbook_channel_message_parsers.ts +++ b/packages/connect/src/utils/orderbook_channel_message_parser.ts @@ -4,10 +4,10 @@ import * as _ from 'lodash'; import { OrderbookChannelMessage, OrderbookChannelMessageTypes } from '../types'; -import { typeConverters } from './type_converters'; +import { relayerResponseJsonParsers } from './relayer_response_json_parsers'; -export const orderbookChannelMessageParsers = { - parser(utf8Data: string): OrderbookChannelMessage { +export const orderbookChannelMessageParser = { + parse(utf8Data: string): OrderbookChannelMessage { const messageObj = JSON.parse(utf8Data); const type: string = _.get(messageObj, 'type'); assert.assert(!_.isUndefined(type), `Message is missing a type parameter: ${utf8Data}`); @@ -15,15 +15,15 @@ export const orderbookChannelMessageParsers = { switch (type) { case OrderbookChannelMessageTypes.Snapshot: { assert.doesConformToSchema('message', messageObj, schemas.relayerApiOrderbookChannelSnapshotSchema); - const orderbook = messageObj.payload; - typeConverters.convertOrderbookStringFieldsToBigNumber(orderbook); - return messageObj; + const orderbookJson = messageObj.payload; + const orderbook = relayerResponseJsonParsers.parseOrderbookResponseJson(orderbookJson); + return _.assign(messageObj, { payload: orderbook }); } case OrderbookChannelMessageTypes.Update: { assert.doesConformToSchema('message', messageObj, schemas.relayerApiOrderbookChannelUpdateSchema); - const order = messageObj.payload; - typeConverters.convertOrderStringFieldsToBigNumber(order); - return messageObj; + const orderJson = messageObj.payload; + const order = relayerResponseJsonParsers.parseOrderJson(orderJson); + return _.assign(messageObj, { payload: order }); } default: { return { diff --git a/packages/connect/src/utils/relayer_response_json_parsers.ts b/packages/connect/src/utils/relayer_response_json_parsers.ts new file mode 100644 index 000000000..668461bf4 --- /dev/null +++ b/packages/connect/src/utils/relayer_response_json_parsers.ts @@ -0,0 +1,37 @@ +import { assert } from '@0xproject/assert'; +import { schemas } from '@0xproject/json-schemas'; +import * as _ from 'lodash'; + +import { FeesResponse, OrderbookResponse, SignedOrder, TokenPairsItem } from '../types'; + +import { typeConverters } from './type_converters'; + +export const relayerResponseJsonParsers = { + parseTokenPairsJson(json: any): TokenPairsItem[] { + assert.doesConformToSchema('tokenPairs', json, schemas.relayerApiTokenPairsResponseSchema); + return json.map((tokenPair: any) => { + return typeConverters.convertStringsFieldsToBigNumbers(tokenPair, [ + 'tokenA.minAmount', + 'tokenA.maxAmount', + 'tokenB.minAmount', + 'tokenB.maxAmount', + ]); + }); + }, + parseOrdersJson(json: any): SignedOrder[] { + assert.doesConformToSchema('orders', json, schemas.signedOrdersSchema); + return json.map((order: object) => typeConverters.convertOrderStringFieldsToBigNumber(order)); + }, + parseOrderJson(json: any): SignedOrder { + assert.doesConformToSchema('order', json, schemas.signedOrderSchema); + return typeConverters.convertOrderStringFieldsToBigNumber(json); + }, + parseOrderbookResponseJson(json: any): OrderbookResponse { + assert.doesConformToSchema('orderBook', json, schemas.relayerApiOrderBookResponseSchema); + return typeConverters.convertOrderbookStringFieldsToBigNumber(json); + }, + parseFeesResponseJson(json: any): FeesResponse { + assert.doesConformToSchema('fees', json, schemas.relayerApiFeesResponseSchema); + return typeConverters.convertStringsFieldsToBigNumbers(json, ['makerFee', 'takerFee']); + }, +}; diff --git a/packages/connect/src/utils/type_converters.ts b/packages/connect/src/utils/type_converters.ts index 9d20154fd..c1808ce8a 100644 --- a/packages/connect/src/utils/type_converters.ts +++ b/packages/connect/src/utils/type_converters.ts @@ -1,15 +1,17 @@ import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; -// TODO: convert all of these to non-mutating, pure functions export const typeConverters = { - convertOrderbookStringFieldsToBigNumber(orderbook: object): void { - _.each(orderbook, (orders: object[]) => { - _.each(orders, (order: object) => this.convertOrderStringFieldsToBigNumber(order)); - }); + convertOrderbookStringFieldsToBigNumber(orderbook: any): any { + const bids = _.get(orderbook, 'bids', []); + const asks = _.get(orderbook, 'asks', []); + return { + bids: bids.map((order: any) => this.convertOrderStringFieldsToBigNumber(order)), + asks: asks.map((order: any) => this.convertOrderStringFieldsToBigNumber(order)), + }; }, - convertOrderStringFieldsToBigNumber(order: object): void { - this.convertStringsFieldsToBigNumbers(order, [ + convertOrderStringFieldsToBigNumber(order: any): any { + return this.convertStringsFieldsToBigNumbers(order, [ 'makerTokenAmount', 'takerTokenAmount', 'makerFee', @@ -18,14 +20,11 @@ export const typeConverters = { 'salt', ]); }, - convertBigNumberFieldsToStrings(obj: object, fields: string[]): void { - _.each(fields, field => { - _.update(obj, field, (value: BigNumber) => value.toString()); - }); - }, - convertStringsFieldsToBigNumbers(obj: object, fields: string[]): void { + convertStringsFieldsToBigNumbers(obj: any, fields: string[]): any { + const result = _.assign({}, obj); _.each(fields, field => { - _.update(obj, field, (value: string) => new BigNumber(value)); + _.update(result, field, (value: string) => new BigNumber(value)); }); + return result; }, }; diff --git a/packages/connect/src/ws_orderbook_channel.ts b/packages/connect/src/ws_orderbook_channel.ts index 399f97319..822a022f4 100644 --- a/packages/connect/src/ws_orderbook_channel.ts +++ b/packages/connect/src/ws_orderbook_channel.ts @@ -11,7 +11,7 @@ import { WebsocketClientEventType, WebsocketConnectionEventType, } from './types'; -import { orderbookChannelMessageParsers } from './utils/orderbook_channel_message_parsers'; +import { orderbookChannelMessageParser } from './utils/orderbook_channel_message_parser'; /** * This class includes all the functionality related to interacting with a websocket endpoint @@ -104,7 +104,7 @@ export class WebSocketOrderbookChannel implements OrderbookChannel { if (!_.isUndefined(message.utf8Data)) { try { const utf8Data = message.utf8Data; - const parserResult = orderbookChannelMessageParsers.parser(utf8Data); + const parserResult = orderbookChannelMessageParser.parse(utf8Data); if (parserResult.requestId === requestId) { switch (parserResult.type) { case OrderbookChannelMessageTypes.Snapshot: { diff --git a/packages/connect/test/http_client_test.ts b/packages/connect/test/http_client_test.ts index a0ab000e2..e7096edad 100644 --- a/packages/connect/test/http_client_test.ts +++ b/packages/connect/test/http_client_test.ts @@ -122,6 +122,18 @@ describe('HttpClient', () => { const fees = await relayerClient.getFeesAsync(request); expect(fees).to.be.deep.equal(feesResponse); }); + it('does not mutate input', async () => { + fetchMock.post(url, feesResponseJSON); + const makerTokenAmountBefore = new BigNumber(request.makerTokenAmount); + const takerTokenAmountBefore = new BigNumber(request.takerTokenAmount); + const saltBefore = new BigNumber(request.salt); + const expirationUnixTimestampSecBefore = new BigNumber(request.expirationUnixTimestampSec); + await relayerClient.getFeesAsync(request); + expect(makerTokenAmountBefore).to.be.deep.equal(request.makerTokenAmount); + expect(takerTokenAmountBefore).to.be.deep.equal(request.takerTokenAmount); + expect(saltBefore).to.be.deep.equal(request.salt); + expect(expirationUnixTimestampSecBefore).to.be.deep.equal(request.expirationUnixTimestampSec); + }); it('throws an error for invalid JSON response', async () => { fetchMock.post(url, { test: 'dummy' }); expect(relayerClient.getFeesAsync(request)).to.be.rejected(); diff --git a/packages/connect/test/orderbook_channel_message_parsers_test.ts b/packages/connect/test/orderbook_channel_message_parsers_test.ts index e6cc05fed..3e1f44384 100644 --- a/packages/connect/test/orderbook_channel_message_parsers_test.ts +++ b/packages/connect/test/orderbook_channel_message_parsers_test.ts @@ -2,7 +2,7 @@ import * as chai from 'chai'; import * as dirtyChai from 'dirty-chai'; import 'mocha'; -import { orderbookChannelMessageParsers } from '../src/utils/orderbook_channel_message_parsers'; +import { orderbookChannelMessageParser } from '../src/utils/orderbook_channel_message_parser'; import { orderResponse } from './fixtures/standard_relayer_api/order/0xabc67323774bdbd24d94f977fa9ac94a50f016026fd13f42990861238897721f'; import { orderbookResponse } from './fixtures/standard_relayer_api/orderbook'; @@ -20,20 +20,20 @@ chai.config.includeStack = true; chai.use(dirtyChai); const expect = chai.expect; -describe('orderbookChannelMessageParsers', () => { +describe('orderbookChannelMessageParser', () => { describe('#parser', () => { it('parses snapshot messages', () => { - const snapshotMessage = orderbookChannelMessageParsers.parser(snapshotOrderbookChannelMessage); + const snapshotMessage = orderbookChannelMessageParser.parse(snapshotOrderbookChannelMessage); expect(snapshotMessage.type).to.be.equal('snapshot'); expect(snapshotMessage.payload).to.be.deep.equal(orderbookResponse); }); it('parses update messages', () => { - const updateMessage = orderbookChannelMessageParsers.parser(updateOrderbookChannelMessage); + const updateMessage = orderbookChannelMessageParser.parse(updateOrderbookChannelMessage); expect(updateMessage.type).to.be.equal('update'); expect(updateMessage.payload).to.be.deep.equal(orderResponse); }); it('returns unknown message for messages with unsupported types', () => { - const unknownMessage = orderbookChannelMessageParsers.parser(unknownOrderbookChannelMessage); + const unknownMessage = orderbookChannelMessageParser.parse(unknownOrderbookChannelMessage); expect(unknownMessage.type).to.be.equal('unknown'); expect(unknownMessage.payload).to.be.undefined(); }); @@ -43,7 +43,7 @@ describe('orderbookChannelMessageParsers', () => { "requestId": 1, "payload": {} }`; - const badCall = () => orderbookChannelMessageParsers.parser(typelessMessage); + const badCall = () => orderbookChannelMessageParser.parse(typelessMessage); expect(badCall).throws(`Message is missing a type parameter: ${typelessMessage}`); }); it('throws when type is not a string', () => { @@ -53,22 +53,23 @@ describe('orderbookChannelMessageParsers', () => { "requestId": 1, "payload": {} }`; - const badCall = () => orderbookChannelMessageParsers.parser(messageWithBadType); + const badCall = () => orderbookChannelMessageParser.parse(messageWithBadType); expect(badCall).throws('Expected type to be of type string, encountered: 1'); }); it('throws when snapshot message has malformed payload', () => { - const badCall = () => orderbookChannelMessageParsers.parser(malformedSnapshotOrderbookChannelMessage); + const badCall = () => orderbookChannelMessageParser.parse(malformedSnapshotOrderbookChannelMessage); + // tslint:disable-next-line:max-line-length const errMsg = 'Validation errors: instance.payload requires property "bids", instance.payload requires property "asks"'; expect(badCall).throws(errMsg); }); it('throws when update message has malformed payload', () => { - const badCall = () => orderbookChannelMessageParsers.parser(malformedUpdateOrderbookChannelMessage); + const badCall = () => orderbookChannelMessageParser.parse(malformedUpdateOrderbookChannelMessage); expect(badCall).throws(/^Expected message to conform to schema/); }); it('throws when input message is not valid JSON', () => { const nonJsonString = 'h93b{sdfs9fsd f'; - const badCall = () => orderbookChannelMessageParsers.parser(nonJsonString); + const badCall = () => orderbookChannelMessageParser.parse(nonJsonString); expect(badCall).throws('Unexpected token h in JSON at position 0'); }); }); @@ -9450,6 +9450,12 @@ web3-typescript-typings@^0.7.2: dependencies: bignumber.js "^4.0.2" +web3-typescript-typings@^0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/web3-typescript-typings/-/web3-typescript-typings-0.9.0.tgz#c658db3c84427d9c05a93613e35e6d8147c931c4" + dependencies: + bignumber.js "^4.0.2" + web3-utils@^1.0.0-beta.26: version "1.0.0-beta.26" resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-1.0.0-beta.26.tgz#f04ad8c144b1781c6b20c2818e0532cb9e6dca15" |