From ddf3bb7c0446f2d85b6fa55cbe0b00b227f08af7 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Wed, 12 Dec 2018 17:26:54 -0800 Subject: Add Python SRA client to Developer home --- packages/website/ts/pages/documentation/docs_home.tsx | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'packages') diff --git a/packages/website/ts/pages/documentation/docs_home.tsx b/packages/website/ts/pages/documentation/docs_home.tsx index f68d2892f..acd17dd06 100644 --- a/packages/website/ts/pages/documentation/docs_home.tsx +++ b/packages/website/ts/pages/documentation/docs_home.tsx @@ -91,6 +91,15 @@ const CATEGORY_TO_PACKAGES: ObjectMap = { shouldOpenInNewTab: true, }, }, + { + description: + 'A Python Standard Relayer API client', + link: { + title: '0x-sra-client.py', + to: 'https://pypi.org/project/0x-sra-client/', + shouldOpenInNewTab: true, + }, + }, { description: 'An http & websocket client for interacting with relayers that have implemented the [Standard Relayer API](https://github.com/0xProject/standard-relayer-api)', -- cgit From 687749460d026ae8b16e355c85c70e1e79b63252 Mon Sep 17 00:00:00 2001 From: kao Date: Mon, 26 Nov 2018 17:12:33 -0800 Subject: WIP: OrderWatcher WebSocket Currently incomplete. Main challenge is to figure out how to test a client + server setup in the single-threaded javascript environment. --- packages/order-watcher/package.json | 3 +- .../src/order_watcher/order_watcher_websocket.ts | 184 +++++++++++++++++ .../src/schemas/websocket_utf8_message_schema.ts | 8 + .../test/order_watcher_websocket_test.ts | 226 +++++++++++++++++++++ 4 files changed, 420 insertions(+), 1 deletion(-) create mode 100644 packages/order-watcher/src/order_watcher/order_watcher_websocket.ts create mode 100644 packages/order-watcher/src/schemas/websocket_utf8_message_schema.ts create mode 100644 packages/order-watcher/test/order_watcher_websocket_test.ts (limited to 'packages') diff --git a/packages/order-watcher/package.json b/packages/order-watcher/package.json index 9a51203f4..cfaf5d724 100644 --- a/packages/order-watcher/package.json +++ b/packages/order-watcher/package.json @@ -74,7 +74,8 @@ "ethereum-types": "^1.1.2", "ethereumjs-blockstream": "6.0.0", "ethers": "~4.0.4", - "lodash": "^4.17.5" + "lodash": "^4.17.5", + "websocket": "^1.0.25" }, "publishConfig": { "access": "public" diff --git a/packages/order-watcher/src/order_watcher/order_watcher_websocket.ts b/packages/order-watcher/src/order_watcher/order_watcher_websocket.ts new file mode 100644 index 000000000..84afc4000 --- /dev/null +++ b/packages/order-watcher/src/order_watcher/order_watcher_websocket.ts @@ -0,0 +1,184 @@ +import { ContractAddresses } from '@0x/contract-addresses'; +import { SignedOrder } from '@0x/types'; +import { BigNumber, logUtils } from '@0x/utils'; +import { Provider } from 'ethereum-types'; +import * as http from 'http'; +import * as WebSocket from 'websocket'; + +import { webSocketUtf8MessageSchema } from '../schemas/websocket_utf8_message_schema'; +import { OnOrderStateChangeCallback, OrderWatcherConfig } from '../types'; +import { assert } from '../utils/assert'; + +import { OrderWatcher } from './order_watcher'; + +const DEFAULT_HTTP_PORT = 8080; + +const enum OrderWatcherAction { + // Actions initiated by the user. + getStats = 'getStats', + addOrderAsync = 'addOrderAsync', + removeOrder = 'removeOrder', + // These are spontaneous; they are primarily orderstate changes. + orderWatcherUpdate = 'orderWatcherUpdate', + // `subscribe` and `unsubscribe` are methods of OrderWatcher, but we don't + // need to expose them to the WebSocket server user because the user implicitly + // subscribes and unsubscribes by connecting and disconnecting from the server. +} + +// Users have to create a json object of this format and attach it to +// the data field of their WebSocket message to interact with this server. +interface WebSocketRequestData { + action: OrderWatcherAction; + params: any; +} + +// Users should expect a json object of this format in the data field +// of the WebSocket messages that this server sends out. +interface WebSocketResponseData { + action: OrderWatcherAction; + success: number; + result: any; +} + +// Wraps the OrderWatcher functionality in a WebSocket server. Motivations: +// 1) Users can watch orders via non-typescript programs. +// 2) Better encapsulation so that users can work +export class OrderWatcherWebSocketServer { + public httpServer: http.Server; + public readonly _orderWatcher: OrderWatcher; + private readonly _connectionStore: Set; + private readonly _wsServer: WebSocket.server; + /** + * Instantiate a new web socket server which provides OrderWatcher functionality + * @param provider Web3 provider to use for JSON RPC calls (for OrderWatcher) + * @param networkId NetworkId to watch orders on (for OrderWatcher) + * @param contractAddresses Optional contract addresses. Defaults to known + * addresses based on networkId (for OrderWatcher) + * @param partialConfig Optional configurations (for OrderWatcher) + */ + constructor( + provider: Provider, + networkId: number, + contractAddresses?: ContractAddresses, + partialConfig?: Partial, + ) { + this._orderWatcher = new OrderWatcher(provider, networkId, contractAddresses, partialConfig); + this._connectionStore = new Set(); + this.httpServer = http.createServer(); + this._wsServer = new WebSocket.server({ + httpServer: this.httpServer, + autoAcceptConnections: false, + }); + + this._wsServer.on('request', (request: any) => { + // Designed for usage pattern where client and server are run on the same + // machine by the same user. As such, no security checks are in place. + const connection: WebSocket.connection = request.accept(null, request.origin); + logUtils.log(`${new Date()} [Server] Accepted connection from origin ${request.origin}.`); + connection.on('message', this._messageCallback.bind(this, connection)); + connection.on('close', this._closeCallback.bind(this, connection)); + this._connectionStore.add(connection); + }); + + // Have the WebSocket server subscribe to the OrderWatcher to receive updates. + // These updates are then broadcast to clients in the _connectionStore. + this._orderWatcher.subscribe(this._broadcastCallback); + } + + /** + * Activates the WebSocket server by having its HTTP server start listening. + */ + public listen(): void { + this.httpServer.listen(DEFAULT_HTTP_PORT, () => { + logUtils.log(`${new Date()} [Server] Listening on port ${DEFAULT_HTTP_PORT}`); + }); + } + + public close(): void { + this.httpServer.close(); + } + + private _messageCallback(connection: WebSocket.connection, message: any): void { + assert.doesConformToSchema('message', message, webSocketUtf8MessageSchema); + const requestData: WebSocketRequestData = JSON.parse(message.utf8Data); + const responseData = this._routeRequest(requestData); + logUtils.log(`${new Date()} [Server] OrderWatcher output: ${JSON.stringify(responseData)}`); + connection.sendUTF(JSON.stringify(responseData)); + } + + private _closeCallback(connection: WebSocket.connection): void { + this._connectionStore.delete(connection); + logUtils.log(`${new Date()} [Server] Client ${connection.remoteAddress} disconnected.`); + } + + private _routeRequest(requestData: WebSocketRequestData): WebSocketResponseData { + const responseData: WebSocketResponseData = { + action: requestData.action, + success: 0, + result: undefined, + }; + + try { + logUtils.log(`${new Date()} [Server] Request received: ${requestData.action}`); + switch (requestData.action) { + case 'addOrderAsync': { + const signedOrder: SignedOrder = this._parseSignedOrder(requestData); + // tslint:disable-next-line:no-floating-promises + this._orderWatcher.addOrderAsync(signedOrder); // Ok to fireNforget + break; + } + case 'removeOrder': { + this._orderWatcher.removeOrder(requestData.params.orderHash); + break; + } + case 'getStats': { + responseData.result = this._orderWatcher.getStats(); + break; + } + default: + throw new Error(`[Server] Invalid request action: ${requestData.action}`); + } + responseData.success = 1; + } catch (err) { + responseData.result = { error: err.toString() }; + } + return responseData; + } + + /** + * Broadcasts OrderState changes to ALL connected clients. At the moment, + * we do not support clients subscribing to only a subset of orders. As such, + * Client B will be notified of changes to an order that Client A added. + */ + private readonly _broadcastCallback: OnOrderStateChangeCallback = (err, orderState) => { + this._connectionStore.forEach((connection: WebSocket.connection) => { + const responseData: WebSocketResponseData = { + action: OrderWatcherAction.orderWatcherUpdate, + success: 1, + result: orderState || err, + }; + connection.sendUTF(JSON.stringify(responseData)); + }); + // tslint:disable-next-line:semicolon + }; // tslint thinks this is a class method, It's actally a property that holds a function. + + /** + * Recover types lost when the payload is stringified. + */ + private readonly _parseSignedOrder = (requestData: WebSocketRequestData) => { + const signedOrder = requestData.params.signedOrder; + const bigNumberFields = [ + 'salt', + 'makerFee', + 'takerFee', + 'makerAssetAmount', + 'takerAssetAmount', + 'expirationTimeSeconds', + ]; + for (const field of bigNumberFields) { + signedOrder[field] = new BigNumber(signedOrder[field]); + } + return signedOrder; + // tslint:disable-next-line:semicolon + }; // tslint thinks this is a class method, It's actally a property that holds a function. +} diff --git a/packages/order-watcher/src/schemas/websocket_utf8_message_schema.ts b/packages/order-watcher/src/schemas/websocket_utf8_message_schema.ts new file mode 100644 index 000000000..0a0eed407 --- /dev/null +++ b/packages/order-watcher/src/schemas/websocket_utf8_message_schema.ts @@ -0,0 +1,8 @@ +export const webSocketUtf8MessageSchema = { + id: '/WebSocketUtf8MessageSchema', + properties: { + utf8Data: { type: 'string' }, + }, + type: 'object', + required: ['utf8Data'], +}; diff --git a/packages/order-watcher/test/order_watcher_websocket_test.ts b/packages/order-watcher/test/order_watcher_websocket_test.ts new file mode 100644 index 000000000..e7b18e44b --- /dev/null +++ b/packages/order-watcher/test/order_watcher_websocket_test.ts @@ -0,0 +1,226 @@ +import { ContractWrappers } from '@0x/contract-wrappers'; +import { tokenUtils } from '@0x/contract-wrappers/lib/test/utils/token_utils'; +import { BlockchainLifecycle } from '@0x/dev-utils'; +import { FillScenarios } from '@0x/fill-scenarios'; +import { assetDataUtils, orderHashUtils } from '@0x/order-utils'; +import { ExchangeContractErrs, OrderStateInvalid, OrderStateValid, SignedOrder } from '@0x/types'; +import { BigNumber, logUtils } from '@0x/utils'; +import { Web3Wrapper } from '@0x/web3-wrapper'; +import * as chai from 'chai'; +import 'mocha'; +import * as WebSocket from 'websocket'; + +import { OrderWatcherWebSocketServer } from '../src/order_watcher/order_watcher_websocket'; + +import { chaiSetup } from './utils/chai_setup'; +import { constants } from './utils/constants'; +import { migrateOnceAsync } from './utils/migrate'; +import { provider, web3Wrapper } from './utils/web3_wrapper'; + +chaiSetup.configure(); +const expect = chai.expect; +const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); + +interface WsMessage { + data: string; +} + +describe.only('OrderWatcherWebSocket', async () => { + let contractWrappers: ContractWrappers; + let wsServer: OrderWatcherWebSocketServer; + let wsClient: WebSocket.w3cwebsocket; + let wsClientTwo: WebSocket.w3cwebsocket; + let fillScenarios: FillScenarios; + let userAddresses: string[]; + let makerAssetData: string; + let takerAssetData: string; + let makerTokenAddress: string; + let takerTokenAddress: string; + let makerAddress: string; + let takerAddress: string; + let zrxTokenAddress: string; + let signedOrder: SignedOrder; + let orderHash: string; + let addOrderPayload: { action: string; params: { signedOrder: SignedOrder } }; + let removeOrderPayload: { action: string; params: { orderHash: string } }; + const decimals = constants.ZRX_DECIMALS; + const fillableAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(5), decimals); + // createFillableSignedOrderAsync is Promise-based, which forces us + // to use Promises instead of the done() callbacks for tests. + // onmessage callback must thus be wrapped as a Promise. + const _onMessageAsync = async (client: WebSocket.w3cwebsocket) => + new Promise(resolve => { + client.onmessage = (msg: WsMessage) => resolve(msg); + }); + + before(async () => { + // Set up constants + const contractAddresses = await migrateOnceAsync(); + await blockchainLifecycle.startAsync(); + const networkId = constants.TESTRPC_NETWORK_ID; + const config = { + networkId, + contractAddresses, + }; + contractWrappers = new ContractWrappers(provider, config); + userAddresses = await web3Wrapper.getAvailableAddressesAsync(); + zrxTokenAddress = contractAddresses.zrxToken; + [makerAddress, takerAddress] = userAddresses; + [makerTokenAddress, takerTokenAddress] = tokenUtils.getDummyERC20TokenAddresses(); + [makerAssetData, takerAssetData] = [ + assetDataUtils.encodeERC20AssetData(makerTokenAddress), + assetDataUtils.encodeERC20AssetData(takerTokenAddress), + ]; + fillScenarios = new FillScenarios( + provider, + userAddresses, + zrxTokenAddress, + contractAddresses.exchange, + contractAddresses.erc20Proxy, + contractAddresses.erc721Proxy, + ); + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerAssetData, + takerAssetData, + makerAddress, + takerAddress, + fillableAmount, + ); + orderHash = orderHashUtils.getOrderHashHex(signedOrder); + addOrderPayload = { + action: 'addOrderAsync', + params: { signedOrder }, + }; + removeOrderPayload = { + action: 'removeOrder', + params: { orderHash }, + }; + + // Prepare OrderWatcher WebSocket server + const orderWatcherConfig = {}; + wsServer = new OrderWatcherWebSocketServer(provider, networkId, contractAddresses, orderWatcherConfig); + wsServer.listen(); + }); + after(async () => { + await blockchainLifecycle.revertAsync(); + wsServer.close(); + }); + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + wsClient = new WebSocket.w3cwebsocket('ws://127.0.0.1:8080/'); + logUtils.log(`${new Date()} [Client] Connected.`); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + wsClient.close(); + logUtils.log(`${new Date()} [Client] Closed.`); + }); + + it('responds to getStats requests correctly', (done: any) => { + const payload = { + action: 'getStats', + params: {}, + }; + wsClient.onopen = () => wsClient.send(JSON.stringify(payload)); + wsClient.onmessage = (msg: any) => { + const responseData = JSON.parse(msg.data); + expect(responseData.action).to.be.eq('getStats'); + expect(responseData.success).to.be.eq(1); + expect(responseData.result.orderCount).to.be.eq(0); + done(); + }; + }); + + it('throws an error when an invalid action is attempted', async () => { + const invalidActionPayload = { + action: 'badAction', + params: {}, + }; + wsClient.onopen = () => wsClient.send(JSON.stringify(invalidActionPayload)); + const errorMsg = await _onMessageAsync(wsClient); + const errorData = JSON.parse(errorMsg.data); + expect(errorData.action).to.be.eq('badAction'); + expect(errorData.success).to.be.eq(0); + expect(errorData.result.error).to.be.eq('Error: [Server] Invalid request action: badAction'); + }); + + it('executes addOrderAsync and removeOrder requests correctly', async () => { + wsClient.onopen = () => wsClient.send(JSON.stringify(addOrderPayload)); + const addOrderMsg = await _onMessageAsync(wsClient); + const addOrderData = JSON.parse(addOrderMsg.data); + expect(addOrderData.action).to.be.eq('addOrderAsync'); + expect(addOrderData.success).to.be.eq(1); + expect((wsServer._orderWatcher as any)._orderByOrderHash).to.deep.include({ + [orderHash]: signedOrder, + }); + + wsClient.send(JSON.stringify(removeOrderPayload)); + const removeOrderMsg = await _onMessageAsync(wsClient); + const removeOrderData = JSON.parse(removeOrderMsg.data); + expect(removeOrderData.action).to.be.eq('removeOrder'); + expect(removeOrderData.success).to.be.eq(1); + expect((wsServer._orderWatcher as any)._orderByOrderHash).to.not.deep.include({ + [orderHash]: signedOrder, + }); + }); + + it('broadcasts orderStateInvalid message when makerAddress allowance set to 0 for watched order', async () => { + // Add the regular order + wsClient.onopen = () => wsClient.send(JSON.stringify(addOrderPayload)); + await _onMessageAsync(wsClient); + + // Set the allowance to 0 + await contractWrappers.erc20Token.setProxyAllowanceAsync(makerTokenAddress, makerAddress, new BigNumber(0)); + + // Ensure that orderStateInvalid message is received. + const orderWatcherUpdateMsg = await _onMessageAsync(wsClient); + const orderWatcherUpdateData = JSON.parse(orderWatcherUpdateMsg.data); + expect(orderWatcherUpdateData.action).to.be.eq('orderWatcherUpdate'); + expect(orderWatcherUpdateData.success).to.be.eq(1); + const invalidOrderState = orderWatcherUpdateData.result as OrderStateInvalid; + expect(invalidOrderState.isValid).to.be.false(); + expect(invalidOrderState.orderHash).to.be.eq(orderHash); + expect(invalidOrderState.error).to.be.eq(ExchangeContractErrs.InsufficientMakerAllowance); + }); + + it('broadcasts to multiple clients when an order backing ZRX allowance changes', async () => { + // Prepare order + const makerFee = Web3Wrapper.toBaseUnitAmount(new BigNumber(2), decimals); + const takerFee = Web3Wrapper.toBaseUnitAmount(new BigNumber(0), decimals); + const nonZeroMakerFeeSignedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync( + makerAssetData, + takerAssetData, + makerFee, + takerFee, + makerAddress, + takerAddress, + fillableAmount, + takerAddress, + ); + const nonZeroMakerFeeOrderPayload = { + action: 'addOrderAsync', + params: { nonZeroMakerFeeSignedOrder }, + }; + + // Set up a second client and have it add the order + wsClientTwo = new WebSocket.w3cwebsocket('ws://127.0.0.1:8080/'); + logUtils.log(`${new Date()} [Client] Connected.`); + wsClientTwo.onopen = () => wsClientTwo.send(JSON.stringify(nonZeroMakerFeeOrderPayload)); + await _onMessageAsync(wsClientTwo); + + // Change the allowance + await contractWrappers.erc20Token.setProxyAllowanceAsync(zrxTokenAddress, makerAddress, new BigNumber(0)); + + // Check that both clients receive the emitted event + for (const client of [wsClient, wsClientTwo]) { + const updateMsg = await _onMessageAsync(client); + const updateData = JSON.parse(updateMsg.data); + const orderState = updateData.result as OrderStateValid; + expect(orderState.isValid).to.be.true(); + expect(orderState.orderRelevantState.makerFeeProxyAllowance).to.be.eq('0'); + } + + wsClientTwo.close(); + logUtils.log(`${new Date()} [Client] Closed.`); + }); +}); -- cgit From d9b58848346a4be41684eea244e393afaab6a617 Mon Sep 17 00:00:00 2001 From: kao Date: Thu, 13 Dec 2018 13:33:46 -0800 Subject: Respond to CR --- packages/order-watcher/README.md | 81 ++++++++ .../src/order_watcher/order_watcher_websocket.ts | 209 ++++++++++----------- .../order-watcher/src/schemas/websocket_schemas.ts | 32 ++++ .../src/schemas/websocket_utf8_message_schema.ts | 8 - packages/order-watcher/src/types.ts | 30 ++- .../test/order_watcher_websocket_test.ts | 79 +++++--- 6 files changed, 295 insertions(+), 144 deletions(-) create mode 100644 packages/order-watcher/src/schemas/websocket_schemas.ts delete mode 100644 packages/order-watcher/src/schemas/websocket_utf8_message_schema.ts (limited to 'packages') diff --git a/packages/order-watcher/README.md b/packages/order-watcher/README.md index c0b99b272..7eae0ae16 100644 --- a/packages/order-watcher/README.md +++ b/packages/order-watcher/README.md @@ -4,6 +4,9 @@ An order watcher daemon that watches for order validity. #### Read the wiki [article](https://0xproject.com/wiki#0x-OrderWatcher). +OrderWatcher also comes with a WebSocket server to provide language-agnostic access +to order watching functionality. We used the [WebSocket Client and Server Implementation for Node](https://www.npmjs.com/package/websocket). + ## Installation **Install** @@ -26,6 +29,84 @@ If your project is in [TypeScript](https://www.typescriptlang.org/), add the fol } ``` +## Using the WebSocket Server + +**Setup** + +**Environmental Variables** +Several environmental variables can be set to configure the server: + +* `ORDER_WATCHER_HTTP_PORT` specifies the port that the http server will listen on + and accept connections from. When this is not set, we default to 8080. + +**Requests** +The server accepts three types of requests: `ADD_ORDER`, `REMOVE_ORDER` and `GET_STATS`. These mirror what the underlying OrderWatcher does. You can read more in the [wiki](https://0xproject.com/wiki#0x-OrderWatcher). Unlike the OrderWatcher, it does not expose any subscribe or unsubscribe functionality because the client implicitly subscribes and unsubscribes by connecting to the server. + +The first step for making a request is establishing a connection with the server. In Javascript: + +``` +var W3CWebSocket = require('websocket').w3cwebsocket; +wsClient = new WebSocket.w3cwebsocket('ws://127.0.0.1:8080'); +``` + +In Python, you could use the [websocket-client library](http://pypi.python.org/pypi/websocket-client/) and run: + +``` +from websocket import create_connection +wsClient = create_connection("ws://127.0.0.1:8080") +``` + +With the connection established, you prepare the payload for your request. The payload is a json object with the following structure: + +* For `GET_STATE`, the payload is `{ action: 'GET_STATS }`. +* For `ADD_ORDER`, use `{ action: 'ADD_ORDER', signedOrder: }`. +* For `REMOVE_ORDER`, use `{ action: 'REMOVE_ORDER', orderHash: }`. + +Next, convert the payload to a string and send it through the connection. +In Javascript: + +``` +const addOrderPayload = { + action: 'ADD_ORDER', + signedOrder: , +}; +wsClient.send(JSON.stringify(addOrderPayload)); +``` + +In Python: + +``` +import json +remove_order_payload = { + 'action': 'REMOVE_ORDER', + 'orderHash': '0x6edc16bf37fde79f5012088c33784c730e2f103d9ab1caf73060c386ad107b7e', +} +wsClient.send(json.dumps(remove_order_payload)); +``` + +**Response** +The server responds to all requests in a similar format. In the data field, you'll find another json object that has been converted into a string. This json object contains the following fields: + +* `action`: The action the server is responding to. Eg. `ADD_ORDER`. When order states change the server may also initiate a response. In this case, action will be listed as `UPDATE`. +* `success`: `true` or `false`; Indicates whether the server handled the request without problems. +* `result`: This field varies based on the action. `UPDATE` responses contained the new order state. `GET_STATS` responses contain the current order count. When there are errors, the error messages are stored in here. + +In Javascript, the responses can be parsed using the `onmessage` callback: + +``` +wsClient.onmessage = (msg) => { + const responseData = JSON.parse(msg.data); + const action = responseData.action +}; +``` + +In Python, `recv` is a lightweight way to receive a response: + +``` +result = wsClient.recv() +action = result.action +``` + ## Contributing We strongly recommend that the community help us make improvements and determine the future direction of the protocol. To report bugs within this package, please create an issue in this repository. diff --git a/packages/order-watcher/src/order_watcher/order_watcher_websocket.ts b/packages/order-watcher/src/order_watcher/order_watcher_websocket.ts index 84afc4000..806c7c6a5 100644 --- a/packages/order-watcher/src/order_watcher/order_watcher_websocket.ts +++ b/packages/order-watcher/src/order_watcher/order_watcher_websocket.ts @@ -1,60 +1,57 @@ import { ContractAddresses } from '@0x/contract-addresses'; -import { SignedOrder } from '@0x/types'; +import { OrderState, SignedOrder } from '@0x/types'; import { BigNumber, logUtils } from '@0x/utils'; import { Provider } from 'ethereum-types'; import * as http from 'http'; import * as WebSocket from 'websocket'; -import { webSocketUtf8MessageSchema } from '../schemas/websocket_utf8_message_schema'; -import { OnOrderStateChangeCallback, OrderWatcherConfig } from '../types'; +import { webSocketRequestSchema, webSocketUtf8MessageSchema } from '../schemas/websocket_schemas'; +import { + OnOrderStateChangeCallback, + OrderWatcherAction, + OrderWatcherConfig, + WebSocketRequest, + WebSocketResponse, +} from '../types'; import { assert } from '../utils/assert'; import { OrderWatcher } from './order_watcher'; const DEFAULT_HTTP_PORT = 8080; -const enum OrderWatcherAction { - // Actions initiated by the user. - getStats = 'getStats', - addOrderAsync = 'addOrderAsync', - removeOrder = 'removeOrder', - // These are spontaneous; they are primarily orderstate changes. - orderWatcherUpdate = 'orderWatcherUpdate', - // `subscribe` and `unsubscribe` are methods of OrderWatcher, but we don't - // need to expose them to the WebSocket server user because the user implicitly - // subscribes and unsubscribes by connecting and disconnecting from the server. -} - -// Users have to create a json object of this format and attach it to -// the data field of their WebSocket message to interact with this server. -interface WebSocketRequestData { - action: OrderWatcherAction; - params: any; -} - -// Users should expect a json object of this format in the data field -// of the WebSocket messages that this server sends out. -interface WebSocketResponseData { - action: OrderWatcherAction; - success: number; - result: any; -} - // Wraps the OrderWatcher functionality in a WebSocket server. Motivations: // 1) Users can watch orders via non-typescript programs. // 2) Better encapsulation so that users can work export class OrderWatcherWebSocketServer { - public httpServer: http.Server; - public readonly _orderWatcher: OrderWatcher; + public readonly _orderWatcher: OrderWatcher; // public for testing + private readonly _httpServer: http.Server; private readonly _connectionStore: Set; private readonly _wsServer: WebSocket.server; /** - * Instantiate a new web socket server which provides OrderWatcher functionality - * @param provider Web3 provider to use for JSON RPC calls (for OrderWatcher) - * @param networkId NetworkId to watch orders on (for OrderWatcher) + * Recover types lost when the payload is stringified. + */ + private static _parseSignedOrder(rawRequest: any): SignedOrder { + const bigNumberFields = [ + 'salt', + 'makerFee', + 'takerFee', + 'makerAssetAmount', + 'takerAssetAmount', + 'expirationTimeSeconds', + ]; + for (const field of bigNumberFields) { + rawRequest[field] = new BigNumber(rawRequest[field]); + } + return rawRequest; + } + + /** + * Instantiate a new WebSocket server which provides OrderWatcher functionality + * @param provider Web3 provider to use for JSON RPC calls. + * @param networkId NetworkId to watch orders on. * @param contractAddresses Optional contract addresses. Defaults to known - * addresses based on networkId (for OrderWatcher) - * @param partialConfig Optional configurations (for OrderWatcher) + * addresses based on networkId. + * @param partialConfig Optional configurations. */ constructor( provider: Provider, @@ -64,85 +61,98 @@ export class OrderWatcherWebSocketServer { ) { this._orderWatcher = new OrderWatcher(provider, networkId, contractAddresses, partialConfig); this._connectionStore = new Set(); - this.httpServer = http.createServer(); + this._httpServer = http.createServer(); this._wsServer = new WebSocket.server({ - httpServer: this.httpServer, + httpServer: this._httpServer, + // Avoid setting autoAcceptConnections to true as it defeats all + // standard cross-origin protection facilities built into the protocol + // and the browser. Also ensures that a request event is emitted by + // the server whenever a new WebSocket request is made. autoAcceptConnections: false, }); - this._wsServer.on('request', (request: any) => { + this._wsServer.on('request', async (request: any) => { // Designed for usage pattern where client and server are run on the same // machine by the same user. As such, no security checks are in place. const connection: WebSocket.connection = request.accept(null, request.origin); logUtils.log(`${new Date()} [Server] Accepted connection from origin ${request.origin}.`); - connection.on('message', this._messageCallback.bind(this, connection)); - connection.on('close', this._closeCallback.bind(this, connection)); + connection.on('message', await this._onMessageCallbackAsync.bind(this, connection)); + connection.on('close', this._onCloseCallback.bind(this, connection)); this._connectionStore.add(connection); }); // Have the WebSocket server subscribe to the OrderWatcher to receive updates. // These updates are then broadcast to clients in the _connectionStore. - this._orderWatcher.subscribe(this._broadcastCallback); + const broadcastCallback: OnOrderStateChangeCallback = this._broadcastCallback.bind(this); + this._orderWatcher.subscribe(broadcastCallback); } /** * Activates the WebSocket server by having its HTTP server start listening. */ public listen(): void { - this.httpServer.listen(DEFAULT_HTTP_PORT, () => { - logUtils.log(`${new Date()} [Server] Listening on port ${DEFAULT_HTTP_PORT}`); + const port = process.env.ORDER_WATCHER_HTTP_PORT || DEFAULT_HTTP_PORT; + this._httpServer.listen(port, () => { + logUtils.log(`${new Date()} [Server] Listening on port ${port}`); }); } + /** + * Deactivates the WebSocket server by stopping the HTTP server from accepting + * new connections. + */ public close(): void { - this.httpServer.close(); + this._httpServer.close(); } - private _messageCallback(connection: WebSocket.connection, message: any): void { - assert.doesConformToSchema('message', message, webSocketUtf8MessageSchema); - const requestData: WebSocketRequestData = JSON.parse(message.utf8Data); - const responseData = this._routeRequest(requestData); - logUtils.log(`${new Date()} [Server] OrderWatcher output: ${JSON.stringify(responseData)}`); - connection.sendUTF(JSON.stringify(responseData)); + private async _onMessageCallbackAsync(connection: WebSocket.connection, message: any): Promise { + const response: WebSocketResponse = { + action: null, + success: false, + result: null, + }; + try { + assert.doesConformToSchema('message', message, webSocketUtf8MessageSchema); + const request: WebSocketRequest = JSON.parse(message.utf8Data); + assert.doesConformToSchema('request', request, webSocketRequestSchema); + response.action = request.action; + response.success = true; + response.result = await this._routeRequestAsync(request); + } catch (err) { + response.result = err.toString(); + } + logUtils.log(`${new Date()} [Server] OrderWatcher output: ${JSON.stringify(response)}`); + connection.sendUTF(JSON.stringify(response)); } - private _closeCallback(connection: WebSocket.connection): void { + private _onCloseCallback(connection: WebSocket.connection): void { this._connectionStore.delete(connection); logUtils.log(`${new Date()} [Server] Client ${connection.remoteAddress} disconnected.`); } - private _routeRequest(requestData: WebSocketRequestData): WebSocketResponseData { - const responseData: WebSocketResponseData = { - action: requestData.action, - success: 0, - result: undefined, - }; - - try { - logUtils.log(`${new Date()} [Server] Request received: ${requestData.action}`); - switch (requestData.action) { - case 'addOrderAsync': { - const signedOrder: SignedOrder = this._parseSignedOrder(requestData); - // tslint:disable-next-line:no-floating-promises - this._orderWatcher.addOrderAsync(signedOrder); // Ok to fireNforget - break; - } - case 'removeOrder': { - this._orderWatcher.removeOrder(requestData.params.orderHash); - break; - } - case 'getStats': { - responseData.result = this._orderWatcher.getStats(); - break; - } - default: - throw new Error(`[Server] Invalid request action: ${requestData.action}`); + private async _routeRequestAsync(request: WebSocketRequest): Promise { + logUtils.log(`${new Date()} [Server] Request received: ${request.action}`); + let result = null; + switch (request.action) { + case OrderWatcherAction.AddOrder: { + const signedOrder: SignedOrder = OrderWatcherWebSocketServer._parseSignedOrder(request.signedOrder); + await this._orderWatcher.addOrderAsync(signedOrder); + break; } - responseData.success = 1; - } catch (err) { - responseData.result = { error: err.toString() }; + case OrderWatcherAction.RemoveOrder: { + const orderHash = request.orderHash || '_'; + this._orderWatcher.removeOrder(orderHash); + break; + } + case OrderWatcherAction.GetStats: { + result = this._orderWatcher.getStats(); + break; + } + default: + // Should never reach here. Should be caught by JSON schema check. + throw new Error(`[Server] Invalid request action: ${request.action}`); } - return responseData; + return result; } /** @@ -150,35 +160,14 @@ export class OrderWatcherWebSocketServer { * we do not support clients subscribing to only a subset of orders. As such, * Client B will be notified of changes to an order that Client A added. */ - private readonly _broadcastCallback: OnOrderStateChangeCallback = (err, orderState) => { + private _broadcastCallback(err: Error | null, orderState?: OrderState): void { this._connectionStore.forEach((connection: WebSocket.connection) => { - const responseData: WebSocketResponseData = { - action: OrderWatcherAction.orderWatcherUpdate, - success: 1, + const response: WebSocketResponse = { + action: OrderWatcherAction.Update, + success: true, result: orderState || err, }; - connection.sendUTF(JSON.stringify(responseData)); + connection.sendUTF(JSON.stringify(response)); }); - // tslint:disable-next-line:semicolon - }; // tslint thinks this is a class method, It's actally a property that holds a function. - - /** - * Recover types lost when the payload is stringified. - */ - private readonly _parseSignedOrder = (requestData: WebSocketRequestData) => { - const signedOrder = requestData.params.signedOrder; - const bigNumberFields = [ - 'salt', - 'makerFee', - 'takerFee', - 'makerAssetAmount', - 'takerAssetAmount', - 'expirationTimeSeconds', - ]; - for (const field of bigNumberFields) { - signedOrder[field] = new BigNumber(signedOrder[field]); - } - return signedOrder; - // tslint:disable-next-line:semicolon - }; // tslint thinks this is a class method, It's actally a property that holds a function. + } } diff --git a/packages/order-watcher/src/schemas/websocket_schemas.ts b/packages/order-watcher/src/schemas/websocket_schemas.ts new file mode 100644 index 000000000..c250d12f1 --- /dev/null +++ b/packages/order-watcher/src/schemas/websocket_schemas.ts @@ -0,0 +1,32 @@ +export const webSocketUtf8MessageSchema = { + id: '/webSocketUtf8MessageSchema', + properties: { + utf8Data: { type: 'string' }, + }, + type: 'object', + required: ['utf8Data'], +}; + +export const webSocketRequestSchema = { + id: '/webSocketRequestSchema', + properties: { + action: { enum: ['GET_STATS', 'ADD_ORDER', 'REMOVE_ORDER'] }, + signedOrder: { $ref: '/signedOrderSchema' }, + orderHash: { type: 'string' }, + }, + anyOf: [ + { + properties: { action: { enum: ['ADD_ORDER'] } }, + required: ['signedOrder'], + }, + { + properties: { action: { enum: ['REMOVE_ORDER'] } }, + required: ['orderHash'], + }, + { + properties: { action: { enum: ['GET_STATS'] } }, + required: [], + }, + ], + type: 'object', +}; diff --git a/packages/order-watcher/src/schemas/websocket_utf8_message_schema.ts b/packages/order-watcher/src/schemas/websocket_utf8_message_schema.ts deleted file mode 100644 index 0a0eed407..000000000 --- a/packages/order-watcher/src/schemas/websocket_utf8_message_schema.ts +++ /dev/null @@ -1,8 +0,0 @@ -export const webSocketUtf8MessageSchema = { - id: '/WebSocketUtf8MessageSchema', - properties: { - utf8Data: { type: 'string' }, - }, - type: 'object', - required: ['utf8Data'], -}; diff --git a/packages/order-watcher/src/types.ts b/packages/order-watcher/src/types.ts index 8078dd971..7f6219732 100644 --- a/packages/order-watcher/src/types.ts +++ b/packages/order-watcher/src/types.ts @@ -1,4 +1,4 @@ -import { OrderState } from '@0x/types'; +import { OrderState, SignedOrder } from '@0x/types'; import { LogEntryEvent } from 'ethereum-types'; export enum OrderWatcherError { @@ -31,3 +31,31 @@ export enum InternalOrderWatcherError { ZrxNotInTokenRegistry = 'ZRX_NOT_IN_TOKEN_REGISTRY', WethNotInTokenRegistry = 'WETH_NOT_IN_TOKEN_REGISTRY', } + +export enum OrderWatcherAction { + // Actions initiated by the user. + GetStats = 'GET_STATS', + AddOrder = 'ADD_ORDER', + RemoveOrder = 'REMOVE_ORDER', + // These are spontaneous; they are primarily orderstate changes. + Update = 'UPDATE', + // `subscribe` and `unsubscribe` are methods of OrderWatcher, but we don't + // need to expose them to the WebSocket server user because the user implicitly + // subscribes and unsubscribes by connecting and disconnecting from the server. +} + +// Users have to create a json object of this format and attach it to +// the data field of their WebSocket message to interact with the server. +export interface WebSocketRequest { + action: OrderWatcherAction; + signedOrder?: SignedOrder; + orderHash?: string; +} + +// Users should expect a json object of this format in the data field +// of the WebSocket messages that the server sends out. +export interface WebSocketResponse { + action: OrderWatcherAction | null; + success: boolean; + result: any; +} diff --git a/packages/order-watcher/test/order_watcher_websocket_test.ts b/packages/order-watcher/test/order_watcher_websocket_test.ts index e7b18e44b..a9e72ce21 100644 --- a/packages/order-watcher/test/order_watcher_websocket_test.ts +++ b/packages/order-watcher/test/order_watcher_websocket_test.ts @@ -41,11 +41,11 @@ describe.only('OrderWatcherWebSocket', async () => { let zrxTokenAddress: string; let signedOrder: SignedOrder; let orderHash: string; - let addOrderPayload: { action: string; params: { signedOrder: SignedOrder } }; - let removeOrderPayload: { action: string; params: { orderHash: string } }; + let addOrderPayload: { action: string; signedOrder: SignedOrder }; + let removeOrderPayload: { action: string; orderHash: string }; const decimals = constants.ZRX_DECIMALS; const fillableAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(5), decimals); - // createFillableSignedOrderAsync is Promise-based, which forces us + // HACK: createFillableSignedOrderAsync is Promise-based, which forces us // to use Promises instead of the done() callbacks for tests. // onmessage callback must thus be wrapped as a Promise. const _onMessageAsync = async (client: WebSocket.w3cwebsocket) => @@ -88,12 +88,12 @@ describe.only('OrderWatcherWebSocket', async () => { ); orderHash = orderHashUtils.getOrderHashHex(signedOrder); addOrderPayload = { - action: 'addOrderAsync', - params: { signedOrder }, + action: 'ADD_ORDER', + signedOrder, }; removeOrderPayload = { - action: 'removeOrder', - params: { orderHash }, + action: 'REMOVE_ORDER', + orderHash, }; // Prepare OrderWatcher WebSocket server @@ -118,14 +118,13 @@ describe.only('OrderWatcherWebSocket', async () => { it('responds to getStats requests correctly', (done: any) => { const payload = { - action: 'getStats', - params: {}, + action: 'GET_STATS', }; wsClient.onopen = () => wsClient.send(JSON.stringify(payload)); wsClient.onmessage = (msg: any) => { const responseData = JSON.parse(msg.data); - expect(responseData.action).to.be.eq('getStats'); - expect(responseData.success).to.be.eq(1); + expect(responseData.action).to.be.eq('GET_STATS'); + expect(responseData.success).to.be.true(); expect(responseData.result.orderCount).to.be.eq(0); done(); }; @@ -133,23 +132,53 @@ describe.only('OrderWatcherWebSocket', async () => { it('throws an error when an invalid action is attempted', async () => { const invalidActionPayload = { - action: 'badAction', - params: {}, + action: 'BAD_ACTION', }; wsClient.onopen = () => wsClient.send(JSON.stringify(invalidActionPayload)); const errorMsg = await _onMessageAsync(wsClient); const errorData = JSON.parse(errorMsg.data); - expect(errorData.action).to.be.eq('badAction'); - expect(errorData.success).to.be.eq(0); - expect(errorData.result.error).to.be.eq('Error: [Server] Invalid request action: badAction'); + // tslint:disable-next-line:no-unused-expression + expect(errorData.action).to.be.null; + expect(errorData.success).to.be.false(); + expect(errorData.result).to.match(/^Error: Expected request to conform to schema/); }); - it('executes addOrderAsync and removeOrder requests correctly', async () => { + it('throws an error when we try to add an order without a signedOrder', async () => { + const noSignedOrderAddOrderPayload = { + action: 'ADD_ORDER', + orderHash: '0x0', + }; + wsClient.onopen = () => wsClient.send(JSON.stringify(noSignedOrderAddOrderPayload)); + const errorMsg = await _onMessageAsync(wsClient); + const errorData = JSON.parse(errorMsg.data); + // tslint:disable-next-line:no-unused-expression + expect(errorData.action).to.be.null; + expect(errorData.success).to.be.false(); + expect(errorData.result).to.match(/^Error: Expected request to conform to schema/); + }); + + it('throws an error when we try to add a bad signedOrder', async () => { + const invalidAddOrderPayload = { + action: 'ADD_ORDER', + signedOrder: { + makerAddress: '0x0', + }, + }; + wsClient.onopen = () => wsClient.send(JSON.stringify(invalidAddOrderPayload)); + const errorMsg = await _onMessageAsync(wsClient); + const errorData = JSON.parse(errorMsg.data); + // tslint:disable-next-line:no-unused-expression + expect(errorData.action).to.be.null; + expect(errorData.success).to.be.false(); + expect(errorData.result).to.match(/^Error: Expected request to conform to schema/); + }); + + it('executes addOrder and removeOrder requests correctly', async () => { wsClient.onopen = () => wsClient.send(JSON.stringify(addOrderPayload)); const addOrderMsg = await _onMessageAsync(wsClient); const addOrderData = JSON.parse(addOrderMsg.data); - expect(addOrderData.action).to.be.eq('addOrderAsync'); - expect(addOrderData.success).to.be.eq(1); + expect(addOrderData.action).to.be.eq('ADD_ORDER'); + expect(addOrderData.success).to.be.true(); expect((wsServer._orderWatcher as any)._orderByOrderHash).to.deep.include({ [orderHash]: signedOrder, }); @@ -157,8 +186,8 @@ describe.only('OrderWatcherWebSocket', async () => { wsClient.send(JSON.stringify(removeOrderPayload)); const removeOrderMsg = await _onMessageAsync(wsClient); const removeOrderData = JSON.parse(removeOrderMsg.data); - expect(removeOrderData.action).to.be.eq('removeOrder'); - expect(removeOrderData.success).to.be.eq(1); + expect(removeOrderData.action).to.be.eq('REMOVE_ORDER'); + expect(removeOrderData.success).to.be.true(); expect((wsServer._orderWatcher as any)._orderByOrderHash).to.not.deep.include({ [orderHash]: signedOrder, }); @@ -175,8 +204,8 @@ describe.only('OrderWatcherWebSocket', async () => { // Ensure that orderStateInvalid message is received. const orderWatcherUpdateMsg = await _onMessageAsync(wsClient); const orderWatcherUpdateData = JSON.parse(orderWatcherUpdateMsg.data); - expect(orderWatcherUpdateData.action).to.be.eq('orderWatcherUpdate'); - expect(orderWatcherUpdateData.success).to.be.eq(1); + expect(orderWatcherUpdateData.action).to.be.eq('UPDATE'); + expect(orderWatcherUpdateData.success).to.be.true(); const invalidOrderState = orderWatcherUpdateData.result as OrderStateInvalid; expect(invalidOrderState.isValid).to.be.false(); expect(invalidOrderState.orderHash).to.be.eq(orderHash); @@ -198,8 +227,8 @@ describe.only('OrderWatcherWebSocket', async () => { takerAddress, ); const nonZeroMakerFeeOrderPayload = { - action: 'addOrderAsync', - params: { nonZeroMakerFeeSignedOrder }, + action: 'ADD_ORDER', + signedOrder: nonZeroMakerFeeSignedOrder, }; // Set up a second client and have it add the order -- cgit From 6bb2ef923894a3572c3fa824c3bf1a69759eb43a Mon Sep 17 00:00:00 2001 From: kao Date: Sat, 15 Dec 2018 01:23:08 -0800 Subject: Respond to CR --- packages/order-watcher/README.md | 37 +++++--- .../src/order_watcher/order_watcher_websocket.ts | 69 ++++++++------ .../order-watcher/src/schemas/websocket_schemas.ts | 53 ++++++++--- packages/order-watcher/src/types.ts | 50 ++++++++-- .../test/order_watcher_websocket_test.ts | 101 ++++++++++++++------- 5 files changed, 209 insertions(+), 101 deletions(-) (limited to 'packages') diff --git a/packages/order-watcher/README.md b/packages/order-watcher/README.md index 7eae0ae16..aad90a59a 100644 --- a/packages/order-watcher/README.md +++ b/packages/order-watcher/README.md @@ -5,7 +5,7 @@ An order watcher daemon that watches for order validity. #### Read the wiki [article](https://0xproject.com/wiki#0x-OrderWatcher). OrderWatcher also comes with a WebSocket server to provide language-agnostic access -to order watching functionality. We used the [WebSocket Client and Server Implementation for Node](https://www.npmjs.com/package/websocket). +to order watching functionality. We used the [WebSocket Client and Server Implementation for Node](https://www.npmjs.com/package/websocket). The server sends and receives messages that conform to the [JSON RPC specifications](https://www.jsonrpc.org/specification). ## Installation @@ -46,7 +46,7 @@ The first step for making a request is establishing a connection with the server ``` var W3CWebSocket = require('websocket').w3cwebsocket; -wsClient = new WebSocket.w3cwebsocket('ws://127.0.0.1:8080'); +wsClient = new W3CWebSocket('ws://127.0.0.1:8080'); ``` In Python, you could use the [websocket-client library](http://pypi.python.org/pypi/websocket-client/) and run: @@ -56,19 +56,22 @@ from websocket import create_connection wsClient = create_connection("ws://127.0.0.1:8080") ``` -With the connection established, you prepare the payload for your request. The payload is a json object with the following structure: +With the connection established, you prepare the payload for your request. The payload is a json object with a format established by the [JSON RPC specification](https://www.jsonrpc.org/specification): -* For `GET_STATE`, the payload is `{ action: 'GET_STATS }`. -* For `ADD_ORDER`, use `{ action: 'ADD_ORDER', signedOrder: }`. -* For `REMOVE_ORDER`, use `{ action: 'REMOVE_ORDER', orderHash: }`. +* `id`: All requests require you to specify a string as an id. When the server responds to the request, it provides an id as well to allow you to determine which request it is responding to. +* `jsonrpc`: This is always the string `'2.0'`. +* `method`: This specifies the OrderWatcher method you want to call. I.e., `'ADD_ORDER'`, `'REMOVE_ORDER'`, and `'GET_STATS'`. +* `params`: These contain the parameters needed by OrderWatcher to execute the method you called. For `ADD_ORDER`, provide `{ signedOrder: }`. For `REMOVE_ORDER`, provide `{ orderHash: }`. For `GET_STATS`, no parameters are needed, so you may leave this empty. Next, convert the payload to a string and send it through the connection. In Javascript: ``` const addOrderPayload = { - action: 'ADD_ORDER', - signedOrder: , + id: 'order32', + jsonrpc: '2.0', + method: 'ADD_ORDER', + params: { signedOrder: }, }; wsClient.send(JSON.stringify(addOrderPayload)); ``` @@ -78,8 +81,10 @@ In Python: ``` import json remove_order_payload = { - 'action': 'REMOVE_ORDER', - 'orderHash': '0x6edc16bf37fde79f5012088c33784c730e2f103d9ab1caf73060c386ad107b7e', + 'id': 'order33', + 'jsonrpc': '2.0', + 'method': 'REMOVE_ORDER', + 'params': {'orderHash': '0x6edc16bf37fde79f5012088c33784c730e2f103d9ab1caf73060c386ad107b7e'}, } wsClient.send(json.dumps(remove_order_payload)); ``` @@ -87,16 +92,18 @@ wsClient.send(json.dumps(remove_order_payload)); **Response** The server responds to all requests in a similar format. In the data field, you'll find another json object that has been converted into a string. This json object contains the following fields: -* `action`: The action the server is responding to. Eg. `ADD_ORDER`. When order states change the server may also initiate a response. In this case, action will be listed as `UPDATE`. -* `success`: `true` or `false`; Indicates whether the server handled the request without problems. -* `result`: This field varies based on the action. `UPDATE` responses contained the new order state. `GET_STATS` responses contain the current order count. When there are errors, the error messages are stored in here. +* `id`: The id corresponding to the request that the server is responding to. `UPDATE` responses are not based on any requests so the `id` field is `null`. +* `jsonrpc`: Always `'2.0'`. +* `method`: The method the server is responding to. Eg. `ADD_ORDER`. When order states change the server may also initiate a response. In this case, method will be listed as `UPDATE`. +* `result`: This field varies based on the method. `UPDATE` responses contained the new order state. `GET_STATS` responses contain the current order count. When there are errors, this field is `null`. +* `error`: When there is an error executing a request, the error message is listed here. When the server responds successfully, this field is `null`. In Javascript, the responses can be parsed using the `onmessage` callback: ``` wsClient.onmessage = (msg) => { const responseData = JSON.parse(msg.data); - const action = responseData.action + const method = responseData.method }; ``` @@ -104,7 +111,7 @@ In Python, `recv` is a lightweight way to receive a response: ``` result = wsClient.recv() -action = result.action +method = result.method ``` ## Contributing diff --git a/packages/order-watcher/src/order_watcher/order_watcher_websocket.ts b/packages/order-watcher/src/order_watcher/order_watcher_websocket.ts index 806c7c6a5..7a88597ef 100644 --- a/packages/order-watcher/src/order_watcher/order_watcher_websocket.ts +++ b/packages/order-watcher/src/order_watcher/order_watcher_websocket.ts @@ -7,9 +7,10 @@ import * as WebSocket from 'websocket'; import { webSocketRequestSchema, webSocketUtf8MessageSchema } from '../schemas/websocket_schemas'; import { + GetStatsResult, OnOrderStateChangeCallback, - OrderWatcherAction, OrderWatcherConfig, + OrderWatcherMethod, WebSocketRequest, WebSocketResponse, } from '../types'; @@ -18,12 +19,13 @@ import { assert } from '../utils/assert'; import { OrderWatcher } from './order_watcher'; const DEFAULT_HTTP_PORT = 8080; +const JSONRPC_VERSION = '2.0'; // Wraps the OrderWatcher functionality in a WebSocket server. Motivations: // 1) Users can watch orders via non-typescript programs. // 2) Better encapsulation so that users can work export class OrderWatcherWebSocketServer { - public readonly _orderWatcher: OrderWatcher; // public for testing + private readonly _orderWatcher: OrderWatcher; private readonly _httpServer: http.Server; private readonly _connectionStore: Set; private readonly _wsServer: WebSocket.server; @@ -66,7 +68,9 @@ export class OrderWatcherWebSocketServer { httpServer: this._httpServer, // Avoid setting autoAcceptConnections to true as it defeats all // standard cross-origin protection facilities built into the protocol - // and the browser. Also ensures that a request event is emitted by + // and the browser. + // Source: https://www.npmjs.com/package/websocket#server-example + // Also ensures that a request event is emitted by // the server whenever a new WebSocket request is made. autoAcceptConnections: false, }); @@ -76,7 +80,7 @@ export class OrderWatcherWebSocketServer { // machine by the same user. As such, no security checks are in place. const connection: WebSocket.connection = request.accept(null, request.origin); logUtils.log(`${new Date()} [Server] Accepted connection from origin ${request.origin}.`); - connection.on('message', await this._onMessageCallbackAsync.bind(this, connection)); + connection.on('message', this._onMessageCallbackAsync.bind(this, connection)); connection.on('close', this._onCloseCallback.bind(this, connection)); this._connectionStore.add(connection); }); @@ -106,20 +110,25 @@ export class OrderWatcherWebSocketServer { } private async _onMessageCallbackAsync(connection: WebSocket.connection, message: any): Promise { - const response: WebSocketResponse = { - action: null, - success: false, - result: null, - }; + let response: WebSocketResponse; try { assert.doesConformToSchema('message', message, webSocketUtf8MessageSchema); const request: WebSocketRequest = JSON.parse(message.utf8Data); assert.doesConformToSchema('request', request, webSocketRequestSchema); - response.action = request.action; - response.success = true; - response.result = await this._routeRequestAsync(request); + assert.isString(request.jsonrpc, JSONRPC_VERSION); + response = { + id: request.id, + jsonrpc: JSONRPC_VERSION, + method: request.method, + result: await this._routeRequestAsync(request), + }; } catch (err) { - response.result = err.toString(); + response = { + id: null, + jsonrpc: JSONRPC_VERSION, + method: null, + error: err.toString(), + }; } logUtils.log(`${new Date()} [Server] OrderWatcher output: ${JSON.stringify(response)}`); connection.sendUTF(JSON.stringify(response)); @@ -130,29 +139,28 @@ export class OrderWatcherWebSocketServer { logUtils.log(`${new Date()} [Server] Client ${connection.remoteAddress} disconnected.`); } - private async _routeRequestAsync(request: WebSocketRequest): Promise { - logUtils.log(`${new Date()} [Server] Request received: ${request.action}`); - let result = null; - switch (request.action) { - case OrderWatcherAction.AddOrder: { - const signedOrder: SignedOrder = OrderWatcherWebSocketServer._parseSignedOrder(request.signedOrder); + private async _routeRequestAsync(request: WebSocketRequest): Promise { + logUtils.log(`${new Date()} [Server] Request received: ${request.method}`); + switch (request.method) { + case OrderWatcherMethod.AddOrder: { + const signedOrder: SignedOrder = OrderWatcherWebSocketServer._parseSignedOrder( + request.params.signedOrder, + ); await this._orderWatcher.addOrderAsync(signedOrder); break; } - case OrderWatcherAction.RemoveOrder: { - const orderHash = request.orderHash || '_'; - this._orderWatcher.removeOrder(orderHash); + case OrderWatcherMethod.RemoveOrder: { + this._orderWatcher.removeOrder(request.params.orderHash || 'undefined'); break; } - case OrderWatcherAction.GetStats: { - result = this._orderWatcher.getStats(); + case OrderWatcherMethod.GetStats: { + return this._orderWatcher.getStats(); break; } default: - // Should never reach here. Should be caught by JSON schema check. - throw new Error(`[Server] Invalid request action: ${request.action}`); + // Should never reach here. Should be caught by JSON schema check. } - return result; + return undefined; } /** @@ -163,9 +171,10 @@ export class OrderWatcherWebSocketServer { private _broadcastCallback(err: Error | null, orderState?: OrderState): void { this._connectionStore.forEach((connection: WebSocket.connection) => { const response: WebSocketResponse = { - action: OrderWatcherAction.Update, - success: true, - result: orderState || err, + id: null, + jsonrpc: JSONRPC_VERSION, + method: OrderWatcherMethod.Update, + result: orderState, }; connection.sendUTF(JSON.stringify(response)); }); diff --git a/packages/order-watcher/src/schemas/websocket_schemas.ts b/packages/order-watcher/src/schemas/websocket_schemas.ts index c250d12f1..5e4e1ab74 100644 --- a/packages/order-watcher/src/schemas/websocket_schemas.ts +++ b/packages/order-watcher/src/schemas/websocket_schemas.ts @@ -9,24 +9,53 @@ export const webSocketUtf8MessageSchema = { export const webSocketRequestSchema = { id: '/webSocketRequestSchema', - properties: { - action: { enum: ['GET_STATS', 'ADD_ORDER', 'REMOVE_ORDER'] }, - signedOrder: { $ref: '/signedOrderSchema' }, - orderHash: { type: 'string' }, + type: 'object', + definitions: { + signedOrderParam: { + type: 'object', + properties: { + signedOrder: { $ref: '/signedOrderSchema' }, + }, + required: ['signedOrder'], + }, + orderHashParam: { + type: 'object', + properties: { + orderHash: { $ref: '/hexSchema' }, + }, + required: ['orderHash'], + }, }, - anyOf: [ + oneOf: [ { - properties: { action: { enum: ['ADD_ORDER'] } }, - required: ['signedOrder'], + type: 'object', + properties: { + id: { type: 'string' }, + jsonrpc: { type: 'string' }, + method: { enum: ['ADD_ORDER'] }, + params: { $ref: '#/definitions/signedOrderParam' }, + }, + required: ['id', 'jsonrpc', 'method', 'params'], }, { - properties: { action: { enum: ['REMOVE_ORDER'] } }, - required: ['orderHash'], + type: 'object', + properties: { + id: { type: 'string' }, + jsonrpc: { type: 'string' }, + method: { enum: ['REMOVE_ORDER'] }, + params: { $ref: '#/definitions/orderHashParam' }, + }, + required: ['id', 'jsonrpc', 'method', 'params'], }, { - properties: { action: { enum: ['GET_STATS'] } }, - required: [], + type: 'object', + properties: { + id: { type: 'string' }, + jsonrpc: { type: 'string' }, + method: { enum: ['GET_STATS'] }, + params: {}, + }, + required: ['id', 'jsonrpc', 'method'], }, ], - type: 'object', }; diff --git a/packages/order-watcher/src/types.ts b/packages/order-watcher/src/types.ts index 7f6219732..90d383660 100644 --- a/packages/order-watcher/src/types.ts +++ b/packages/order-watcher/src/types.ts @@ -32,8 +32,8 @@ export enum InternalOrderWatcherError { WethNotInTokenRegistry = 'WETH_NOT_IN_TOKEN_REGISTRY', } -export enum OrderWatcherAction { - // Actions initiated by the user. +export enum OrderWatcherMethod { + // Methods initiated by the user. GetStats = 'GET_STATS', AddOrder = 'ADD_ORDER', RemoveOrder = 'REMOVE_ORDER', @@ -46,16 +46,46 @@ export enum OrderWatcherAction { // Users have to create a json object of this format and attach it to // the data field of their WebSocket message to interact with the server. -export interface WebSocketRequest { - action: OrderWatcherAction; - signedOrder?: SignedOrder; - orderHash?: string; +export type WebSocketRequest = AddOrderRequest | RemoveOrderRequest | GetStatsRequest; + +interface AddOrderRequest { + id: string; + jsonrpc: string; + method: OrderWatcherMethod.AddOrder; + params: { signedOrder: SignedOrder }; +} + +interface RemoveOrderRequest { + id: string; + jsonrpc: string; + method: OrderWatcherMethod.RemoveOrder; + params: { orderHash: string }; +} + +interface GetStatsRequest { + id: string; + jsonrpc: string; + method: OrderWatcherMethod.GetStats; } // Users should expect a json object of this format in the data field // of the WebSocket messages that the server sends out. -export interface WebSocketResponse { - action: OrderWatcherAction | null; - success: boolean; - result: any; +export type WebSocketResponse = SuccessfulWebSocketResponse | ErrorWebSocketResponse; + +interface SuccessfulWebSocketResponse { + id: string | null; // id is null for UPDATE + jsonrpc: string; + method: OrderWatcherMethod; + result: OrderState | GetStatsResult | undefined; // result is undefined for ADD_ORDER and REMOVE_ORDER +} + +interface ErrorWebSocketResponse { + id: null; + jsonrpc: string; + method: null; + error: string; +} + +export interface GetStatsResult { + orderCount: number; } diff --git a/packages/order-watcher/test/order_watcher_websocket_test.ts b/packages/order-watcher/test/order_watcher_websocket_test.ts index a9e72ce21..c4d1ede45 100644 --- a/packages/order-watcher/test/order_watcher_websocket_test.ts +++ b/packages/order-watcher/test/order_watcher_websocket_test.ts @@ -41,8 +41,10 @@ describe.only('OrderWatcherWebSocket', async () => { let zrxTokenAddress: string; let signedOrder: SignedOrder; let orderHash: string; - let addOrderPayload: { action: string; signedOrder: SignedOrder }; - let removeOrderPayload: { action: string; orderHash: string }; + // Manually encode types rather than use /src/types to mimick real data that user + // would input. Otherwise we would be forced to use enums, which hide problems. + let addOrderPayload: { id: string; jsonrpc: string; method: string; params: { signedOrder: SignedOrder } }; + let removeOrderPayload: { id: string; jsonrpc: string; method: string; params: { orderHash: string } }; const decimals = constants.ZRX_DECIMALS; const fillableAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(5), decimals); // HACK: createFillableSignedOrderAsync is Promise-based, which forces us @@ -88,12 +90,16 @@ describe.only('OrderWatcherWebSocket', async () => { ); orderHash = orderHashUtils.getOrderHashHex(signedOrder); addOrderPayload = { - action: 'ADD_ORDER', - signedOrder, + id: 'addOrderPayload', + jsonrpc: '2.0', + method: 'ADD_ORDER', + params: { signedOrder }, }; removeOrderPayload = { - action: 'REMOVE_ORDER', - orderHash, + id: 'removeOrderPayload', + jsonrpc: '2.0', + method: 'REMOVE_ORDER', + params: { orderHash }, }; // Prepare OrderWatcher WebSocket server @@ -118,48 +124,75 @@ describe.only('OrderWatcherWebSocket', async () => { it('responds to getStats requests correctly', (done: any) => { const payload = { - action: 'GET_STATS', + id: 'getStats', + jsonrpc: '2.0', + method: 'GET_STATS', }; wsClient.onopen = () => wsClient.send(JSON.stringify(payload)); wsClient.onmessage = (msg: any) => { const responseData = JSON.parse(msg.data); - expect(responseData.action).to.be.eq('GET_STATS'); - expect(responseData.success).to.be.true(); + expect(responseData.id).to.be.eq('getStats'); + expect(responseData.jsonrpc).to.be.eq('2.0'); + expect(responseData.method).to.be.eq('GET_STATS'); expect(responseData.result.orderCount).to.be.eq(0); done(); }; }); - it('throws an error when an invalid action is attempted', async () => { - const invalidActionPayload = { - action: 'BAD_ACTION', + it('throws an error when an invalid method is attempted', async () => { + const invalidMethodPayload = { + id: 'invalidMethodPayload', + jsonrpc: '2.0', + method: 'BAD_METHOD', }; - wsClient.onopen = () => wsClient.send(JSON.stringify(invalidActionPayload)); + wsClient.onopen = () => wsClient.send(JSON.stringify(invalidMethodPayload)); const errorMsg = await _onMessageAsync(wsClient); const errorData = JSON.parse(errorMsg.data); // tslint:disable-next-line:no-unused-expression - expect(errorData.action).to.be.null; - expect(errorData.success).to.be.false(); - expect(errorData.result).to.match(/^Error: Expected request to conform to schema/); + expect(errorData.id).to.be.null; + // tslint:disable-next-line:no-unused-expression + expect(errorData.method).to.be.null; + expect(errorData.jsonrpc).to.be.eq('2.0'); + expect(errorData.error).to.match(/^Error: Expected request to conform to schema/); + }); + + it('throws an error when jsonrpc field missing from request', async () => { + const noJsonRpcPayload = { + id: 'noJsonRpcPayload', + method: 'GET_STATS', + }; + wsClient.onopen = () => wsClient.send(JSON.stringify(noJsonRpcPayload)); + const errorMsg = await _onMessageAsync(wsClient); + const errorData = JSON.parse(errorMsg.data); + // tslint:disable-next-line:no-unused-expression + expect(errorData.method).to.be.null; + expect(errorData.jsonrpc).to.be.eq('2.0'); + expect(errorData.error).to.match(/^Error: Expected request to conform to schema/); }); it('throws an error when we try to add an order without a signedOrder', async () => { const noSignedOrderAddOrderPayload = { - action: 'ADD_ORDER', - orderHash: '0x0', + id: 'noSignedOrderAddOrderPayload', + jsonrpc: '2.0', + method: 'ADD_ORDER', + orderHash: '0x7337e2f2a9aa2ed6afe26edc2df7ad79c3ffa9cf9b81a964f707ea63f5272355', }; wsClient.onopen = () => wsClient.send(JSON.stringify(noSignedOrderAddOrderPayload)); const errorMsg = await _onMessageAsync(wsClient); const errorData = JSON.parse(errorMsg.data); // tslint:disable-next-line:no-unused-expression - expect(errorData.action).to.be.null; - expect(errorData.success).to.be.false(); - expect(errorData.result).to.match(/^Error: Expected request to conform to schema/); + expect(errorData.id).to.be.null; + // tslint:disable-next-line:no-unused-expression + expect(errorData.method).to.be.null; + expect(errorData.jsonrpc).to.be.eq('2.0'); + expect(errorData.error).to.match(/^Error: Expected request to conform to schema/); }); it('throws an error when we try to add a bad signedOrder', async () => { const invalidAddOrderPayload = { - action: 'ADD_ORDER', + id: 'invalidAddOrderPayload', + jsonrpc: '2.0', + method: 'ADD_ORDER', signedOrder: { makerAddress: '0x0', }, @@ -168,27 +201,26 @@ describe.only('OrderWatcherWebSocket', async () => { const errorMsg = await _onMessageAsync(wsClient); const errorData = JSON.parse(errorMsg.data); // tslint:disable-next-line:no-unused-expression - expect(errorData.action).to.be.null; - expect(errorData.success).to.be.false(); - expect(errorData.result).to.match(/^Error: Expected request to conform to schema/); + expect(errorData.id).to.be.null; + // tslint:disable-next-line:no-unused-expression + expect(errorData.method).to.be.null; + expect(errorData.error).to.match(/^Error: Expected request to conform to schema/); }); it('executes addOrder and removeOrder requests correctly', async () => { wsClient.onopen = () => wsClient.send(JSON.stringify(addOrderPayload)); const addOrderMsg = await _onMessageAsync(wsClient); const addOrderData = JSON.parse(addOrderMsg.data); - expect(addOrderData.action).to.be.eq('ADD_ORDER'); - expect(addOrderData.success).to.be.true(); - expect((wsServer._orderWatcher as any)._orderByOrderHash).to.deep.include({ + expect(addOrderData.method).to.be.eq('ADD_ORDER'); + expect((wsServer as any)._orderWatcher._orderByOrderHash).to.deep.include({ [orderHash]: signedOrder, }); wsClient.send(JSON.stringify(removeOrderPayload)); const removeOrderMsg = await _onMessageAsync(wsClient); const removeOrderData = JSON.parse(removeOrderMsg.data); - expect(removeOrderData.action).to.be.eq('REMOVE_ORDER'); - expect(removeOrderData.success).to.be.true(); - expect((wsServer._orderWatcher as any)._orderByOrderHash).to.not.deep.include({ + expect(removeOrderData.method).to.be.eq('REMOVE_ORDER'); + expect((wsServer as any)._orderWatcher._orderByOrderHash).to.not.deep.include({ [orderHash]: signedOrder, }); }); @@ -204,8 +236,7 @@ describe.only('OrderWatcherWebSocket', async () => { // Ensure that orderStateInvalid message is received. const orderWatcherUpdateMsg = await _onMessageAsync(wsClient); const orderWatcherUpdateData = JSON.parse(orderWatcherUpdateMsg.data); - expect(orderWatcherUpdateData.action).to.be.eq('UPDATE'); - expect(orderWatcherUpdateData.success).to.be.true(); + expect(orderWatcherUpdateData.method).to.be.eq('UPDATE'); const invalidOrderState = orderWatcherUpdateData.result as OrderStateInvalid; expect(invalidOrderState.isValid).to.be.false(); expect(invalidOrderState.orderHash).to.be.eq(orderHash); @@ -227,7 +258,9 @@ describe.only('OrderWatcherWebSocket', async () => { takerAddress, ); const nonZeroMakerFeeOrderPayload = { - action: 'ADD_ORDER', + id: 'nonZeroMakerFeeOrderPayload', + jsonrpc: '2.0', + method: 'ADD_ORDER', signedOrder: nonZeroMakerFeeSignedOrder, }; -- cgit From 7cafe396de676cec3859c76d6407a0948a8e398e Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Sat, 15 Dec 2018 21:34:35 -0800 Subject: Ensure fileName matches class name, fix broadcast --- packages/order-watcher/src/index.ts | 1 + .../src/order_watcher/order_watcher_websocket.ts | 182 ------------- .../order_watcher_websocket_server.ts | 186 +++++++++++++ packages/order-watcher/src/types.ts | 8 +- .../test/order_watcher_websocket_server_test.ts | 288 +++++++++++++++++++++ .../test/order_watcher_websocket_test.ts | 288 --------------------- 6 files changed, 482 insertions(+), 471 deletions(-) delete mode 100644 packages/order-watcher/src/order_watcher/order_watcher_websocket.ts create mode 100644 packages/order-watcher/src/order_watcher/order_watcher_websocket_server.ts create mode 100644 packages/order-watcher/test/order_watcher_websocket_server_test.ts delete mode 100644 packages/order-watcher/test/order_watcher_websocket_test.ts (limited to 'packages') diff --git a/packages/order-watcher/src/index.ts b/packages/order-watcher/src/index.ts index 5eeba3e87..5bdef4504 100644 --- a/packages/order-watcher/src/index.ts +++ b/packages/order-watcher/src/index.ts @@ -1,4 +1,5 @@ export { OrderWatcher } from './order_watcher/order_watcher'; +export { OrderWatcherWebSocketServer } from './order_watcher/order_watcher_websocket_server'; export { ExpirationWatcher } from './order_watcher/expiration_watcher'; export { diff --git a/packages/order-watcher/src/order_watcher/order_watcher_websocket.ts b/packages/order-watcher/src/order_watcher/order_watcher_websocket.ts deleted file mode 100644 index 7a88597ef..000000000 --- a/packages/order-watcher/src/order_watcher/order_watcher_websocket.ts +++ /dev/null @@ -1,182 +0,0 @@ -import { ContractAddresses } from '@0x/contract-addresses'; -import { OrderState, SignedOrder } from '@0x/types'; -import { BigNumber, logUtils } from '@0x/utils'; -import { Provider } from 'ethereum-types'; -import * as http from 'http'; -import * as WebSocket from 'websocket'; - -import { webSocketRequestSchema, webSocketUtf8MessageSchema } from '../schemas/websocket_schemas'; -import { - GetStatsResult, - OnOrderStateChangeCallback, - OrderWatcherConfig, - OrderWatcherMethod, - WebSocketRequest, - WebSocketResponse, -} from '../types'; -import { assert } from '../utils/assert'; - -import { OrderWatcher } from './order_watcher'; - -const DEFAULT_HTTP_PORT = 8080; -const JSONRPC_VERSION = '2.0'; - -// Wraps the OrderWatcher functionality in a WebSocket server. Motivations: -// 1) Users can watch orders via non-typescript programs. -// 2) Better encapsulation so that users can work -export class OrderWatcherWebSocketServer { - private readonly _orderWatcher: OrderWatcher; - private readonly _httpServer: http.Server; - private readonly _connectionStore: Set; - private readonly _wsServer: WebSocket.server; - /** - * Recover types lost when the payload is stringified. - */ - private static _parseSignedOrder(rawRequest: any): SignedOrder { - const bigNumberFields = [ - 'salt', - 'makerFee', - 'takerFee', - 'makerAssetAmount', - 'takerAssetAmount', - 'expirationTimeSeconds', - ]; - for (const field of bigNumberFields) { - rawRequest[field] = new BigNumber(rawRequest[field]); - } - return rawRequest; - } - - /** - * Instantiate a new WebSocket server which provides OrderWatcher functionality - * @param provider Web3 provider to use for JSON RPC calls. - * @param networkId NetworkId to watch orders on. - * @param contractAddresses Optional contract addresses. Defaults to known - * addresses based on networkId. - * @param partialConfig Optional configurations. - */ - constructor( - provider: Provider, - networkId: number, - contractAddresses?: ContractAddresses, - partialConfig?: Partial, - ) { - this._orderWatcher = new OrderWatcher(provider, networkId, contractAddresses, partialConfig); - this._connectionStore = new Set(); - this._httpServer = http.createServer(); - this._wsServer = new WebSocket.server({ - httpServer: this._httpServer, - // Avoid setting autoAcceptConnections to true as it defeats all - // standard cross-origin protection facilities built into the protocol - // and the browser. - // Source: https://www.npmjs.com/package/websocket#server-example - // Also ensures that a request event is emitted by - // the server whenever a new WebSocket request is made. - autoAcceptConnections: false, - }); - - this._wsServer.on('request', async (request: any) => { - // Designed for usage pattern where client and server are run on the same - // machine by the same user. As such, no security checks are in place. - const connection: WebSocket.connection = request.accept(null, request.origin); - logUtils.log(`${new Date()} [Server] Accepted connection from origin ${request.origin}.`); - connection.on('message', this._onMessageCallbackAsync.bind(this, connection)); - connection.on('close', this._onCloseCallback.bind(this, connection)); - this._connectionStore.add(connection); - }); - - // Have the WebSocket server subscribe to the OrderWatcher to receive updates. - // These updates are then broadcast to clients in the _connectionStore. - const broadcastCallback: OnOrderStateChangeCallback = this._broadcastCallback.bind(this); - this._orderWatcher.subscribe(broadcastCallback); - } - - /** - * Activates the WebSocket server by having its HTTP server start listening. - */ - public listen(): void { - const port = process.env.ORDER_WATCHER_HTTP_PORT || DEFAULT_HTTP_PORT; - this._httpServer.listen(port, () => { - logUtils.log(`${new Date()} [Server] Listening on port ${port}`); - }); - } - - /** - * Deactivates the WebSocket server by stopping the HTTP server from accepting - * new connections. - */ - public close(): void { - this._httpServer.close(); - } - - private async _onMessageCallbackAsync(connection: WebSocket.connection, message: any): Promise { - let response: WebSocketResponse; - try { - assert.doesConformToSchema('message', message, webSocketUtf8MessageSchema); - const request: WebSocketRequest = JSON.parse(message.utf8Data); - assert.doesConformToSchema('request', request, webSocketRequestSchema); - assert.isString(request.jsonrpc, JSONRPC_VERSION); - response = { - id: request.id, - jsonrpc: JSONRPC_VERSION, - method: request.method, - result: await this._routeRequestAsync(request), - }; - } catch (err) { - response = { - id: null, - jsonrpc: JSONRPC_VERSION, - method: null, - error: err.toString(), - }; - } - logUtils.log(`${new Date()} [Server] OrderWatcher output: ${JSON.stringify(response)}`); - connection.sendUTF(JSON.stringify(response)); - } - - private _onCloseCallback(connection: WebSocket.connection): void { - this._connectionStore.delete(connection); - logUtils.log(`${new Date()} [Server] Client ${connection.remoteAddress} disconnected.`); - } - - private async _routeRequestAsync(request: WebSocketRequest): Promise { - logUtils.log(`${new Date()} [Server] Request received: ${request.method}`); - switch (request.method) { - case OrderWatcherMethod.AddOrder: { - const signedOrder: SignedOrder = OrderWatcherWebSocketServer._parseSignedOrder( - request.params.signedOrder, - ); - await this._orderWatcher.addOrderAsync(signedOrder); - break; - } - case OrderWatcherMethod.RemoveOrder: { - this._orderWatcher.removeOrder(request.params.orderHash || 'undefined'); - break; - } - case OrderWatcherMethod.GetStats: { - return this._orderWatcher.getStats(); - break; - } - default: - // Should never reach here. Should be caught by JSON schema check. - } - return undefined; - } - - /** - * Broadcasts OrderState changes to ALL connected clients. At the moment, - * we do not support clients subscribing to only a subset of orders. As such, - * Client B will be notified of changes to an order that Client A added. - */ - private _broadcastCallback(err: Error | null, orderState?: OrderState): void { - this._connectionStore.forEach((connection: WebSocket.connection) => { - const response: WebSocketResponse = { - id: null, - jsonrpc: JSONRPC_VERSION, - method: OrderWatcherMethod.Update, - result: orderState, - }; - connection.sendUTF(JSON.stringify(response)); - }); - } -} diff --git a/packages/order-watcher/src/order_watcher/order_watcher_websocket_server.ts b/packages/order-watcher/src/order_watcher/order_watcher_websocket_server.ts new file mode 100644 index 000000000..2d2d9e82e --- /dev/null +++ b/packages/order-watcher/src/order_watcher/order_watcher_websocket_server.ts @@ -0,0 +1,186 @@ +import { ContractAddresses } from '@0x/contract-addresses'; +import { OrderStateInvalid, OrderStateValid, SignedOrder } from '@0x/types'; +import { BigNumber, logUtils } from '@0x/utils'; +import { Provider } from 'ethereum-types'; +import * as http from 'http'; +import * as WebSocket from 'websocket'; + +import { webSocketRequestSchema, webSocketUtf8MessageSchema } from '../schemas/websocket_schemas'; +import { GetStatsResult, OrderWatcherConfig, OrderWatcherMethod, WebSocketRequest, WebSocketResponse } from '../types'; +import { assert } from '../utils/assert'; + +import { OrderWatcher } from './order_watcher'; + +const DEFAULT_HTTP_PORT = 8080; +const JSONRPC_VERSION = '2.0'; + +// Wraps the OrderWatcher functionality in a WebSocket server. Motivations: +// 1) Users can watch orders via non-typescript programs. +// 2) Better encapsulation so that users can work +export class OrderWatcherWebSocketServer { + private readonly _orderWatcher: OrderWatcher; + private readonly _httpServer: http.Server; + private readonly _connectionStore: Set; + private readonly _wsServer: WebSocket.server; + private _jsonRpcRequestId: number; + /** + * Recover types lost when the payload is stringified. + */ + private static _parseSignedOrder(rawRequest: any): SignedOrder { + const bigNumberFields = [ + 'salt', + 'makerFee', + 'takerFee', + 'makerAssetAmount', + 'takerAssetAmount', + 'expirationTimeSeconds', + ]; + for (const field of bigNumberFields) { + rawRequest[field] = new BigNumber(rawRequest[field]); + } + return rawRequest; + } + + /** + * Instantiate a new WebSocket server which provides OrderWatcher functionality + * @param provider Web3 provider to use for JSON RPC calls. + * @param networkId NetworkId to watch orders on. + * @param contractAddresses Optional contract addresses. Defaults to known + * addresses based on networkId. + * @param partialConfig Optional configurations. + */ + constructor( + provider: Provider, + networkId: number, + contractAddresses?: ContractAddresses, + partialConfig?: Partial, + ) { + this._jsonRpcRequestId = 1; + this._orderWatcher = new OrderWatcher(provider, networkId, contractAddresses, partialConfig); + this._connectionStore = new Set(); + this._httpServer = http.createServer(); + this._wsServer = new WebSocket.server({ + httpServer: this._httpServer, + // Avoid setting autoAcceptConnections to true as it defeats all + // standard cross-origin protection facilities built into the protocol + // and the browser. + // Source: https://www.npmjs.com/package/websocket#server-example + // Also ensures that a request event is emitted by + // the server whenever a new WebSocket request is made. + autoAcceptConnections: false, + }); + + this._wsServer.on('request', async (request: any) => { + // Designed for usage pattern where client and server are run on the same + // machine by the same user. As such, no security checks are in place. + const connection: WebSocket.connection = request.accept(null, request.origin); + logUtils.log(`${new Date()} [Server] Accepted connection from origin ${request.origin}.`); + connection.on('message', this._onMessageCallbackAsync.bind(this, connection)); + connection.on('close', this._onCloseCallback.bind(this, connection)); + this._connectionStore.add(connection); + }); + + // Have the WebSocket server subscribe to the OrderWatcher to receive updates. + // These updates are then broadcast to clients in the _connectionStore. + this._orderWatcher.subscribe(this._broadcastCallback.bind(this)); + } + + /** + * Activates the WebSocket server by having its HTTP server start listening. + */ + public listen(): void { + const port = process.env.ORDER_WATCHER_HTTP_PORT || DEFAULT_HTTP_PORT; + this._httpServer.listen(port, () => { + logUtils.log(`${new Date()} [Server] Listening on port ${port}`); + }); + } + + /** + * Deactivates the WebSocket server by stopping the HTTP server from accepting + * new connections. + */ + public close(): void { + this._httpServer.close(); + } + + private async _onMessageCallbackAsync(connection: WebSocket.connection, message: any): Promise { + let response: WebSocketResponse; + try { + assert.doesConformToSchema('message', message, webSocketUtf8MessageSchema); + const request: WebSocketRequest = JSON.parse(message.utf8Data); + assert.doesConformToSchema('request', request, webSocketRequestSchema); + assert.isString(request.jsonrpc, JSONRPC_VERSION); + response = { + id: request.id, + jsonrpc: JSONRPC_VERSION, + method: request.method, + result: await this._routeRequestAsync(request), + }; + } catch (err) { + response = { + id: null, + jsonrpc: JSONRPC_VERSION, + method: null, + error: err.toString(), + }; + } + logUtils.log(`${new Date()} [Server] OrderWatcher output: ${JSON.stringify(response)}`); + connection.sendUTF(JSON.stringify(response)); + } + + private _onCloseCallback(connection: WebSocket.connection): void { + this._connectionStore.delete(connection); + logUtils.log(`${new Date()} [Server] Client ${connection.remoteAddress} disconnected.`); + } + + private async _routeRequestAsync(request: WebSocketRequest): Promise { + logUtils.log(`${new Date()} [Server] Request received: ${request.method}`); + switch (request.method) { + case OrderWatcherMethod.AddOrder: { + const signedOrder: SignedOrder = OrderWatcherWebSocketServer._parseSignedOrder( + request.params.signedOrder, + ); + await this._orderWatcher.addOrderAsync(signedOrder); + break; + } + case OrderWatcherMethod.RemoveOrder: { + this._orderWatcher.removeOrder(request.params.orderHash || 'undefined'); + break; + } + case OrderWatcherMethod.GetStats: { + return this._orderWatcher.getStats(); + break; + } + default: + // Should never reach here. Should be caught by JSON schema check. + } + return undefined; + } + + /** + * Broadcasts OrderState changes to ALL connected clients. At the moment, + * we do not support clients subscribing to only a subset of orders. As such, + * Client B will be notified of changes to an order that Client A added. + */ + private _broadcastCallback(err: Error | null, orderState?: OrderStateValid | OrderStateInvalid | undefined): void { + const method = OrderWatcherMethod.Update; + const response = + err === null + ? { + jsonrpc: JSONRPC_VERSION, + method, + result: orderState, + } + : { + jsonrpc: JSONRPC_VERSION, + method, + error: { + code: -32000, + message: err.message, + }, + }; + this._connectionStore.forEach((connection: WebSocket.connection) => { + connection.sendUTF(JSON.stringify(response)); + }); + } +} diff --git a/packages/order-watcher/src/types.ts b/packages/order-watcher/src/types.ts index 90d383660..ecbebe305 100644 --- a/packages/order-watcher/src/types.ts +++ b/packages/order-watcher/src/types.ts @@ -83,7 +83,13 @@ interface ErrorWebSocketResponse { id: null; jsonrpc: string; method: null; - error: string; + error: JSONRPCError; +} + +interface JSONRPCError { + code: number; + message: string; + data?: string | object; } export interface GetStatsResult { diff --git a/packages/order-watcher/test/order_watcher_websocket_server_test.ts b/packages/order-watcher/test/order_watcher_websocket_server_test.ts new file mode 100644 index 000000000..9f9db7b1f --- /dev/null +++ b/packages/order-watcher/test/order_watcher_websocket_server_test.ts @@ -0,0 +1,288 @@ +import { ContractWrappers } from '@0x/contract-wrappers'; +import { tokenUtils } from '@0x/contract-wrappers/lib/test/utils/token_utils'; +import { BlockchainLifecycle } from '@0x/dev-utils'; +import { FillScenarios } from '@0x/fill-scenarios'; +import { assetDataUtils, orderHashUtils } from '@0x/order-utils'; +import { ExchangeContractErrs, OrderStateInvalid, OrderStateValid, SignedOrder } from '@0x/types'; +import { BigNumber, logUtils } from '@0x/utils'; +import { Web3Wrapper } from '@0x/web3-wrapper'; +import * as chai from 'chai'; +import 'mocha'; +import * as WebSocket from 'websocket'; + +import { OrderWatcherWebSocketServer } from '../src/order_watcher/order_watcher_websocket_server'; + +import { chaiSetup } from './utils/chai_setup'; +import { constants } from './utils/constants'; +import { migrateOnceAsync } from './utils/migrate'; +import { provider, web3Wrapper } from './utils/web3_wrapper'; + +chaiSetup.configure(); +const expect = chai.expect; +const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); + +interface WsMessage { + data: string; +} + +describe.only('OrderWatcherWebSocketServer', async () => { + let contractWrappers: ContractWrappers; + let wsServer: OrderWatcherWebSocketServer; + let wsClient: WebSocket.w3cwebsocket; + let wsClientTwo: WebSocket.w3cwebsocket; + let fillScenarios: FillScenarios; + let userAddresses: string[]; + let makerAssetData: string; + let takerAssetData: string; + let makerTokenAddress: string; + let takerTokenAddress: string; + let makerAddress: string; + let takerAddress: string; + let zrxTokenAddress: string; + let signedOrder: SignedOrder; + let orderHash: string; + // Manually encode types rather than use /src/types to mimick real data that user + // would input. Otherwise we would be forced to use enums, which hide problems. + let addOrderPayload: { id: string; jsonrpc: string; method: string; params: { signedOrder: SignedOrder } }; + let removeOrderPayload: { id: string; jsonrpc: string; method: string; params: { orderHash: string } }; + const decimals = constants.ZRX_DECIMALS; + const fillableAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(5), decimals); + // HACK: createFillableSignedOrderAsync is Promise-based, which forces us + // to use Promises instead of the done() callbacks for tests. + // onmessage callback must thus be wrapped as a Promise. + const _onMessageAsync = async (client: WebSocket.w3cwebsocket) => + new Promise(resolve => { + client.onmessage = (msg: WsMessage) => resolve(msg); + }); + + before(async () => { + // Set up constants + const contractAddresses = await migrateOnceAsync(); + await blockchainLifecycle.startAsync(); + const networkId = constants.TESTRPC_NETWORK_ID; + const config = { + networkId, + contractAddresses, + }; + contractWrappers = new ContractWrappers(provider, config); + userAddresses = await web3Wrapper.getAvailableAddressesAsync(); + zrxTokenAddress = contractAddresses.zrxToken; + [makerAddress, takerAddress] = userAddresses; + [makerTokenAddress, takerTokenAddress] = tokenUtils.getDummyERC20TokenAddresses(); + [makerAssetData, takerAssetData] = [ + assetDataUtils.encodeERC20AssetData(makerTokenAddress), + assetDataUtils.encodeERC20AssetData(takerTokenAddress), + ]; + fillScenarios = new FillScenarios( + provider, + userAddresses, + zrxTokenAddress, + contractAddresses.exchange, + contractAddresses.erc20Proxy, + contractAddresses.erc721Proxy, + ); + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerAssetData, + takerAssetData, + makerAddress, + takerAddress, + fillableAmount, + ); + orderHash = orderHashUtils.getOrderHashHex(signedOrder); + addOrderPayload = { + id: 'addOrderPayload', + jsonrpc: '2.0', + method: 'ADD_ORDER', + params: { signedOrder }, + }; + removeOrderPayload = { + id: 'removeOrderPayload', + jsonrpc: '2.0', + method: 'REMOVE_ORDER', + params: { orderHash }, + }; + + // Prepare OrderWatcher WebSocket server + const orderWatcherConfig = {}; + wsServer = new OrderWatcherWebSocketServer(provider, networkId, contractAddresses, orderWatcherConfig); + wsServer.listen(); + }); + after(async () => { + await blockchainLifecycle.revertAsync(); + wsServer.close(); + }); + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + wsClient = new WebSocket.w3cwebsocket('ws://127.0.0.1:8080/'); + logUtils.log(`${new Date()} [Client] Connected.`); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + wsClient.close(); + logUtils.log(`${new Date()} [Client] Closed.`); + }); + + it('responds to getStats requests correctly', (done: any) => { + const payload = { + id: 'getStats', + jsonrpc: '2.0', + method: 'GET_STATS', + }; + wsClient.onopen = () => wsClient.send(JSON.stringify(payload)); + wsClient.onmessage = (msg: any) => { + const responseData = JSON.parse(msg.data); + expect(responseData.id).to.be.eq('getStats'); + expect(responseData.jsonrpc).to.be.eq('2.0'); + expect(responseData.method).to.be.eq('GET_STATS'); + expect(responseData.result.orderCount).to.be.eq(0); + done(); + }; + }); + + it('throws an error when an invalid method is attempted', async () => { + const invalidMethodPayload = { + id: 'invalidMethodPayload', + jsonrpc: '2.0', + method: 'BAD_METHOD', + }; + wsClient.onopen = () => wsClient.send(JSON.stringify(invalidMethodPayload)); + const errorMsg = await _onMessageAsync(wsClient); + const errorData = JSON.parse(errorMsg.data); + // tslint:disable-next-line:no-unused-expression + expect(errorData.id).to.be.null; + // tslint:disable-next-line:no-unused-expression + expect(errorData.method).to.be.null; + expect(errorData.jsonrpc).to.be.eq('2.0'); + expect(errorData.error).to.match(/^Error: Expected request to conform to schema/); + }); + + it('throws an error when jsonrpc field missing from request', async () => { + const noJsonRpcPayload = { + id: 'noJsonRpcPayload', + method: 'GET_STATS', + }; + wsClient.onopen = () => wsClient.send(JSON.stringify(noJsonRpcPayload)); + const errorMsg = await _onMessageAsync(wsClient); + const errorData = JSON.parse(errorMsg.data); + // tslint:disable-next-line:no-unused-expression + expect(errorData.method).to.be.null; + expect(errorData.jsonrpc).to.be.eq('2.0'); + expect(errorData.error).to.match(/^Error: Expected request to conform to schema/); + }); + + it('throws an error when we try to add an order without a signedOrder', async () => { + const noSignedOrderAddOrderPayload = { + id: 'noSignedOrderAddOrderPayload', + jsonrpc: '2.0', + method: 'ADD_ORDER', + orderHash: '0x7337e2f2a9aa2ed6afe26edc2df7ad79c3ffa9cf9b81a964f707ea63f5272355', + }; + wsClient.onopen = () => wsClient.send(JSON.stringify(noSignedOrderAddOrderPayload)); + const errorMsg = await _onMessageAsync(wsClient); + const errorData = JSON.parse(errorMsg.data); + // tslint:disable-next-line:no-unused-expression + expect(errorData.id).to.be.null; + // tslint:disable-next-line:no-unused-expression + expect(errorData.method).to.be.null; + expect(errorData.jsonrpc).to.be.eq('2.0'); + expect(errorData.error).to.match(/^Error: Expected request to conform to schema/); + }); + + it('throws an error when we try to add a bad signedOrder', async () => { + const invalidAddOrderPayload = { + id: 'invalidAddOrderPayload', + jsonrpc: '2.0', + method: 'ADD_ORDER', + signedOrder: { + makerAddress: '0x0', + }, + }; + wsClient.onopen = () => wsClient.send(JSON.stringify(invalidAddOrderPayload)); + const errorMsg = await _onMessageAsync(wsClient); + const errorData = JSON.parse(errorMsg.data); + // tslint:disable-next-line:no-unused-expression + expect(errorData.id).to.be.null; + // tslint:disable-next-line:no-unused-expression + expect(errorData.method).to.be.null; + expect(errorData.error).to.match(/^Error: Expected request to conform to schema/); + }); + + it('executes addOrder and removeOrder requests correctly', async () => { + wsClient.onopen = () => wsClient.send(JSON.stringify(addOrderPayload)); + const addOrderMsg = await _onMessageAsync(wsClient); + const addOrderData = JSON.parse(addOrderMsg.data); + expect(addOrderData.method).to.be.eq('ADD_ORDER'); + expect((wsServer as any)._orderWatcher._orderByOrderHash).to.deep.include({ + [orderHash]: signedOrder, + }); + + wsClient.send(JSON.stringify(removeOrderPayload)); + const removeOrderMsg = await _onMessageAsync(wsClient); + const removeOrderData = JSON.parse(removeOrderMsg.data); + expect(removeOrderData.method).to.be.eq('REMOVE_ORDER'); + expect((wsServer as any)._orderWatcher._orderByOrderHash).to.not.deep.include({ + [orderHash]: signedOrder, + }); + }); + + it('broadcasts orderStateInvalid message when makerAddress allowance set to 0 for watched order', async () => { + // Add the regular order + wsClient.onopen = () => wsClient.send(JSON.stringify(addOrderPayload)); + await _onMessageAsync(wsClient); + + // Set the allowance to 0 + await contractWrappers.erc20Token.setProxyAllowanceAsync(makerTokenAddress, makerAddress, new BigNumber(0)); + + // Ensure that orderStateInvalid message is received. + const orderWatcherUpdateMsg = await _onMessageAsync(wsClient); + const orderWatcherUpdateData = JSON.parse(orderWatcherUpdateMsg.data); + expect(orderWatcherUpdateData.method).to.be.eq('UPDATE'); + const invalidOrderState = orderWatcherUpdateData.result as OrderStateInvalid; + expect(invalidOrderState.isValid).to.be.false(); + expect(invalidOrderState.orderHash).to.be.eq(orderHash); + expect(invalidOrderState.error).to.be.eq(ExchangeContractErrs.InsufficientMakerAllowance); + }); + + it('broadcasts to multiple clients when an order backing ZRX allowance changes', async () => { + // Prepare order + const makerFee = Web3Wrapper.toBaseUnitAmount(new BigNumber(2), decimals); + const takerFee = Web3Wrapper.toBaseUnitAmount(new BigNumber(0), decimals); + const nonZeroMakerFeeSignedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync( + makerAssetData, + takerAssetData, + makerFee, + takerFee, + makerAddress, + takerAddress, + fillableAmount, + takerAddress, + ); + const nonZeroMakerFeeOrderPayload = { + id: 'nonZeroMakerFeeOrderPayload', + jsonrpc: '2.0', + method: 'ADD_ORDER', + signedOrder: nonZeroMakerFeeSignedOrder, + }; + + // Set up a second client and have it add the order + wsClientTwo = new WebSocket.w3cwebsocket('ws://127.0.0.1:8080/'); + logUtils.log(`${new Date()} [Client] Connected.`); + wsClientTwo.onopen = () => wsClientTwo.send(JSON.stringify(nonZeroMakerFeeOrderPayload)); + await _onMessageAsync(wsClientTwo); + + // Change the allowance + await contractWrappers.erc20Token.setProxyAllowanceAsync(zrxTokenAddress, makerAddress, new BigNumber(0)); + + // Check that both clients receive the emitted event + for (const client of [wsClient, wsClientTwo]) { + const updateMsg = await _onMessageAsync(client); + const updateData = JSON.parse(updateMsg.data); + const orderState = updateData.result as OrderStateValid; + expect(orderState.isValid).to.be.true(); + expect(orderState.orderRelevantState.makerFeeProxyAllowance).to.be.eq('0'); + } + + wsClientTwo.close(); + logUtils.log(`${new Date()} [Client] Closed.`); + }); +}); diff --git a/packages/order-watcher/test/order_watcher_websocket_test.ts b/packages/order-watcher/test/order_watcher_websocket_test.ts deleted file mode 100644 index c4d1ede45..000000000 --- a/packages/order-watcher/test/order_watcher_websocket_test.ts +++ /dev/null @@ -1,288 +0,0 @@ -import { ContractWrappers } from '@0x/contract-wrappers'; -import { tokenUtils } from '@0x/contract-wrappers/lib/test/utils/token_utils'; -import { BlockchainLifecycle } from '@0x/dev-utils'; -import { FillScenarios } from '@0x/fill-scenarios'; -import { assetDataUtils, orderHashUtils } from '@0x/order-utils'; -import { ExchangeContractErrs, OrderStateInvalid, OrderStateValid, SignedOrder } from '@0x/types'; -import { BigNumber, logUtils } from '@0x/utils'; -import { Web3Wrapper } from '@0x/web3-wrapper'; -import * as chai from 'chai'; -import 'mocha'; -import * as WebSocket from 'websocket'; - -import { OrderWatcherWebSocketServer } from '../src/order_watcher/order_watcher_websocket'; - -import { chaiSetup } from './utils/chai_setup'; -import { constants } from './utils/constants'; -import { migrateOnceAsync } from './utils/migrate'; -import { provider, web3Wrapper } from './utils/web3_wrapper'; - -chaiSetup.configure(); -const expect = chai.expect; -const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); - -interface WsMessage { - data: string; -} - -describe.only('OrderWatcherWebSocket', async () => { - let contractWrappers: ContractWrappers; - let wsServer: OrderWatcherWebSocketServer; - let wsClient: WebSocket.w3cwebsocket; - let wsClientTwo: WebSocket.w3cwebsocket; - let fillScenarios: FillScenarios; - let userAddresses: string[]; - let makerAssetData: string; - let takerAssetData: string; - let makerTokenAddress: string; - let takerTokenAddress: string; - let makerAddress: string; - let takerAddress: string; - let zrxTokenAddress: string; - let signedOrder: SignedOrder; - let orderHash: string; - // Manually encode types rather than use /src/types to mimick real data that user - // would input. Otherwise we would be forced to use enums, which hide problems. - let addOrderPayload: { id: string; jsonrpc: string; method: string; params: { signedOrder: SignedOrder } }; - let removeOrderPayload: { id: string; jsonrpc: string; method: string; params: { orderHash: string } }; - const decimals = constants.ZRX_DECIMALS; - const fillableAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(5), decimals); - // HACK: createFillableSignedOrderAsync is Promise-based, which forces us - // to use Promises instead of the done() callbacks for tests. - // onmessage callback must thus be wrapped as a Promise. - const _onMessageAsync = async (client: WebSocket.w3cwebsocket) => - new Promise(resolve => { - client.onmessage = (msg: WsMessage) => resolve(msg); - }); - - before(async () => { - // Set up constants - const contractAddresses = await migrateOnceAsync(); - await blockchainLifecycle.startAsync(); - const networkId = constants.TESTRPC_NETWORK_ID; - const config = { - networkId, - contractAddresses, - }; - contractWrappers = new ContractWrappers(provider, config); - userAddresses = await web3Wrapper.getAvailableAddressesAsync(); - zrxTokenAddress = contractAddresses.zrxToken; - [makerAddress, takerAddress] = userAddresses; - [makerTokenAddress, takerTokenAddress] = tokenUtils.getDummyERC20TokenAddresses(); - [makerAssetData, takerAssetData] = [ - assetDataUtils.encodeERC20AssetData(makerTokenAddress), - assetDataUtils.encodeERC20AssetData(takerTokenAddress), - ]; - fillScenarios = new FillScenarios( - provider, - userAddresses, - zrxTokenAddress, - contractAddresses.exchange, - contractAddresses.erc20Proxy, - contractAddresses.erc721Proxy, - ); - signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerAssetData, - takerAssetData, - makerAddress, - takerAddress, - fillableAmount, - ); - orderHash = orderHashUtils.getOrderHashHex(signedOrder); - addOrderPayload = { - id: 'addOrderPayload', - jsonrpc: '2.0', - method: 'ADD_ORDER', - params: { signedOrder }, - }; - removeOrderPayload = { - id: 'removeOrderPayload', - jsonrpc: '2.0', - method: 'REMOVE_ORDER', - params: { orderHash }, - }; - - // Prepare OrderWatcher WebSocket server - const orderWatcherConfig = {}; - wsServer = new OrderWatcherWebSocketServer(provider, networkId, contractAddresses, orderWatcherConfig); - wsServer.listen(); - }); - after(async () => { - await blockchainLifecycle.revertAsync(); - wsServer.close(); - }); - beforeEach(async () => { - await blockchainLifecycle.startAsync(); - wsClient = new WebSocket.w3cwebsocket('ws://127.0.0.1:8080/'); - logUtils.log(`${new Date()} [Client] Connected.`); - }); - afterEach(async () => { - await blockchainLifecycle.revertAsync(); - wsClient.close(); - logUtils.log(`${new Date()} [Client] Closed.`); - }); - - it('responds to getStats requests correctly', (done: any) => { - const payload = { - id: 'getStats', - jsonrpc: '2.0', - method: 'GET_STATS', - }; - wsClient.onopen = () => wsClient.send(JSON.stringify(payload)); - wsClient.onmessage = (msg: any) => { - const responseData = JSON.parse(msg.data); - expect(responseData.id).to.be.eq('getStats'); - expect(responseData.jsonrpc).to.be.eq('2.0'); - expect(responseData.method).to.be.eq('GET_STATS'); - expect(responseData.result.orderCount).to.be.eq(0); - done(); - }; - }); - - it('throws an error when an invalid method is attempted', async () => { - const invalidMethodPayload = { - id: 'invalidMethodPayload', - jsonrpc: '2.0', - method: 'BAD_METHOD', - }; - wsClient.onopen = () => wsClient.send(JSON.stringify(invalidMethodPayload)); - const errorMsg = await _onMessageAsync(wsClient); - const errorData = JSON.parse(errorMsg.data); - // tslint:disable-next-line:no-unused-expression - expect(errorData.id).to.be.null; - // tslint:disable-next-line:no-unused-expression - expect(errorData.method).to.be.null; - expect(errorData.jsonrpc).to.be.eq('2.0'); - expect(errorData.error).to.match(/^Error: Expected request to conform to schema/); - }); - - it('throws an error when jsonrpc field missing from request', async () => { - const noJsonRpcPayload = { - id: 'noJsonRpcPayload', - method: 'GET_STATS', - }; - wsClient.onopen = () => wsClient.send(JSON.stringify(noJsonRpcPayload)); - const errorMsg = await _onMessageAsync(wsClient); - const errorData = JSON.parse(errorMsg.data); - // tslint:disable-next-line:no-unused-expression - expect(errorData.method).to.be.null; - expect(errorData.jsonrpc).to.be.eq('2.0'); - expect(errorData.error).to.match(/^Error: Expected request to conform to schema/); - }); - - it('throws an error when we try to add an order without a signedOrder', async () => { - const noSignedOrderAddOrderPayload = { - id: 'noSignedOrderAddOrderPayload', - jsonrpc: '2.0', - method: 'ADD_ORDER', - orderHash: '0x7337e2f2a9aa2ed6afe26edc2df7ad79c3ffa9cf9b81a964f707ea63f5272355', - }; - wsClient.onopen = () => wsClient.send(JSON.stringify(noSignedOrderAddOrderPayload)); - const errorMsg = await _onMessageAsync(wsClient); - const errorData = JSON.parse(errorMsg.data); - // tslint:disable-next-line:no-unused-expression - expect(errorData.id).to.be.null; - // tslint:disable-next-line:no-unused-expression - expect(errorData.method).to.be.null; - expect(errorData.jsonrpc).to.be.eq('2.0'); - expect(errorData.error).to.match(/^Error: Expected request to conform to schema/); - }); - - it('throws an error when we try to add a bad signedOrder', async () => { - const invalidAddOrderPayload = { - id: 'invalidAddOrderPayload', - jsonrpc: '2.0', - method: 'ADD_ORDER', - signedOrder: { - makerAddress: '0x0', - }, - }; - wsClient.onopen = () => wsClient.send(JSON.stringify(invalidAddOrderPayload)); - const errorMsg = await _onMessageAsync(wsClient); - const errorData = JSON.parse(errorMsg.data); - // tslint:disable-next-line:no-unused-expression - expect(errorData.id).to.be.null; - // tslint:disable-next-line:no-unused-expression - expect(errorData.method).to.be.null; - expect(errorData.error).to.match(/^Error: Expected request to conform to schema/); - }); - - it('executes addOrder and removeOrder requests correctly', async () => { - wsClient.onopen = () => wsClient.send(JSON.stringify(addOrderPayload)); - const addOrderMsg = await _onMessageAsync(wsClient); - const addOrderData = JSON.parse(addOrderMsg.data); - expect(addOrderData.method).to.be.eq('ADD_ORDER'); - expect((wsServer as any)._orderWatcher._orderByOrderHash).to.deep.include({ - [orderHash]: signedOrder, - }); - - wsClient.send(JSON.stringify(removeOrderPayload)); - const removeOrderMsg = await _onMessageAsync(wsClient); - const removeOrderData = JSON.parse(removeOrderMsg.data); - expect(removeOrderData.method).to.be.eq('REMOVE_ORDER'); - expect((wsServer as any)._orderWatcher._orderByOrderHash).to.not.deep.include({ - [orderHash]: signedOrder, - }); - }); - - it('broadcasts orderStateInvalid message when makerAddress allowance set to 0 for watched order', async () => { - // Add the regular order - wsClient.onopen = () => wsClient.send(JSON.stringify(addOrderPayload)); - await _onMessageAsync(wsClient); - - // Set the allowance to 0 - await contractWrappers.erc20Token.setProxyAllowanceAsync(makerTokenAddress, makerAddress, new BigNumber(0)); - - // Ensure that orderStateInvalid message is received. - const orderWatcherUpdateMsg = await _onMessageAsync(wsClient); - const orderWatcherUpdateData = JSON.parse(orderWatcherUpdateMsg.data); - expect(orderWatcherUpdateData.method).to.be.eq('UPDATE'); - const invalidOrderState = orderWatcherUpdateData.result as OrderStateInvalid; - expect(invalidOrderState.isValid).to.be.false(); - expect(invalidOrderState.orderHash).to.be.eq(orderHash); - expect(invalidOrderState.error).to.be.eq(ExchangeContractErrs.InsufficientMakerAllowance); - }); - - it('broadcasts to multiple clients when an order backing ZRX allowance changes', async () => { - // Prepare order - const makerFee = Web3Wrapper.toBaseUnitAmount(new BigNumber(2), decimals); - const takerFee = Web3Wrapper.toBaseUnitAmount(new BigNumber(0), decimals); - const nonZeroMakerFeeSignedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync( - makerAssetData, - takerAssetData, - makerFee, - takerFee, - makerAddress, - takerAddress, - fillableAmount, - takerAddress, - ); - const nonZeroMakerFeeOrderPayload = { - id: 'nonZeroMakerFeeOrderPayload', - jsonrpc: '2.0', - method: 'ADD_ORDER', - signedOrder: nonZeroMakerFeeSignedOrder, - }; - - // Set up a second client and have it add the order - wsClientTwo = new WebSocket.w3cwebsocket('ws://127.0.0.1:8080/'); - logUtils.log(`${new Date()} [Client] Connected.`); - wsClientTwo.onopen = () => wsClientTwo.send(JSON.stringify(nonZeroMakerFeeOrderPayload)); - await _onMessageAsync(wsClientTwo); - - // Change the allowance - await contractWrappers.erc20Token.setProxyAllowanceAsync(zrxTokenAddress, makerAddress, new BigNumber(0)); - - // Check that both clients receive the emitted event - for (const client of [wsClient, wsClientTwo]) { - const updateMsg = await _onMessageAsync(client); - const updateData = JSON.parse(updateMsg.data); - const orderState = updateData.result as OrderStateValid; - expect(orderState.isValid).to.be.true(); - expect(orderState.orderRelevantState.makerFeeProxyAllowance).to.be.eq('0'); - } - - wsClientTwo.close(); - logUtils.log(`${new Date()} [Client] Closed.`); - }); -}); -- cgit From f510f9df997633830e93e174ba598a45cae51f48 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Sat, 15 Dec 2018 21:34:56 -0800 Subject: remove unused instance variable --- .../order-watcher/src/order_watcher/order_watcher_websocket_server.ts | 2 -- 1 file changed, 2 deletions(-) (limited to 'packages') diff --git a/packages/order-watcher/src/order_watcher/order_watcher_websocket_server.ts b/packages/order-watcher/src/order_watcher/order_watcher_websocket_server.ts index 2d2d9e82e..a1b63128f 100644 --- a/packages/order-watcher/src/order_watcher/order_watcher_websocket_server.ts +++ b/packages/order-watcher/src/order_watcher/order_watcher_websocket_server.ts @@ -22,7 +22,6 @@ export class OrderWatcherWebSocketServer { private readonly _httpServer: http.Server; private readonly _connectionStore: Set; private readonly _wsServer: WebSocket.server; - private _jsonRpcRequestId: number; /** * Recover types lost when the payload is stringified. */ @@ -55,7 +54,6 @@ export class OrderWatcherWebSocketServer { contractAddresses?: ContractAddresses, partialConfig?: Partial, ) { - this._jsonRpcRequestId = 1; this._orderWatcher = new OrderWatcher(provider, networkId, contractAddresses, partialConfig); this._connectionStore = new Set(); this._httpServer = http.createServer(); -- cgit From 7661cfc85ef9e267d15bd4d7bd06c3b6cc3f7931 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Sun, 16 Dec 2018 16:21:27 -0800 Subject: Improve our compliance to the JSON RPC spec --- packages/order-watcher/README.md | 18 +++++------ .../order_watcher_websocket_server.ts | 36 ++++++++++++---------- .../order-watcher/src/schemas/websocket_schemas.ts | 2 ++ packages/order-watcher/src/types.ts | 10 +++--- .../test/order_watcher_websocket_server_test.ts | 4 +-- 5 files changed, 37 insertions(+), 33 deletions(-) (limited to 'packages') diff --git a/packages/order-watcher/README.md b/packages/order-watcher/README.md index aad90a59a..385fe4715 100644 --- a/packages/order-watcher/README.md +++ b/packages/order-watcher/README.md @@ -40,7 +40,7 @@ Several environmental variables can be set to configure the server: and accept connections from. When this is not set, we default to 8080. **Requests** -The server accepts three types of requests: `ADD_ORDER`, `REMOVE_ORDER` and `GET_STATS`. These mirror what the underlying OrderWatcher does. You can read more in the [wiki](https://0xproject.com/wiki#0x-OrderWatcher). Unlike the OrderWatcher, it does not expose any subscribe or unsubscribe functionality because the client implicitly subscribes and unsubscribes by connecting to the server. +The server accepts three types of requests: `ADD_ORDER`, `REMOVE_ORDER` and `GET_STATS`. These mirror what the underlying OrderWatcher does. You can read more in the [wiki](https://0xproject.com/wiki#0x-OrderWatcher). Unlike the OrderWatcher, it does not expose any `subscribe` or `unsubscribe` functionality because the WebSocket server keeps a single subscription open for all clients. The first step for making a request is establishing a connection with the server. In Javascript: @@ -58,9 +58,9 @@ wsClient = create_connection("ws://127.0.0.1:8080") With the connection established, you prepare the payload for your request. The payload is a json object with a format established by the [JSON RPC specification](https://www.jsonrpc.org/specification): -* `id`: All requests require you to specify a string as an id. When the server responds to the request, it provides an id as well to allow you to determine which request it is responding to. +* `id`: All requests require you to specify a numerical `id`. When the server responds to the request, the response will have the same `id` as the one supplied with your request. * `jsonrpc`: This is always the string `'2.0'`. -* `method`: This specifies the OrderWatcher method you want to call. I.e., `'ADD_ORDER'`, `'REMOVE_ORDER'`, and `'GET_STATS'`. +* `method`: This specifies the OrderWatcher method you want to call. I.e., `'ADD_ORDER'`, `'REMOVE_ORDER'` or `'GET_STATS'`. * `params`: These contain the parameters needed by OrderWatcher to execute the method you called. For `ADD_ORDER`, provide `{ signedOrder: }`. For `REMOVE_ORDER`, provide `{ orderHash: }`. For `GET_STATS`, no parameters are needed, so you may leave this empty. Next, convert the payload to a string and send it through the connection. @@ -68,7 +68,7 @@ In Javascript: ``` const addOrderPayload = { - id: 'order32', + id: 1, jsonrpc: '2.0', method: 'ADD_ORDER', params: { signedOrder: }, @@ -81,7 +81,7 @@ In Python: ``` import json remove_order_payload = { - 'id': 'order33', + 'id': 1, 'jsonrpc': '2.0', 'method': 'REMOVE_ORDER', 'params': {'orderHash': '0x6edc16bf37fde79f5012088c33784c730e2f103d9ab1caf73060c386ad107b7e'}, @@ -90,13 +90,13 @@ wsClient.send(json.dumps(remove_order_payload)); ``` **Response** -The server responds to all requests in a similar format. In the data field, you'll find another json object that has been converted into a string. This json object contains the following fields: +The server responds to all requests in a similar format. In the data field, you'll find another object containing the following fields: -* `id`: The id corresponding to the request that the server is responding to. `UPDATE` responses are not based on any requests so the `id` field is `null`. +* `id`: The id corresponding to the request that the server is responding to. `UPDATE` responses are not based on any requests so the `id` field is omitted`. * `jsonrpc`: Always `'2.0'`. * `method`: The method the server is responding to. Eg. `ADD_ORDER`. When order states change the server may also initiate a response. In this case, method will be listed as `UPDATE`. -* `result`: This field varies based on the method. `UPDATE` responses contained the new order state. `GET_STATS` responses contain the current order count. When there are errors, this field is `null`. -* `error`: When there is an error executing a request, the error message is listed here. When the server responds successfully, this field is `null`. +* `result`: This field varies based on the method. `UPDATE` responses contain the new order state. `GET_STATS` responses contain the current order count. When there are errors, this field is omitted. +* `error`: When there is an error executing a request, the [JSON RPC](https://www.jsonrpc.org/specification) error object is listed here. When the server responds successfully, this field is omitted. In Javascript, the responses can be parsed using the `onmessage` callback: diff --git a/packages/order-watcher/src/order_watcher/order_watcher_websocket_server.ts b/packages/order-watcher/src/order_watcher/order_watcher_websocket_server.ts index a1b63128f..eac48f849 100644 --- a/packages/order-watcher/src/order_watcher/order_watcher_websocket_server.ts +++ b/packages/order-watcher/src/order_watcher/order_watcher_websocket_server.ts @@ -12,7 +12,7 @@ import { assert } from '../utils/assert'; import { OrderWatcher } from './order_watcher'; const DEFAULT_HTTP_PORT = 8080; -const JSONRPC_VERSION = '2.0'; +const JSON_RPC_VERSION = '2.0'; // Wraps the OrderWatcher functionality in a WebSocket server. Motivations: // 1) Users can watch orders via non-typescript programs. @@ -77,16 +77,17 @@ export class OrderWatcherWebSocketServer { connection.on('close', this._onCloseCallback.bind(this, connection)); this._connectionStore.add(connection); }); + } + /** + * Activates the WebSocket server by subscribing to the OrderWatcher and + * starting the WebSocket's HTTP server + */ + public start(): void { // Have the WebSocket server subscribe to the OrderWatcher to receive updates. // These updates are then broadcast to clients in the _connectionStore. this._orderWatcher.subscribe(this._broadcastCallback.bind(this)); - } - /** - * Activates the WebSocket server by having its HTTP server start listening. - */ - public listen(): void { const port = process.env.ORDER_WATCHER_HTTP_PORT || DEFAULT_HTTP_PORT; this._httpServer.listen(port, () => { logUtils.log(`${new Date()} [Server] Listening on port ${port}`); @@ -95,29 +96,30 @@ export class OrderWatcherWebSocketServer { /** * Deactivates the WebSocket server by stopping the HTTP server from accepting - * new connections. + * new connections and unsubscribing from the OrderWatcher */ - public close(): void { + public stop(): void { this._httpServer.close(); + this._orderWatcher.unsubscribe(); } private async _onMessageCallbackAsync(connection: WebSocket.connection, message: any): Promise { let response: WebSocketResponse; + assert.doesConformToSchema('message', message, webSocketUtf8MessageSchema); + const request: WebSocketRequest = JSON.parse(message.utf8Data); + assert.doesConformToSchema('request', request, webSocketRequestSchema); + assert.isString(request.jsonrpc, JSON_RPC_VERSION); try { - assert.doesConformToSchema('message', message, webSocketUtf8MessageSchema); - const request: WebSocketRequest = JSON.parse(message.utf8Data); - assert.doesConformToSchema('request', request, webSocketRequestSchema); - assert.isString(request.jsonrpc, JSONRPC_VERSION); response = { id: request.id, - jsonrpc: JSONRPC_VERSION, + jsonrpc: JSON_RPC_VERSION, method: request.method, result: await this._routeRequestAsync(request), }; } catch (err) { response = { - id: null, - jsonrpc: JSONRPC_VERSION, + id: request.id, + jsonrpc: JSON_RPC_VERSION, method: null, error: err.toString(), }; @@ -165,12 +167,12 @@ export class OrderWatcherWebSocketServer { const response = err === null ? { - jsonrpc: JSONRPC_VERSION, + jsonrpc: JSON_RPC_VERSION, method, result: orderState, } : { - jsonrpc: JSONRPC_VERSION, + jsonrpc: JSON_RPC_VERSION, method, error: { code: -32000, diff --git a/packages/order-watcher/src/schemas/websocket_schemas.ts b/packages/order-watcher/src/schemas/websocket_schemas.ts index 5e4e1ab74..263dd45b3 100644 --- a/packages/order-watcher/src/schemas/websocket_schemas.ts +++ b/packages/order-watcher/src/schemas/websocket_schemas.ts @@ -1,3 +1,5 @@ +// TODO: Move these schemas to the `json-schemas` package and convert to JSON +// Rename to `OrderWatcherWebSocketRequestSchema`, etc... export const webSocketUtf8MessageSchema = { id: '/webSocketUtf8MessageSchema', properties: { diff --git a/packages/order-watcher/src/types.ts b/packages/order-watcher/src/types.ts index ecbebe305..536363d8a 100644 --- a/packages/order-watcher/src/types.ts +++ b/packages/order-watcher/src/types.ts @@ -49,21 +49,21 @@ export enum OrderWatcherMethod { export type WebSocketRequest = AddOrderRequest | RemoveOrderRequest | GetStatsRequest; interface AddOrderRequest { - id: string; + id: number; jsonrpc: string; method: OrderWatcherMethod.AddOrder; params: { signedOrder: SignedOrder }; } interface RemoveOrderRequest { - id: string; + id: number; jsonrpc: string; method: OrderWatcherMethod.RemoveOrder; params: { orderHash: string }; } interface GetStatsRequest { - id: string; + id: number; jsonrpc: string; method: OrderWatcherMethod.GetStats; } @@ -73,14 +73,14 @@ interface GetStatsRequest { export type WebSocketResponse = SuccessfulWebSocketResponse | ErrorWebSocketResponse; interface SuccessfulWebSocketResponse { - id: string | null; // id is null for UPDATE + id: number; jsonrpc: string; method: OrderWatcherMethod; result: OrderState | GetStatsResult | undefined; // result is undefined for ADD_ORDER and REMOVE_ORDER } interface ErrorWebSocketResponse { - id: null; + id: number; jsonrpc: string; method: null; error: JSONRPCError; diff --git a/packages/order-watcher/test/order_watcher_websocket_server_test.ts b/packages/order-watcher/test/order_watcher_websocket_server_test.ts index 9f9db7b1f..8a6deede8 100644 --- a/packages/order-watcher/test/order_watcher_websocket_server_test.ts +++ b/packages/order-watcher/test/order_watcher_websocket_server_test.ts @@ -105,11 +105,11 @@ describe.only('OrderWatcherWebSocketServer', async () => { // Prepare OrderWatcher WebSocket server const orderWatcherConfig = {}; wsServer = new OrderWatcherWebSocketServer(provider, networkId, contractAddresses, orderWatcherConfig); - wsServer.listen(); + wsServer.start(); }); after(async () => { await blockchainLifecycle.revertAsync(); - wsServer.close(); + wsServer.stop(); }); beforeEach(async () => { await blockchainLifecycle.startAsync(); -- cgit From ee4185ab465c76b64b65efefb92e11b0ca4ecad4 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Sun, 16 Dec 2018 16:52:37 -0800 Subject: Move OrderWatcher Websocket schemas to json-schemas and convert to JSON so that they are language agnostic --- .../order_watcher_web_socket_request_schema.json | 52 ++++++++++++++++++ ...der_watcher_web_socket_utf8_message_schema.json | 10 ++++ packages/json-schemas/src/schemas.ts | 4 ++ packages/json-schemas/tsconfig.json | 2 + .../order_watcher_websocket_server.ts | 6 +-- .../order-watcher/src/schemas/websocket_schemas.ts | 61 ---------------------- 6 files changed, 71 insertions(+), 64 deletions(-) create mode 100644 packages/json-schemas/schemas/order_watcher_web_socket_request_schema.json create mode 100644 packages/json-schemas/schemas/order_watcher_web_socket_utf8_message_schema.json (limited to 'packages') diff --git a/packages/json-schemas/schemas/order_watcher_web_socket_request_schema.json b/packages/json-schemas/schemas/order_watcher_web_socket_request_schema.json new file mode 100644 index 000000000..4666c6291 --- /dev/null +++ b/packages/json-schemas/schemas/order_watcher_web_socket_request_schema.json @@ -0,0 +1,52 @@ +{ + "id": "/orderWatcherWebSocketRequestSchema", + "type": "object", + "definitions": { + "signedOrderParam": { + "type": "object", + "properties": { + "signedOrder": { "$ref": "/signedOrderSchema" } + }, + "required": ["signedOrder"] + }, + "orderHashParam": { + "type": "object", + "properties": { + "orderHash": { "$ref": "/hexSchema" } + }, + "required": ["orderHash"] + } + }, + "oneOf": [ + { + "type": "object", + "properties": { + "id": { "type": "string" }, + "jsonrpc": { "type": "string" }, + "method": { "enum": ["ADD_ORDER"] }, + "params": { "$ref": "#/definitions/signedOrderParam" } + }, + "required": ["id", "jsonrpc", "method", "params"] + }, + { + "type": "object", + "properties": { + "id": { "type": "string" }, + "jsonrpc": { "type": "string" }, + "method": { "enum": ["REMOVE_ORDER"] }, + "params": { "$ref": "#/definitions/orderHashParam" } + }, + "required": ["id", "jsonrpc", "method", "params"] + }, + { + "type": "object", + "properties": { + "id": { "type": "string" }, + "jsonrpc": { "type": "string" }, + "method": { "enum": ["GET_STATS"] }, + "params": {} + }, + "required": ["id", "jsonrpc", "method"] + } + ] +} \ No newline at end of file diff --git a/packages/json-schemas/schemas/order_watcher_web_socket_utf8_message_schema.json b/packages/json-schemas/schemas/order_watcher_web_socket_utf8_message_schema.json new file mode 100644 index 000000000..154d6d754 --- /dev/null +++ b/packages/json-schemas/schemas/order_watcher_web_socket_utf8_message_schema.json @@ -0,0 +1,10 @@ +{ + "id": "/orderWatcherWebSocketUtf8MessageSchema", + "properties": { + "utf8Data": { "type": "string" } + }, + "required": [ + "utf8Data" + ], + "type": "object" +} diff --git a/packages/json-schemas/src/schemas.ts b/packages/json-schemas/src/schemas.ts index 21a6f424c..050f4e625 100644 --- a/packages/json-schemas/src/schemas.ts +++ b/packages/json-schemas/src/schemas.ts @@ -16,6 +16,8 @@ import * as orderFillOrKillRequestsSchema from '../schemas/order_fill_or_kill_re import * as orderFillRequestsSchema from '../schemas/order_fill_requests_schema.json'; import * as orderHashSchema from '../schemas/order_hash_schema.json'; import * as orderSchema from '../schemas/order_schema.json'; +import * as orderWatcherWebSocketRequestSchema from '../schemas/order_watcher_web_socket_request_schema.json'; +import * as orderWatcherWebSocketUtf8MessageSchema from '../schemas/order_watcher_web_socket_utf8_message_schema.json'; import * as orderBookRequestSchema from '../schemas/orderbook_request_schema.json'; import * as ordersRequestOptsSchema from '../schemas/orders_request_opts_schema.json'; import * as ordersSchema from '../schemas/orders_schema.json'; @@ -66,6 +68,8 @@ export const schemas = { jsNumber, requestOptsSchema, pagedRequestOptsSchema, + orderWatcherWebSocketRequestSchema, + orderWatcherWebSocketUtf8MessageSchema, ordersRequestOptsSchema, orderBookRequestSchema, orderConfigRequestSchema, diff --git a/packages/json-schemas/tsconfig.json b/packages/json-schemas/tsconfig.json index a79d54385..ec573290c 100644 --- a/packages/json-schemas/tsconfig.json +++ b/packages/json-schemas/tsconfig.json @@ -23,6 +23,8 @@ "./schemas/order_schema.json", "./schemas/signed_order_schema.json", "./schemas/orders_schema.json", + "./schemas/order_watcher_web_socket_request_schema.json", + "./schemas/order_watcher_web_socket_utf8_message_schema.json", "./schemas/paginated_collection_schema.json", "./schemas/relayer_api_asset_data_pairs_response_schema.json", "./schemas/relayer_api_asset_data_pairs_schema.json", diff --git a/packages/order-watcher/src/order_watcher/order_watcher_websocket_server.ts b/packages/order-watcher/src/order_watcher/order_watcher_websocket_server.ts index eac48f849..f90961cc8 100644 --- a/packages/order-watcher/src/order_watcher/order_watcher_websocket_server.ts +++ b/packages/order-watcher/src/order_watcher/order_watcher_websocket_server.ts @@ -1,11 +1,11 @@ import { ContractAddresses } from '@0x/contract-addresses'; +import { schemas } from '@0x/json-schemas'; import { OrderStateInvalid, OrderStateValid, SignedOrder } from '@0x/types'; import { BigNumber, logUtils } from '@0x/utils'; import { Provider } from 'ethereum-types'; import * as http from 'http'; import * as WebSocket from 'websocket'; -import { webSocketRequestSchema, webSocketUtf8MessageSchema } from '../schemas/websocket_schemas'; import { GetStatsResult, OrderWatcherConfig, OrderWatcherMethod, WebSocketRequest, WebSocketResponse } from '../types'; import { assert } from '../utils/assert'; @@ -105,9 +105,9 @@ export class OrderWatcherWebSocketServer { private async _onMessageCallbackAsync(connection: WebSocket.connection, message: any): Promise { let response: WebSocketResponse; - assert.doesConformToSchema('message', message, webSocketUtf8MessageSchema); + assert.doesConformToSchema('message', message, schemas.orderWatcherWebSocketUtf8MessageSchema); const request: WebSocketRequest = JSON.parse(message.utf8Data); - assert.doesConformToSchema('request', request, webSocketRequestSchema); + assert.doesConformToSchema('request', request, schemas.orderWatcherWebSocketRequestSchema); assert.isString(request.jsonrpc, JSON_RPC_VERSION); try { response = { diff --git a/packages/order-watcher/src/schemas/websocket_schemas.ts b/packages/order-watcher/src/schemas/websocket_schemas.ts index 263dd45b3..df54a38e1 100644 --- a/packages/order-watcher/src/schemas/websocket_schemas.ts +++ b/packages/order-watcher/src/schemas/websocket_schemas.ts @@ -1,63 +1,2 @@ // TODO: Move these schemas to the `json-schemas` package and convert to JSON // Rename to `OrderWatcherWebSocketRequestSchema`, etc... -export const webSocketUtf8MessageSchema = { - id: '/webSocketUtf8MessageSchema', - properties: { - utf8Data: { type: 'string' }, - }, - type: 'object', - required: ['utf8Data'], -}; - -export const webSocketRequestSchema = { - id: '/webSocketRequestSchema', - type: 'object', - definitions: { - signedOrderParam: { - type: 'object', - properties: { - signedOrder: { $ref: '/signedOrderSchema' }, - }, - required: ['signedOrder'], - }, - orderHashParam: { - type: 'object', - properties: { - orderHash: { $ref: '/hexSchema' }, - }, - required: ['orderHash'], - }, - }, - oneOf: [ - { - type: 'object', - properties: { - id: { type: 'string' }, - jsonrpc: { type: 'string' }, - method: { enum: ['ADD_ORDER'] }, - params: { $ref: '#/definitions/signedOrderParam' }, - }, - required: ['id', 'jsonrpc', 'method', 'params'], - }, - { - type: 'object', - properties: { - id: { type: 'string' }, - jsonrpc: { type: 'string' }, - method: { enum: ['REMOVE_ORDER'] }, - params: { $ref: '#/definitions/orderHashParam' }, - }, - required: ['id', 'jsonrpc', 'method', 'params'], - }, - { - type: 'object', - properties: { - id: { type: 'string' }, - jsonrpc: { type: 'string' }, - method: { enum: ['GET_STATS'] }, - params: {}, - }, - required: ['id', 'jsonrpc', 'method'], - }, - ], -}; -- cgit From 896c8d17c16c4f1e9670ab0747ae8934ce5400a5 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Sun, 16 Dec 2018 17:31:38 -0800 Subject: Fix schemas and tests --- .../order_watcher_web_socket_request_schema.json | 6 ++--- .../order_watcher_websocket_server.ts | 14 ++++++----- packages/order-watcher/src/types.ts | 14 +++++------ .../test/order_watcher_websocket_server_test.ts | 27 +++++++++++----------- 4 files changed, 32 insertions(+), 29 deletions(-) (limited to 'packages') diff --git a/packages/json-schemas/schemas/order_watcher_web_socket_request_schema.json b/packages/json-schemas/schemas/order_watcher_web_socket_request_schema.json index 4666c6291..b0c419f94 100644 --- a/packages/json-schemas/schemas/order_watcher_web_socket_request_schema.json +++ b/packages/json-schemas/schemas/order_watcher_web_socket_request_schema.json @@ -21,7 +21,7 @@ { "type": "object", "properties": { - "id": { "type": "string" }, + "id": { "type": "number" }, "jsonrpc": { "type": "string" }, "method": { "enum": ["ADD_ORDER"] }, "params": { "$ref": "#/definitions/signedOrderParam" } @@ -31,7 +31,7 @@ { "type": "object", "properties": { - "id": { "type": "string" }, + "id": { "type": "number" }, "jsonrpc": { "type": "string" }, "method": { "enum": ["REMOVE_ORDER"] }, "params": { "$ref": "#/definitions/orderHashParam" } @@ -41,7 +41,7 @@ { "type": "object", "properties": { - "id": { "type": "string" }, + "id": { "type": "number" }, "jsonrpc": { "type": "string" }, "method": { "enum": ["GET_STATS"] }, "params": {} diff --git a/packages/order-watcher/src/order_watcher/order_watcher_websocket_server.ts b/packages/order-watcher/src/order_watcher/order_watcher_websocket_server.ts index f90961cc8..da5667db3 100644 --- a/packages/order-watcher/src/order_watcher/order_watcher_websocket_server.ts +++ b/packages/order-watcher/src/order_watcher/order_watcher_websocket_server.ts @@ -105,20 +105,22 @@ export class OrderWatcherWebSocketServer { private async _onMessageCallbackAsync(connection: WebSocket.connection, message: any): Promise { let response: WebSocketResponse; - assert.doesConformToSchema('message', message, schemas.orderWatcherWebSocketUtf8MessageSchema); - const request: WebSocketRequest = JSON.parse(message.utf8Data); - assert.doesConformToSchema('request', request, schemas.orderWatcherWebSocketRequestSchema); - assert.isString(request.jsonrpc, JSON_RPC_VERSION); + let id: number | null = null; try { + assert.doesConformToSchema('message', message, schemas.orderWatcherWebSocketUtf8MessageSchema); + const request: WebSocketRequest = JSON.parse(message.utf8Data); + id = request.id; + assert.doesConformToSchema('request', request, schemas.orderWatcherWebSocketRequestSchema); + assert.isString(request.jsonrpc, JSON_RPC_VERSION); response = { - id: request.id, + id, jsonrpc: JSON_RPC_VERSION, method: request.method, result: await this._routeRequestAsync(request), }; } catch (err) { response = { - id: request.id, + id, jsonrpc: JSON_RPC_VERSION, method: null, error: err.toString(), diff --git a/packages/order-watcher/src/types.ts b/packages/order-watcher/src/types.ts index 536363d8a..2b529a939 100644 --- a/packages/order-watcher/src/types.ts +++ b/packages/order-watcher/src/types.ts @@ -48,21 +48,21 @@ export enum OrderWatcherMethod { // the data field of their WebSocket message to interact with the server. export type WebSocketRequest = AddOrderRequest | RemoveOrderRequest | GetStatsRequest; -interface AddOrderRequest { +export interface AddOrderRequest { id: number; jsonrpc: string; method: OrderWatcherMethod.AddOrder; params: { signedOrder: SignedOrder }; } -interface RemoveOrderRequest { +export interface RemoveOrderRequest { id: number; jsonrpc: string; method: OrderWatcherMethod.RemoveOrder; params: { orderHash: string }; } -interface GetStatsRequest { +export interface GetStatsRequest { id: number; jsonrpc: string; method: OrderWatcherMethod.GetStats; @@ -72,21 +72,21 @@ interface GetStatsRequest { // of the WebSocket messages that the server sends out. export type WebSocketResponse = SuccessfulWebSocketResponse | ErrorWebSocketResponse; -interface SuccessfulWebSocketResponse { +export interface SuccessfulWebSocketResponse { id: number; jsonrpc: string; method: OrderWatcherMethod; result: OrderState | GetStatsResult | undefined; // result is undefined for ADD_ORDER and REMOVE_ORDER } -interface ErrorWebSocketResponse { - id: number; +export interface ErrorWebSocketResponse { + id: number | null; jsonrpc: string; method: null; error: JSONRPCError; } -interface JSONRPCError { +export interface JSONRPCError { code: number; message: string; data?: string | object; diff --git a/packages/order-watcher/test/order_watcher_websocket_server_test.ts b/packages/order-watcher/test/order_watcher_websocket_server_test.ts index 8a6deede8..d21c676fc 100644 --- a/packages/order-watcher/test/order_watcher_websocket_server_test.ts +++ b/packages/order-watcher/test/order_watcher_websocket_server_test.ts @@ -11,6 +11,7 @@ import 'mocha'; import * as WebSocket from 'websocket'; import { OrderWatcherWebSocketServer } from '../src/order_watcher/order_watcher_websocket_server'; +import { AddOrderRequest, OrderWatcherMethod, RemoveOrderRequest } from '../src/types'; import { chaiSetup } from './utils/chai_setup'; import { constants } from './utils/constants'; @@ -43,8 +44,8 @@ describe.only('OrderWatcherWebSocketServer', async () => { let orderHash: string; // Manually encode types rather than use /src/types to mimick real data that user // would input. Otherwise we would be forced to use enums, which hide problems. - let addOrderPayload: { id: string; jsonrpc: string; method: string; params: { signedOrder: SignedOrder } }; - let removeOrderPayload: { id: string; jsonrpc: string; method: string; params: { orderHash: string } }; + let addOrderPayload: AddOrderRequest; + let removeOrderPayload: RemoveOrderRequest; const decimals = constants.ZRX_DECIMALS; const fillableAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(5), decimals); // HACK: createFillableSignedOrderAsync is Promise-based, which forces us @@ -90,15 +91,15 @@ describe.only('OrderWatcherWebSocketServer', async () => { ); orderHash = orderHashUtils.getOrderHashHex(signedOrder); addOrderPayload = { - id: 'addOrderPayload', + id: 1, jsonrpc: '2.0', - method: 'ADD_ORDER', + method: OrderWatcherMethod.AddOrder, params: { signedOrder }, }; removeOrderPayload = { - id: 'removeOrderPayload', + id: 1, jsonrpc: '2.0', - method: 'REMOVE_ORDER', + method: OrderWatcherMethod.RemoveOrder, params: { orderHash }, }; @@ -124,14 +125,14 @@ describe.only('OrderWatcherWebSocketServer', async () => { it('responds to getStats requests correctly', (done: any) => { const payload = { - id: 'getStats', + id: 1, jsonrpc: '2.0', method: 'GET_STATS', }; wsClient.onopen = () => wsClient.send(JSON.stringify(payload)); wsClient.onmessage = (msg: any) => { const responseData = JSON.parse(msg.data); - expect(responseData.id).to.be.eq('getStats'); + expect(responseData.id).to.be.eq(1); expect(responseData.jsonrpc).to.be.eq('2.0'); expect(responseData.method).to.be.eq('GET_STATS'); expect(responseData.result.orderCount).to.be.eq(0); @@ -141,7 +142,7 @@ describe.only('OrderWatcherWebSocketServer', async () => { it('throws an error when an invalid method is attempted', async () => { const invalidMethodPayload = { - id: 'invalidMethodPayload', + id: 1, jsonrpc: '2.0', method: 'BAD_METHOD', }; @@ -158,7 +159,7 @@ describe.only('OrderWatcherWebSocketServer', async () => { it('throws an error when jsonrpc field missing from request', async () => { const noJsonRpcPayload = { - id: 'noJsonRpcPayload', + id: 1, method: 'GET_STATS', }; wsClient.onopen = () => wsClient.send(JSON.stringify(noJsonRpcPayload)); @@ -172,7 +173,7 @@ describe.only('OrderWatcherWebSocketServer', async () => { it('throws an error when we try to add an order without a signedOrder', async () => { const noSignedOrderAddOrderPayload = { - id: 'noSignedOrderAddOrderPayload', + id: 1, jsonrpc: '2.0', method: 'ADD_ORDER', orderHash: '0x7337e2f2a9aa2ed6afe26edc2df7ad79c3ffa9cf9b81a964f707ea63f5272355', @@ -190,7 +191,7 @@ describe.only('OrderWatcherWebSocketServer', async () => { it('throws an error when we try to add a bad signedOrder', async () => { const invalidAddOrderPayload = { - id: 'invalidAddOrderPayload', + id: 1, jsonrpc: '2.0', method: 'ADD_ORDER', signedOrder: { @@ -258,7 +259,7 @@ describe.only('OrderWatcherWebSocketServer', async () => { takerAddress, ); const nonZeroMakerFeeOrderPayload = { - id: 'nonZeroMakerFeeOrderPayload', + id: 1, jsonrpc: '2.0', method: 'ADD_ORDER', signedOrder: nonZeroMakerFeeSignedOrder, -- cgit From 5d0e715d9ac9f358c1cdf23c9c96d622e0f1060c Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Sun, 16 Dec 2018 17:46:28 -0800 Subject: Add isVerbose option to enable/disable logging --- .../order_watcher/order_watcher_websocket_server.ts | 20 +++++++++++++++----- .../test/order_watcher_websocket_server_test.ts | 9 ++++++++- 2 files changed, 23 insertions(+), 6 deletions(-) (limited to 'packages') diff --git a/packages/order-watcher/src/order_watcher/order_watcher_websocket_server.ts b/packages/order-watcher/src/order_watcher/order_watcher_websocket_server.ts index da5667db3..2e29e775a 100644 --- a/packages/order-watcher/src/order_watcher/order_watcher_websocket_server.ts +++ b/packages/order-watcher/src/order_watcher/order_watcher_websocket_server.ts @@ -22,6 +22,7 @@ export class OrderWatcherWebSocketServer { private readonly _httpServer: http.Server; private readonly _connectionStore: Set; private readonly _wsServer: WebSocket.server; + private readonly _isVerbose: boolean; /** * Recover types lost when the payload is stringified. */ @@ -47,13 +48,16 @@ export class OrderWatcherWebSocketServer { * @param contractAddresses Optional contract addresses. Defaults to known * addresses based on networkId. * @param partialConfig Optional configurations. + * @param isVerbose Whether to enable verbose logging. Defaults to true. */ constructor( provider: Provider, networkId: number, contractAddresses?: ContractAddresses, + isVerbose: boolean = true, partialConfig?: Partial, ) { + this._isVerbose = isVerbose; this._orderWatcher = new OrderWatcher(provider, networkId, contractAddresses, partialConfig); this._connectionStore = new Set(); this._httpServer = http.createServer(); @@ -72,7 +76,7 @@ export class OrderWatcherWebSocketServer { // Designed for usage pattern where client and server are run on the same // machine by the same user. As such, no security checks are in place. const connection: WebSocket.connection = request.accept(null, request.origin); - logUtils.log(`${new Date()} [Server] Accepted connection from origin ${request.origin}.`); + this._log(`${new Date()} [Server] Accepted connection from origin ${request.origin}.`); connection.on('message', this._onMessageCallbackAsync.bind(this, connection)); connection.on('close', this._onCloseCallback.bind(this, connection)); this._connectionStore.add(connection); @@ -90,7 +94,7 @@ export class OrderWatcherWebSocketServer { const port = process.env.ORDER_WATCHER_HTTP_PORT || DEFAULT_HTTP_PORT; this._httpServer.listen(port, () => { - logUtils.log(`${new Date()} [Server] Listening on port ${port}`); + this._log(`${new Date()} [Server] Listening on port ${port}`); }); } @@ -103,6 +107,12 @@ export class OrderWatcherWebSocketServer { this._orderWatcher.unsubscribe(); } + private _log(...args: any[]): void { + if (this._isVerbose) { + logUtils.log(...args); + } + } + private async _onMessageCallbackAsync(connection: WebSocket.connection, message: any): Promise { let response: WebSocketResponse; let id: number | null = null; @@ -126,17 +136,17 @@ export class OrderWatcherWebSocketServer { error: err.toString(), }; } - logUtils.log(`${new Date()} [Server] OrderWatcher output: ${JSON.stringify(response)}`); + this._log(`${new Date()} [Server] OrderWatcher output: ${JSON.stringify(response)}`); connection.sendUTF(JSON.stringify(response)); } private _onCloseCallback(connection: WebSocket.connection): void { this._connectionStore.delete(connection); - logUtils.log(`${new Date()} [Server] Client ${connection.remoteAddress} disconnected.`); + this._log(`${new Date()} [Server] Client ${connection.remoteAddress} disconnected.`); } private async _routeRequestAsync(request: WebSocketRequest): Promise { - logUtils.log(`${new Date()} [Server] Request received: ${request.method}`); + this._log(`${new Date()} [Server] Request received: ${request.method}`); switch (request.method) { case OrderWatcherMethod.AddOrder: { const signedOrder: SignedOrder = OrderWatcherWebSocketServer._parseSignedOrder( diff --git a/packages/order-watcher/test/order_watcher_websocket_server_test.ts b/packages/order-watcher/test/order_watcher_websocket_server_test.ts index d21c676fc..d1a947105 100644 --- a/packages/order-watcher/test/order_watcher_websocket_server_test.ts +++ b/packages/order-watcher/test/order_watcher_websocket_server_test.ts @@ -105,7 +105,14 @@ describe.only('OrderWatcherWebSocketServer', async () => { // Prepare OrderWatcher WebSocket server const orderWatcherConfig = {}; - wsServer = new OrderWatcherWebSocketServer(provider, networkId, contractAddresses, orderWatcherConfig); + const isVerbose = true; + wsServer = new OrderWatcherWebSocketServer( + provider, + networkId, + contractAddresses, + isVerbose, + orderWatcherConfig, + ); wsServer.start(); }); after(async () => { -- cgit From a12b9e82f61ac136876f9d4b72b45aad266317cf Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Sun, 16 Dec 2018 18:00:23 -0800 Subject: Consolidate use of isVerbose in orderWatcherConfig --- .../src/order_watcher/order_watcher_websocket_server.ts | 16 +++++++++------- .../test/order_watcher_websocket_server_test.ts | 17 +++++------------ 2 files changed, 14 insertions(+), 19 deletions(-) (limited to 'packages') diff --git a/packages/order-watcher/src/order_watcher/order_watcher_websocket_server.ts b/packages/order-watcher/src/order_watcher/order_watcher_websocket_server.ts index 2e29e775a..b75b07603 100644 --- a/packages/order-watcher/src/order_watcher/order_watcher_websocket_server.ts +++ b/packages/order-watcher/src/order_watcher/order_watcher_websocket_server.ts @@ -47,18 +47,20 @@ export class OrderWatcherWebSocketServer { * @param networkId NetworkId to watch orders on. * @param contractAddresses Optional contract addresses. Defaults to known * addresses based on networkId. - * @param partialConfig Optional configurations. + * @param orderWatcherConfig OrderWatcher configurations. isVerbose sets the verbosity for the WebSocket server aswell. * @param isVerbose Whether to enable verbose logging. Defaults to true. */ constructor( provider: Provider, networkId: number, contractAddresses?: ContractAddresses, - isVerbose: boolean = true, - partialConfig?: Partial, + orderWatcherConfig?: Partial, ) { - this._isVerbose = isVerbose; - this._orderWatcher = new OrderWatcher(provider, networkId, contractAddresses, partialConfig); + this._isVerbose = + orderWatcherConfig !== undefined && orderWatcherConfig.isVerbose !== undefined + ? orderWatcherConfig.isVerbose + : true; + this._orderWatcher = new OrderWatcher(provider, networkId, contractAddresses, orderWatcherConfig); this._connectionStore = new Set(); this._httpServer = http.createServer(); this._wsServer = new WebSocket.server({ @@ -161,10 +163,10 @@ export class OrderWatcherWebSocketServer { } case OrderWatcherMethod.GetStats: { return this._orderWatcher.getStats(); - break; } default: - // Should never reach here. Should be caught by JSON schema check. + // Should never reach here. Should be caught by JSON schema check. + throw new Error(`Unexpected default case hit for request.method`); } return undefined; } diff --git a/packages/order-watcher/test/order_watcher_websocket_server_test.ts b/packages/order-watcher/test/order_watcher_websocket_server_test.ts index d1a947105..a66d2c6c2 100644 --- a/packages/order-watcher/test/order_watcher_websocket_server_test.ts +++ b/packages/order-watcher/test/order_watcher_websocket_server_test.ts @@ -26,7 +26,7 @@ interface WsMessage { data: string; } -describe.only('OrderWatcherWebSocketServer', async () => { +describe('OrderWatcherWebSocketServer', async () => { let contractWrappers: ContractWrappers; let wsServer: OrderWatcherWebSocketServer; let wsClient: WebSocket.w3cwebsocket; @@ -42,8 +42,6 @@ describe.only('OrderWatcherWebSocketServer', async () => { let zrxTokenAddress: string; let signedOrder: SignedOrder; let orderHash: string; - // Manually encode types rather than use /src/types to mimick real data that user - // would input. Otherwise we would be forced to use enums, which hide problems. let addOrderPayload: AddOrderRequest; let removeOrderPayload: RemoveOrderRequest; const decimals = constants.ZRX_DECIMALS; @@ -104,15 +102,10 @@ describe.only('OrderWatcherWebSocketServer', async () => { }; // Prepare OrderWatcher WebSocket server - const orderWatcherConfig = {}; - const isVerbose = true; - wsServer = new OrderWatcherWebSocketServer( - provider, - networkId, - contractAddresses, - isVerbose, - orderWatcherConfig, - ); + const orderWatcherConfig = { + isVerbose: true, + }; + wsServer = new OrderWatcherWebSocketServer(provider, networkId, contractAddresses, orderWatcherConfig); wsServer.start(); }); after(async () => { -- cgit From 6382f986086ee83374d3b78fed7f02e9d52b668f Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Sun, 16 Dec 2018 18:05:20 -0800 Subject: Fix file name --- packages/order-watcher/src/index.ts | 2 +- .../order_watcher_web_socket_server.ts | 200 ++++++++++++++ .../order_watcher_websocket_server.ts | 200 -------------- .../test/order_watcher_web_socket_server_test.ts | 289 +++++++++++++++++++++ .../test/order_watcher_websocket_server_test.ts | 289 --------------------- 5 files changed, 490 insertions(+), 490 deletions(-) create mode 100644 packages/order-watcher/src/order_watcher/order_watcher_web_socket_server.ts delete mode 100644 packages/order-watcher/src/order_watcher/order_watcher_websocket_server.ts create mode 100644 packages/order-watcher/test/order_watcher_web_socket_server_test.ts delete mode 100644 packages/order-watcher/test/order_watcher_websocket_server_test.ts (limited to 'packages') diff --git a/packages/order-watcher/src/index.ts b/packages/order-watcher/src/index.ts index 5bdef4504..e275a0c6a 100644 --- a/packages/order-watcher/src/index.ts +++ b/packages/order-watcher/src/index.ts @@ -1,5 +1,5 @@ export { OrderWatcher } from './order_watcher/order_watcher'; -export { OrderWatcherWebSocketServer } from './order_watcher/order_watcher_websocket_server'; +export { OrderWatcherWebSocketServer } from './order_watcher/order_watcher_web_socket_server'; export { ExpirationWatcher } from './order_watcher/expiration_watcher'; export { diff --git a/packages/order-watcher/src/order_watcher/order_watcher_web_socket_server.ts b/packages/order-watcher/src/order_watcher/order_watcher_web_socket_server.ts new file mode 100644 index 000000000..b75b07603 --- /dev/null +++ b/packages/order-watcher/src/order_watcher/order_watcher_web_socket_server.ts @@ -0,0 +1,200 @@ +import { ContractAddresses } from '@0x/contract-addresses'; +import { schemas } from '@0x/json-schemas'; +import { OrderStateInvalid, OrderStateValid, SignedOrder } from '@0x/types'; +import { BigNumber, logUtils } from '@0x/utils'; +import { Provider } from 'ethereum-types'; +import * as http from 'http'; +import * as WebSocket from 'websocket'; + +import { GetStatsResult, OrderWatcherConfig, OrderWatcherMethod, WebSocketRequest, WebSocketResponse } from '../types'; +import { assert } from '../utils/assert'; + +import { OrderWatcher } from './order_watcher'; + +const DEFAULT_HTTP_PORT = 8080; +const JSON_RPC_VERSION = '2.0'; + +// Wraps the OrderWatcher functionality in a WebSocket server. Motivations: +// 1) Users can watch orders via non-typescript programs. +// 2) Better encapsulation so that users can work +export class OrderWatcherWebSocketServer { + private readonly _orderWatcher: OrderWatcher; + private readonly _httpServer: http.Server; + private readonly _connectionStore: Set; + private readonly _wsServer: WebSocket.server; + private readonly _isVerbose: boolean; + /** + * Recover types lost when the payload is stringified. + */ + private static _parseSignedOrder(rawRequest: any): SignedOrder { + const bigNumberFields = [ + 'salt', + 'makerFee', + 'takerFee', + 'makerAssetAmount', + 'takerAssetAmount', + 'expirationTimeSeconds', + ]; + for (const field of bigNumberFields) { + rawRequest[field] = new BigNumber(rawRequest[field]); + } + return rawRequest; + } + + /** + * Instantiate a new WebSocket server which provides OrderWatcher functionality + * @param provider Web3 provider to use for JSON RPC calls. + * @param networkId NetworkId to watch orders on. + * @param contractAddresses Optional contract addresses. Defaults to known + * addresses based on networkId. + * @param orderWatcherConfig OrderWatcher configurations. isVerbose sets the verbosity for the WebSocket server aswell. + * @param isVerbose Whether to enable verbose logging. Defaults to true. + */ + constructor( + provider: Provider, + networkId: number, + contractAddresses?: ContractAddresses, + orderWatcherConfig?: Partial, + ) { + this._isVerbose = + orderWatcherConfig !== undefined && orderWatcherConfig.isVerbose !== undefined + ? orderWatcherConfig.isVerbose + : true; + this._orderWatcher = new OrderWatcher(provider, networkId, contractAddresses, orderWatcherConfig); + this._connectionStore = new Set(); + this._httpServer = http.createServer(); + this._wsServer = new WebSocket.server({ + httpServer: this._httpServer, + // Avoid setting autoAcceptConnections to true as it defeats all + // standard cross-origin protection facilities built into the protocol + // and the browser. + // Source: https://www.npmjs.com/package/websocket#server-example + // Also ensures that a request event is emitted by + // the server whenever a new WebSocket request is made. + autoAcceptConnections: false, + }); + + this._wsServer.on('request', async (request: any) => { + // Designed for usage pattern where client and server are run on the same + // machine by the same user. As such, no security checks are in place. + const connection: WebSocket.connection = request.accept(null, request.origin); + this._log(`${new Date()} [Server] Accepted connection from origin ${request.origin}.`); + connection.on('message', this._onMessageCallbackAsync.bind(this, connection)); + connection.on('close', this._onCloseCallback.bind(this, connection)); + this._connectionStore.add(connection); + }); + } + + /** + * Activates the WebSocket server by subscribing to the OrderWatcher and + * starting the WebSocket's HTTP server + */ + public start(): void { + // Have the WebSocket server subscribe to the OrderWatcher to receive updates. + // These updates are then broadcast to clients in the _connectionStore. + this._orderWatcher.subscribe(this._broadcastCallback.bind(this)); + + const port = process.env.ORDER_WATCHER_HTTP_PORT || DEFAULT_HTTP_PORT; + this._httpServer.listen(port, () => { + this._log(`${new Date()} [Server] Listening on port ${port}`); + }); + } + + /** + * Deactivates the WebSocket server by stopping the HTTP server from accepting + * new connections and unsubscribing from the OrderWatcher + */ + public stop(): void { + this._httpServer.close(); + this._orderWatcher.unsubscribe(); + } + + private _log(...args: any[]): void { + if (this._isVerbose) { + logUtils.log(...args); + } + } + + private async _onMessageCallbackAsync(connection: WebSocket.connection, message: any): Promise { + let response: WebSocketResponse; + let id: number | null = null; + try { + assert.doesConformToSchema('message', message, schemas.orderWatcherWebSocketUtf8MessageSchema); + const request: WebSocketRequest = JSON.parse(message.utf8Data); + id = request.id; + assert.doesConformToSchema('request', request, schemas.orderWatcherWebSocketRequestSchema); + assert.isString(request.jsonrpc, JSON_RPC_VERSION); + response = { + id, + jsonrpc: JSON_RPC_VERSION, + method: request.method, + result: await this._routeRequestAsync(request), + }; + } catch (err) { + response = { + id, + jsonrpc: JSON_RPC_VERSION, + method: null, + error: err.toString(), + }; + } + this._log(`${new Date()} [Server] OrderWatcher output: ${JSON.stringify(response)}`); + connection.sendUTF(JSON.stringify(response)); + } + + private _onCloseCallback(connection: WebSocket.connection): void { + this._connectionStore.delete(connection); + this._log(`${new Date()} [Server] Client ${connection.remoteAddress} disconnected.`); + } + + private async _routeRequestAsync(request: WebSocketRequest): Promise { + this._log(`${new Date()} [Server] Request received: ${request.method}`); + switch (request.method) { + case OrderWatcherMethod.AddOrder: { + const signedOrder: SignedOrder = OrderWatcherWebSocketServer._parseSignedOrder( + request.params.signedOrder, + ); + await this._orderWatcher.addOrderAsync(signedOrder); + break; + } + case OrderWatcherMethod.RemoveOrder: { + this._orderWatcher.removeOrder(request.params.orderHash || 'undefined'); + break; + } + case OrderWatcherMethod.GetStats: { + return this._orderWatcher.getStats(); + } + default: + // Should never reach here. Should be caught by JSON schema check. + throw new Error(`Unexpected default case hit for request.method`); + } + return undefined; + } + + /** + * Broadcasts OrderState changes to ALL connected clients. At the moment, + * we do not support clients subscribing to only a subset of orders. As such, + * Client B will be notified of changes to an order that Client A added. + */ + private _broadcastCallback(err: Error | null, orderState?: OrderStateValid | OrderStateInvalid | undefined): void { + const method = OrderWatcherMethod.Update; + const response = + err === null + ? { + jsonrpc: JSON_RPC_VERSION, + method, + result: orderState, + } + : { + jsonrpc: JSON_RPC_VERSION, + method, + error: { + code: -32000, + message: err.message, + }, + }; + this._connectionStore.forEach((connection: WebSocket.connection) => { + connection.sendUTF(JSON.stringify(response)); + }); + } +} diff --git a/packages/order-watcher/src/order_watcher/order_watcher_websocket_server.ts b/packages/order-watcher/src/order_watcher/order_watcher_websocket_server.ts deleted file mode 100644 index b75b07603..000000000 --- a/packages/order-watcher/src/order_watcher/order_watcher_websocket_server.ts +++ /dev/null @@ -1,200 +0,0 @@ -import { ContractAddresses } from '@0x/contract-addresses'; -import { schemas } from '@0x/json-schemas'; -import { OrderStateInvalid, OrderStateValid, SignedOrder } from '@0x/types'; -import { BigNumber, logUtils } from '@0x/utils'; -import { Provider } from 'ethereum-types'; -import * as http from 'http'; -import * as WebSocket from 'websocket'; - -import { GetStatsResult, OrderWatcherConfig, OrderWatcherMethod, WebSocketRequest, WebSocketResponse } from '../types'; -import { assert } from '../utils/assert'; - -import { OrderWatcher } from './order_watcher'; - -const DEFAULT_HTTP_PORT = 8080; -const JSON_RPC_VERSION = '2.0'; - -// Wraps the OrderWatcher functionality in a WebSocket server. Motivations: -// 1) Users can watch orders via non-typescript programs. -// 2) Better encapsulation so that users can work -export class OrderWatcherWebSocketServer { - private readonly _orderWatcher: OrderWatcher; - private readonly _httpServer: http.Server; - private readonly _connectionStore: Set; - private readonly _wsServer: WebSocket.server; - private readonly _isVerbose: boolean; - /** - * Recover types lost when the payload is stringified. - */ - private static _parseSignedOrder(rawRequest: any): SignedOrder { - const bigNumberFields = [ - 'salt', - 'makerFee', - 'takerFee', - 'makerAssetAmount', - 'takerAssetAmount', - 'expirationTimeSeconds', - ]; - for (const field of bigNumberFields) { - rawRequest[field] = new BigNumber(rawRequest[field]); - } - return rawRequest; - } - - /** - * Instantiate a new WebSocket server which provides OrderWatcher functionality - * @param provider Web3 provider to use for JSON RPC calls. - * @param networkId NetworkId to watch orders on. - * @param contractAddresses Optional contract addresses. Defaults to known - * addresses based on networkId. - * @param orderWatcherConfig OrderWatcher configurations. isVerbose sets the verbosity for the WebSocket server aswell. - * @param isVerbose Whether to enable verbose logging. Defaults to true. - */ - constructor( - provider: Provider, - networkId: number, - contractAddresses?: ContractAddresses, - orderWatcherConfig?: Partial, - ) { - this._isVerbose = - orderWatcherConfig !== undefined && orderWatcherConfig.isVerbose !== undefined - ? orderWatcherConfig.isVerbose - : true; - this._orderWatcher = new OrderWatcher(provider, networkId, contractAddresses, orderWatcherConfig); - this._connectionStore = new Set(); - this._httpServer = http.createServer(); - this._wsServer = new WebSocket.server({ - httpServer: this._httpServer, - // Avoid setting autoAcceptConnections to true as it defeats all - // standard cross-origin protection facilities built into the protocol - // and the browser. - // Source: https://www.npmjs.com/package/websocket#server-example - // Also ensures that a request event is emitted by - // the server whenever a new WebSocket request is made. - autoAcceptConnections: false, - }); - - this._wsServer.on('request', async (request: any) => { - // Designed for usage pattern where client and server are run on the same - // machine by the same user. As such, no security checks are in place. - const connection: WebSocket.connection = request.accept(null, request.origin); - this._log(`${new Date()} [Server] Accepted connection from origin ${request.origin}.`); - connection.on('message', this._onMessageCallbackAsync.bind(this, connection)); - connection.on('close', this._onCloseCallback.bind(this, connection)); - this._connectionStore.add(connection); - }); - } - - /** - * Activates the WebSocket server by subscribing to the OrderWatcher and - * starting the WebSocket's HTTP server - */ - public start(): void { - // Have the WebSocket server subscribe to the OrderWatcher to receive updates. - // These updates are then broadcast to clients in the _connectionStore. - this._orderWatcher.subscribe(this._broadcastCallback.bind(this)); - - const port = process.env.ORDER_WATCHER_HTTP_PORT || DEFAULT_HTTP_PORT; - this._httpServer.listen(port, () => { - this._log(`${new Date()} [Server] Listening on port ${port}`); - }); - } - - /** - * Deactivates the WebSocket server by stopping the HTTP server from accepting - * new connections and unsubscribing from the OrderWatcher - */ - public stop(): void { - this._httpServer.close(); - this._orderWatcher.unsubscribe(); - } - - private _log(...args: any[]): void { - if (this._isVerbose) { - logUtils.log(...args); - } - } - - private async _onMessageCallbackAsync(connection: WebSocket.connection, message: any): Promise { - let response: WebSocketResponse; - let id: number | null = null; - try { - assert.doesConformToSchema('message', message, schemas.orderWatcherWebSocketUtf8MessageSchema); - const request: WebSocketRequest = JSON.parse(message.utf8Data); - id = request.id; - assert.doesConformToSchema('request', request, schemas.orderWatcherWebSocketRequestSchema); - assert.isString(request.jsonrpc, JSON_RPC_VERSION); - response = { - id, - jsonrpc: JSON_RPC_VERSION, - method: request.method, - result: await this._routeRequestAsync(request), - }; - } catch (err) { - response = { - id, - jsonrpc: JSON_RPC_VERSION, - method: null, - error: err.toString(), - }; - } - this._log(`${new Date()} [Server] OrderWatcher output: ${JSON.stringify(response)}`); - connection.sendUTF(JSON.stringify(response)); - } - - private _onCloseCallback(connection: WebSocket.connection): void { - this._connectionStore.delete(connection); - this._log(`${new Date()} [Server] Client ${connection.remoteAddress} disconnected.`); - } - - private async _routeRequestAsync(request: WebSocketRequest): Promise { - this._log(`${new Date()} [Server] Request received: ${request.method}`); - switch (request.method) { - case OrderWatcherMethod.AddOrder: { - const signedOrder: SignedOrder = OrderWatcherWebSocketServer._parseSignedOrder( - request.params.signedOrder, - ); - await this._orderWatcher.addOrderAsync(signedOrder); - break; - } - case OrderWatcherMethod.RemoveOrder: { - this._orderWatcher.removeOrder(request.params.orderHash || 'undefined'); - break; - } - case OrderWatcherMethod.GetStats: { - return this._orderWatcher.getStats(); - } - default: - // Should never reach here. Should be caught by JSON schema check. - throw new Error(`Unexpected default case hit for request.method`); - } - return undefined; - } - - /** - * Broadcasts OrderState changes to ALL connected clients. At the moment, - * we do not support clients subscribing to only a subset of orders. As such, - * Client B will be notified of changes to an order that Client A added. - */ - private _broadcastCallback(err: Error | null, orderState?: OrderStateValid | OrderStateInvalid | undefined): void { - const method = OrderWatcherMethod.Update; - const response = - err === null - ? { - jsonrpc: JSON_RPC_VERSION, - method, - result: orderState, - } - : { - jsonrpc: JSON_RPC_VERSION, - method, - error: { - code: -32000, - message: err.message, - }, - }; - this._connectionStore.forEach((connection: WebSocket.connection) => { - connection.sendUTF(JSON.stringify(response)); - }); - } -} diff --git a/packages/order-watcher/test/order_watcher_web_socket_server_test.ts b/packages/order-watcher/test/order_watcher_web_socket_server_test.ts new file mode 100644 index 000000000..fd388e907 --- /dev/null +++ b/packages/order-watcher/test/order_watcher_web_socket_server_test.ts @@ -0,0 +1,289 @@ +import { ContractWrappers } from '@0x/contract-wrappers'; +import { tokenUtils } from '@0x/contract-wrappers/lib/test/utils/token_utils'; +import { BlockchainLifecycle } from '@0x/dev-utils'; +import { FillScenarios } from '@0x/fill-scenarios'; +import { assetDataUtils, orderHashUtils } from '@0x/order-utils'; +import { ExchangeContractErrs, OrderStateInvalid, OrderStateValid, SignedOrder } from '@0x/types'; +import { BigNumber, logUtils } from '@0x/utils'; +import { Web3Wrapper } from '@0x/web3-wrapper'; +import * as chai from 'chai'; +import 'mocha'; +import * as WebSocket from 'websocket'; + +import { OrderWatcherWebSocketServer } from '../src/order_watcher/order_watcher_web_socket_server'; +import { AddOrderRequest, OrderWatcherMethod, RemoveOrderRequest } from '../src/types'; + +import { chaiSetup } from './utils/chai_setup'; +import { constants } from './utils/constants'; +import { migrateOnceAsync } from './utils/migrate'; +import { provider, web3Wrapper } from './utils/web3_wrapper'; + +chaiSetup.configure(); +const expect = chai.expect; +const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); + +interface WsMessage { + data: string; +} + +describe('OrderWatcherWebSocketServer', async () => { + let contractWrappers: ContractWrappers; + let wsServer: OrderWatcherWebSocketServer; + let wsClient: WebSocket.w3cwebsocket; + let wsClientTwo: WebSocket.w3cwebsocket; + let fillScenarios: FillScenarios; + let userAddresses: string[]; + let makerAssetData: string; + let takerAssetData: string; + let makerTokenAddress: string; + let takerTokenAddress: string; + let makerAddress: string; + let takerAddress: string; + let zrxTokenAddress: string; + let signedOrder: SignedOrder; + let orderHash: string; + let addOrderPayload: AddOrderRequest; + let removeOrderPayload: RemoveOrderRequest; + const decimals = constants.ZRX_DECIMALS; + const fillableAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(5), decimals); + // HACK: createFillableSignedOrderAsync is Promise-based, which forces us + // to use Promises instead of the done() callbacks for tests. + // onmessage callback must thus be wrapped as a Promise. + const _onMessageAsync = async (client: WebSocket.w3cwebsocket) => + new Promise(resolve => { + client.onmessage = (msg: WsMessage) => resolve(msg); + }); + + before(async () => { + // Set up constants + const contractAddresses = await migrateOnceAsync(); + await blockchainLifecycle.startAsync(); + const networkId = constants.TESTRPC_NETWORK_ID; + const config = { + networkId, + contractAddresses, + }; + contractWrappers = new ContractWrappers(provider, config); + userAddresses = await web3Wrapper.getAvailableAddressesAsync(); + zrxTokenAddress = contractAddresses.zrxToken; + [makerAddress, takerAddress] = userAddresses; + [makerTokenAddress, takerTokenAddress] = tokenUtils.getDummyERC20TokenAddresses(); + [makerAssetData, takerAssetData] = [ + assetDataUtils.encodeERC20AssetData(makerTokenAddress), + assetDataUtils.encodeERC20AssetData(takerTokenAddress), + ]; + fillScenarios = new FillScenarios( + provider, + userAddresses, + zrxTokenAddress, + contractAddresses.exchange, + contractAddresses.erc20Proxy, + contractAddresses.erc721Proxy, + ); + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerAssetData, + takerAssetData, + makerAddress, + takerAddress, + fillableAmount, + ); + orderHash = orderHashUtils.getOrderHashHex(signedOrder); + addOrderPayload = { + id: 1, + jsonrpc: '2.0', + method: OrderWatcherMethod.AddOrder, + params: { signedOrder }, + }; + removeOrderPayload = { + id: 1, + jsonrpc: '2.0', + method: OrderWatcherMethod.RemoveOrder, + params: { orderHash }, + }; + + // Prepare OrderWatcher WebSocket server + const orderWatcherConfig = { + isVerbose: true, + }; + wsServer = new OrderWatcherWebSocketServer(provider, networkId, contractAddresses, orderWatcherConfig); + wsServer.start(); + }); + after(async () => { + await blockchainLifecycle.revertAsync(); + wsServer.stop(); + }); + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + wsClient = new WebSocket.w3cwebsocket('ws://127.0.0.1:8080/'); + logUtils.log(`${new Date()} [Client] Connected.`); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + wsClient.close(); + logUtils.log(`${new Date()} [Client] Closed.`); + }); + + it('responds to getStats requests correctly', (done: any) => { + const payload = { + id: 1, + jsonrpc: '2.0', + method: 'GET_STATS', + }; + wsClient.onopen = () => wsClient.send(JSON.stringify(payload)); + wsClient.onmessage = (msg: any) => { + const responseData = JSON.parse(msg.data); + expect(responseData.id).to.be.eq(1); + expect(responseData.jsonrpc).to.be.eq('2.0'); + expect(responseData.method).to.be.eq('GET_STATS'); + expect(responseData.result.orderCount).to.be.eq(0); + done(); + }; + }); + + it('throws an error when an invalid method is attempted', async () => { + const invalidMethodPayload = { + id: 1, + jsonrpc: '2.0', + method: 'BAD_METHOD', + }; + wsClient.onopen = () => wsClient.send(JSON.stringify(invalidMethodPayload)); + const errorMsg = await _onMessageAsync(wsClient); + const errorData = JSON.parse(errorMsg.data); + // tslint:disable-next-line:no-unused-expression + expect(errorData.id).to.be.null; + // tslint:disable-next-line:no-unused-expression + expect(errorData.method).to.be.null; + expect(errorData.jsonrpc).to.be.eq('2.0'); + expect(errorData.error).to.match(/^Error: Expected request to conform to schema/); + }); + + it('throws an error when jsonrpc field missing from request', async () => { + const noJsonRpcPayload = { + id: 1, + method: 'GET_STATS', + }; + wsClient.onopen = () => wsClient.send(JSON.stringify(noJsonRpcPayload)); + const errorMsg = await _onMessageAsync(wsClient); + const errorData = JSON.parse(errorMsg.data); + // tslint:disable-next-line:no-unused-expression + expect(errorData.method).to.be.null; + expect(errorData.jsonrpc).to.be.eq('2.0'); + expect(errorData.error).to.match(/^Error: Expected request to conform to schema/); + }); + + it('throws an error when we try to add an order without a signedOrder', async () => { + const noSignedOrderAddOrderPayload = { + id: 1, + jsonrpc: '2.0', + method: 'ADD_ORDER', + orderHash: '0x7337e2f2a9aa2ed6afe26edc2df7ad79c3ffa9cf9b81a964f707ea63f5272355', + }; + wsClient.onopen = () => wsClient.send(JSON.stringify(noSignedOrderAddOrderPayload)); + const errorMsg = await _onMessageAsync(wsClient); + const errorData = JSON.parse(errorMsg.data); + // tslint:disable-next-line:no-unused-expression + expect(errorData.id).to.be.null; + // tslint:disable-next-line:no-unused-expression + expect(errorData.method).to.be.null; + expect(errorData.jsonrpc).to.be.eq('2.0'); + expect(errorData.error).to.match(/^Error: Expected request to conform to schema/); + }); + + it('throws an error when we try to add a bad signedOrder', async () => { + const invalidAddOrderPayload = { + id: 1, + jsonrpc: '2.0', + method: 'ADD_ORDER', + signedOrder: { + makerAddress: '0x0', + }, + }; + wsClient.onopen = () => wsClient.send(JSON.stringify(invalidAddOrderPayload)); + const errorMsg = await _onMessageAsync(wsClient); + const errorData = JSON.parse(errorMsg.data); + // tslint:disable-next-line:no-unused-expression + expect(errorData.id).to.be.null; + // tslint:disable-next-line:no-unused-expression + expect(errorData.method).to.be.null; + expect(errorData.error).to.match(/^Error: Expected request to conform to schema/); + }); + + it('executes addOrder and removeOrder requests correctly', async () => { + wsClient.onopen = () => wsClient.send(JSON.stringify(addOrderPayload)); + const addOrderMsg = await _onMessageAsync(wsClient); + const addOrderData = JSON.parse(addOrderMsg.data); + expect(addOrderData.method).to.be.eq('ADD_ORDER'); + expect((wsServer as any)._orderWatcher._orderByOrderHash).to.deep.include({ + [orderHash]: signedOrder, + }); + + wsClient.send(JSON.stringify(removeOrderPayload)); + const removeOrderMsg = await _onMessageAsync(wsClient); + const removeOrderData = JSON.parse(removeOrderMsg.data); + expect(removeOrderData.method).to.be.eq('REMOVE_ORDER'); + expect((wsServer as any)._orderWatcher._orderByOrderHash).to.not.deep.include({ + [orderHash]: signedOrder, + }); + }); + + it('broadcasts orderStateInvalid message when makerAddress allowance set to 0 for watched order', async () => { + // Add the regular order + wsClient.onopen = () => wsClient.send(JSON.stringify(addOrderPayload)); + await _onMessageAsync(wsClient); + + // Set the allowance to 0 + await contractWrappers.erc20Token.setProxyAllowanceAsync(makerTokenAddress, makerAddress, new BigNumber(0)); + + // Ensure that orderStateInvalid message is received. + const orderWatcherUpdateMsg = await _onMessageAsync(wsClient); + const orderWatcherUpdateData = JSON.parse(orderWatcherUpdateMsg.data); + expect(orderWatcherUpdateData.method).to.be.eq('UPDATE'); + const invalidOrderState = orderWatcherUpdateData.result as OrderStateInvalid; + expect(invalidOrderState.isValid).to.be.false(); + expect(invalidOrderState.orderHash).to.be.eq(orderHash); + expect(invalidOrderState.error).to.be.eq(ExchangeContractErrs.InsufficientMakerAllowance); + }); + + it('broadcasts to multiple clients when an order backing ZRX allowance changes', async () => { + // Prepare order + const makerFee = Web3Wrapper.toBaseUnitAmount(new BigNumber(2), decimals); + const takerFee = Web3Wrapper.toBaseUnitAmount(new BigNumber(0), decimals); + const nonZeroMakerFeeSignedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync( + makerAssetData, + takerAssetData, + makerFee, + takerFee, + makerAddress, + takerAddress, + fillableAmount, + takerAddress, + ); + const nonZeroMakerFeeOrderPayload = { + id: 1, + jsonrpc: '2.0', + method: 'ADD_ORDER', + signedOrder: nonZeroMakerFeeSignedOrder, + }; + + // Set up a second client and have it add the order + wsClientTwo = new WebSocket.w3cwebsocket('ws://127.0.0.1:8080/'); + logUtils.log(`${new Date()} [Client] Connected.`); + wsClientTwo.onopen = () => wsClientTwo.send(JSON.stringify(nonZeroMakerFeeOrderPayload)); + await _onMessageAsync(wsClientTwo); + + // Change the allowance + await contractWrappers.erc20Token.setProxyAllowanceAsync(zrxTokenAddress, makerAddress, new BigNumber(0)); + + // Check that both clients receive the emitted event + for (const client of [wsClient, wsClientTwo]) { + const updateMsg = await _onMessageAsync(client); + const updateData = JSON.parse(updateMsg.data); + const orderState = updateData.result as OrderStateValid; + expect(orderState.isValid).to.be.true(); + expect(orderState.orderRelevantState.makerFeeProxyAllowance).to.be.eq('0'); + } + + wsClientTwo.close(); + logUtils.log(`${new Date()} [Client] Closed.`); + }); +}); diff --git a/packages/order-watcher/test/order_watcher_websocket_server_test.ts b/packages/order-watcher/test/order_watcher_websocket_server_test.ts deleted file mode 100644 index a66d2c6c2..000000000 --- a/packages/order-watcher/test/order_watcher_websocket_server_test.ts +++ /dev/null @@ -1,289 +0,0 @@ -import { ContractWrappers } from '@0x/contract-wrappers'; -import { tokenUtils } from '@0x/contract-wrappers/lib/test/utils/token_utils'; -import { BlockchainLifecycle } from '@0x/dev-utils'; -import { FillScenarios } from '@0x/fill-scenarios'; -import { assetDataUtils, orderHashUtils } from '@0x/order-utils'; -import { ExchangeContractErrs, OrderStateInvalid, OrderStateValid, SignedOrder } from '@0x/types'; -import { BigNumber, logUtils } from '@0x/utils'; -import { Web3Wrapper } from '@0x/web3-wrapper'; -import * as chai from 'chai'; -import 'mocha'; -import * as WebSocket from 'websocket'; - -import { OrderWatcherWebSocketServer } from '../src/order_watcher/order_watcher_websocket_server'; -import { AddOrderRequest, OrderWatcherMethod, RemoveOrderRequest } from '../src/types'; - -import { chaiSetup } from './utils/chai_setup'; -import { constants } from './utils/constants'; -import { migrateOnceAsync } from './utils/migrate'; -import { provider, web3Wrapper } from './utils/web3_wrapper'; - -chaiSetup.configure(); -const expect = chai.expect; -const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); - -interface WsMessage { - data: string; -} - -describe('OrderWatcherWebSocketServer', async () => { - let contractWrappers: ContractWrappers; - let wsServer: OrderWatcherWebSocketServer; - let wsClient: WebSocket.w3cwebsocket; - let wsClientTwo: WebSocket.w3cwebsocket; - let fillScenarios: FillScenarios; - let userAddresses: string[]; - let makerAssetData: string; - let takerAssetData: string; - let makerTokenAddress: string; - let takerTokenAddress: string; - let makerAddress: string; - let takerAddress: string; - let zrxTokenAddress: string; - let signedOrder: SignedOrder; - let orderHash: string; - let addOrderPayload: AddOrderRequest; - let removeOrderPayload: RemoveOrderRequest; - const decimals = constants.ZRX_DECIMALS; - const fillableAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(5), decimals); - // HACK: createFillableSignedOrderAsync is Promise-based, which forces us - // to use Promises instead of the done() callbacks for tests. - // onmessage callback must thus be wrapped as a Promise. - const _onMessageAsync = async (client: WebSocket.w3cwebsocket) => - new Promise(resolve => { - client.onmessage = (msg: WsMessage) => resolve(msg); - }); - - before(async () => { - // Set up constants - const contractAddresses = await migrateOnceAsync(); - await blockchainLifecycle.startAsync(); - const networkId = constants.TESTRPC_NETWORK_ID; - const config = { - networkId, - contractAddresses, - }; - contractWrappers = new ContractWrappers(provider, config); - userAddresses = await web3Wrapper.getAvailableAddressesAsync(); - zrxTokenAddress = contractAddresses.zrxToken; - [makerAddress, takerAddress] = userAddresses; - [makerTokenAddress, takerTokenAddress] = tokenUtils.getDummyERC20TokenAddresses(); - [makerAssetData, takerAssetData] = [ - assetDataUtils.encodeERC20AssetData(makerTokenAddress), - assetDataUtils.encodeERC20AssetData(takerTokenAddress), - ]; - fillScenarios = new FillScenarios( - provider, - userAddresses, - zrxTokenAddress, - contractAddresses.exchange, - contractAddresses.erc20Proxy, - contractAddresses.erc721Proxy, - ); - signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerAssetData, - takerAssetData, - makerAddress, - takerAddress, - fillableAmount, - ); - orderHash = orderHashUtils.getOrderHashHex(signedOrder); - addOrderPayload = { - id: 1, - jsonrpc: '2.0', - method: OrderWatcherMethod.AddOrder, - params: { signedOrder }, - }; - removeOrderPayload = { - id: 1, - jsonrpc: '2.0', - method: OrderWatcherMethod.RemoveOrder, - params: { orderHash }, - }; - - // Prepare OrderWatcher WebSocket server - const orderWatcherConfig = { - isVerbose: true, - }; - wsServer = new OrderWatcherWebSocketServer(provider, networkId, contractAddresses, orderWatcherConfig); - wsServer.start(); - }); - after(async () => { - await blockchainLifecycle.revertAsync(); - wsServer.stop(); - }); - beforeEach(async () => { - await blockchainLifecycle.startAsync(); - wsClient = new WebSocket.w3cwebsocket('ws://127.0.0.1:8080/'); - logUtils.log(`${new Date()} [Client] Connected.`); - }); - afterEach(async () => { - await blockchainLifecycle.revertAsync(); - wsClient.close(); - logUtils.log(`${new Date()} [Client] Closed.`); - }); - - it('responds to getStats requests correctly', (done: any) => { - const payload = { - id: 1, - jsonrpc: '2.0', - method: 'GET_STATS', - }; - wsClient.onopen = () => wsClient.send(JSON.stringify(payload)); - wsClient.onmessage = (msg: any) => { - const responseData = JSON.parse(msg.data); - expect(responseData.id).to.be.eq(1); - expect(responseData.jsonrpc).to.be.eq('2.0'); - expect(responseData.method).to.be.eq('GET_STATS'); - expect(responseData.result.orderCount).to.be.eq(0); - done(); - }; - }); - - it('throws an error when an invalid method is attempted', async () => { - const invalidMethodPayload = { - id: 1, - jsonrpc: '2.0', - method: 'BAD_METHOD', - }; - wsClient.onopen = () => wsClient.send(JSON.stringify(invalidMethodPayload)); - const errorMsg = await _onMessageAsync(wsClient); - const errorData = JSON.parse(errorMsg.data); - // tslint:disable-next-line:no-unused-expression - expect(errorData.id).to.be.null; - // tslint:disable-next-line:no-unused-expression - expect(errorData.method).to.be.null; - expect(errorData.jsonrpc).to.be.eq('2.0'); - expect(errorData.error).to.match(/^Error: Expected request to conform to schema/); - }); - - it('throws an error when jsonrpc field missing from request', async () => { - const noJsonRpcPayload = { - id: 1, - method: 'GET_STATS', - }; - wsClient.onopen = () => wsClient.send(JSON.stringify(noJsonRpcPayload)); - const errorMsg = await _onMessageAsync(wsClient); - const errorData = JSON.parse(errorMsg.data); - // tslint:disable-next-line:no-unused-expression - expect(errorData.method).to.be.null; - expect(errorData.jsonrpc).to.be.eq('2.0'); - expect(errorData.error).to.match(/^Error: Expected request to conform to schema/); - }); - - it('throws an error when we try to add an order without a signedOrder', async () => { - const noSignedOrderAddOrderPayload = { - id: 1, - jsonrpc: '2.0', - method: 'ADD_ORDER', - orderHash: '0x7337e2f2a9aa2ed6afe26edc2df7ad79c3ffa9cf9b81a964f707ea63f5272355', - }; - wsClient.onopen = () => wsClient.send(JSON.stringify(noSignedOrderAddOrderPayload)); - const errorMsg = await _onMessageAsync(wsClient); - const errorData = JSON.parse(errorMsg.data); - // tslint:disable-next-line:no-unused-expression - expect(errorData.id).to.be.null; - // tslint:disable-next-line:no-unused-expression - expect(errorData.method).to.be.null; - expect(errorData.jsonrpc).to.be.eq('2.0'); - expect(errorData.error).to.match(/^Error: Expected request to conform to schema/); - }); - - it('throws an error when we try to add a bad signedOrder', async () => { - const invalidAddOrderPayload = { - id: 1, - jsonrpc: '2.0', - method: 'ADD_ORDER', - signedOrder: { - makerAddress: '0x0', - }, - }; - wsClient.onopen = () => wsClient.send(JSON.stringify(invalidAddOrderPayload)); - const errorMsg = await _onMessageAsync(wsClient); - const errorData = JSON.parse(errorMsg.data); - // tslint:disable-next-line:no-unused-expression - expect(errorData.id).to.be.null; - // tslint:disable-next-line:no-unused-expression - expect(errorData.method).to.be.null; - expect(errorData.error).to.match(/^Error: Expected request to conform to schema/); - }); - - it('executes addOrder and removeOrder requests correctly', async () => { - wsClient.onopen = () => wsClient.send(JSON.stringify(addOrderPayload)); - const addOrderMsg = await _onMessageAsync(wsClient); - const addOrderData = JSON.parse(addOrderMsg.data); - expect(addOrderData.method).to.be.eq('ADD_ORDER'); - expect((wsServer as any)._orderWatcher._orderByOrderHash).to.deep.include({ - [orderHash]: signedOrder, - }); - - wsClient.send(JSON.stringify(removeOrderPayload)); - const removeOrderMsg = await _onMessageAsync(wsClient); - const removeOrderData = JSON.parse(removeOrderMsg.data); - expect(removeOrderData.method).to.be.eq('REMOVE_ORDER'); - expect((wsServer as any)._orderWatcher._orderByOrderHash).to.not.deep.include({ - [orderHash]: signedOrder, - }); - }); - - it('broadcasts orderStateInvalid message when makerAddress allowance set to 0 for watched order', async () => { - // Add the regular order - wsClient.onopen = () => wsClient.send(JSON.stringify(addOrderPayload)); - await _onMessageAsync(wsClient); - - // Set the allowance to 0 - await contractWrappers.erc20Token.setProxyAllowanceAsync(makerTokenAddress, makerAddress, new BigNumber(0)); - - // Ensure that orderStateInvalid message is received. - const orderWatcherUpdateMsg = await _onMessageAsync(wsClient); - const orderWatcherUpdateData = JSON.parse(orderWatcherUpdateMsg.data); - expect(orderWatcherUpdateData.method).to.be.eq('UPDATE'); - const invalidOrderState = orderWatcherUpdateData.result as OrderStateInvalid; - expect(invalidOrderState.isValid).to.be.false(); - expect(invalidOrderState.orderHash).to.be.eq(orderHash); - expect(invalidOrderState.error).to.be.eq(ExchangeContractErrs.InsufficientMakerAllowance); - }); - - it('broadcasts to multiple clients when an order backing ZRX allowance changes', async () => { - // Prepare order - const makerFee = Web3Wrapper.toBaseUnitAmount(new BigNumber(2), decimals); - const takerFee = Web3Wrapper.toBaseUnitAmount(new BigNumber(0), decimals); - const nonZeroMakerFeeSignedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync( - makerAssetData, - takerAssetData, - makerFee, - takerFee, - makerAddress, - takerAddress, - fillableAmount, - takerAddress, - ); - const nonZeroMakerFeeOrderPayload = { - id: 1, - jsonrpc: '2.0', - method: 'ADD_ORDER', - signedOrder: nonZeroMakerFeeSignedOrder, - }; - - // Set up a second client and have it add the order - wsClientTwo = new WebSocket.w3cwebsocket('ws://127.0.0.1:8080/'); - logUtils.log(`${new Date()} [Client] Connected.`); - wsClientTwo.onopen = () => wsClientTwo.send(JSON.stringify(nonZeroMakerFeeOrderPayload)); - await _onMessageAsync(wsClientTwo); - - // Change the allowance - await contractWrappers.erc20Token.setProxyAllowanceAsync(zrxTokenAddress, makerAddress, new BigNumber(0)); - - // Check that both clients receive the emitted event - for (const client of [wsClient, wsClientTwo]) { - const updateMsg = await _onMessageAsync(client); - const updateData = JSON.parse(updateMsg.data); - const orderState = updateData.result as OrderStateValid; - expect(orderState.isValid).to.be.true(); - expect(orderState.orderRelevantState.makerFeeProxyAllowance).to.be.eq('0'); - } - - wsClientTwo.close(); - logUtils.log(`${new Date()} [Client] Closed.`); - }); -}); -- cgit From e295eeb8938468b1527d5d81f212766cef40bc81 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Tue, 18 Dec 2018 16:25:26 +0000 Subject: Remove unused file --- packages/order-watcher/src/schemas/websocket_schemas.ts | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 packages/order-watcher/src/schemas/websocket_schemas.ts (limited to 'packages') diff --git a/packages/order-watcher/src/schemas/websocket_schemas.ts b/packages/order-watcher/src/schemas/websocket_schemas.ts deleted file mode 100644 index df54a38e1..000000000 --- a/packages/order-watcher/src/schemas/websocket_schemas.ts +++ /dev/null @@ -1,2 +0,0 @@ -// TODO: Move these schemas to the `json-schemas` package and convert to JSON -// Rename to `OrderWatcherWebSocketRequestSchema`, etc... -- cgit From 07c1a0121f7be48dfe1a70ba0dae08f78e7a8b02 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 26 Nov 2018 14:32:55 -0800 Subject: Untested - Compliant Forwarder with Wyre "Yes Compliance" Token --- .../CompliantForwarder/CompliantForwarder.sol | 101 +++++++++++++++++ .../YesComplianceToken/YesComplianceToken.sol | 119 +++++++++++++++++++++ 2 files changed, 220 insertions(+) create mode 100644 packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol create mode 100644 packages/contracts/contracts/tokens/YesComplianceToken/YesComplianceToken.sol (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol new file mode 100644 index 000000000..646a9f3b8 --- /dev/null +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -0,0 +1,101 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; +pragma experimental ABIEncoderV2; + +import "../../protocol/Exchange/interfaces/IExchange.sol"; +import "../../tokens/ERC721Token/IERC721Token.sol"; +import "../../utils/LibBytes/LibBytes.sol"; + +contract CompliantForwarder { + + using LibBytes for bytes; + + bytes4 constant internal EXCHANGE_FILL_ORDER_SELECTOR = bytes4(keccak256("fillOrder((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),uint256,bytes)")); + IExchange internal EXCHANGE; + IERC721Token internal COMPLIANCE_TOKEN; + + constructor(address exchange, address complianceToken) + public + { + EXCHANGE = IExchange(exchange); + COMPLIANCE_TOKEN = IERC721Token(complianceToken); + } + + function fillOrder( + uint256 salt, + address signerAddress, + bytes signedFillOrderTransaction, + bytes signature + ) + public + { + // Validate `signedFillOrderTransaction` + bytes4 selector = signedFillOrderTransaction.readBytes4(0); + if (selector != EXCHANGE_FILL_ORDER_SELECTOR) { + revert("EXCHANGE_TRANSACTION_NOT_FILL_ORDER"); + } + + // Extract maker address from fill order transaction + // Below is the table of calldata offsets into a fillOrder transaction. + /** + ### parameters + 0x00 ptr + 0x20 takerAssetFillAmount + 0x40 ptr + ### order + 0x60 makerAddress + 0x80 takerAddress + 0xa0 feeRecipientAddress + 0xc0 senderAddress + 0xe0 makerAssetAmount + 0x100 takerAssetAmount + 0x120 makerFee + 0x140 takerFee + 0x160 expirationTimeSeconds + 0x180 salt + 0x1a0 ptr + 0x1c0 ptr + 0x1e0 makerAssetData + * takerAssetData + * signature + ------------------------------ + * Context-dependent offsets, unknown at compile time. + */ + // Add 0x4 to a given offset to account for the fillOrder selector prepended to `signedFillOrderTransaction`. + // Add 0xc to the makerAddress since abi-encoded addresses are left padded with 12 bytes. + // Putting this together: makerAddress = 0x60 + 0x4 + 0xc = 0x70 + address makerAddress = signedFillOrderTransaction.readAddress(0x70); + + // Verify maker/taker have been verified by the compliance token. + if (COMPLIANCE_TOKEN.balanceOf(makerAddress) == 0) { + revert("MAKER_UNVERIFIED"); + } else if (COMPLIANCE_TOKEN.balanceOf(signerAddress) == 0) { + revert("TAKER_UNVERIFIED"); + } + + // All entities are verified. Execute fillOrder. + EXCHANGE.executeTransaction( + salt, + signerAddress, + signedFillOrderTransaction, + signature + ); + } +} \ No newline at end of file diff --git a/packages/contracts/contracts/tokens/YesComplianceToken/YesComplianceToken.sol b/packages/contracts/contracts/tokens/YesComplianceToken/YesComplianceToken.sol new file mode 100644 index 000000000..abd04219d --- /dev/null +++ b/packages/contracts/contracts/tokens/YesComplianceToken/YesComplianceToken.sol @@ -0,0 +1,119 @@ +pragma solidity ^0.4.24; + +import "../ERC721Token/ERC721Token.sol"; + +/** + * @notice an ERC721 "yes" compliance token supporting a collection of country-specific attributions which answer specific + * compliance-related queries with YES. (attestations) + * + * primarily ERC721 is useful for the self-management of claiming addresses. a single token is more useful + * than a non-ERC721 interface because of interop with other 721-supporting systems/ui; it allows users to + * manage their financial stamp with flexibility using a well-established simple concept of non-fungible tokens. + * this interface is for anyone needing to carry around and otherwise manage their proof of compliance. + * + * the financial systems these users authenticate against have a different set of API requirements. they need + * more contextualization ability than a balance check to support distinctions of attestations, as well as geographic + * distinction. these integrations are made simpler as the language of the query more closely match the language of compliance. + * + * this interface describes, beyond 721, these simple compliance-specific interfaces (and their management tools) + * + * notes: + * - no address can be associated with more than one identity (though addresses may have more than token). issuance + * in this circumstance will fail + * - one person or business = one entity + * - one entity may have many tokens across many addresses; they can mint and burn tokens tied to their identity at will + * - two token types: control & non-control. both carry compliance proof + * - control tokens let their holders mint and burn (within the same entity) + * - non-control tokens are solely for compliance queries + * - a lock on the entity is used instead of token revocation to remove the cash burden assumed by a customer to + * redistribute a fleet of coins + * - all country codes should be via ISO-3166-1 + * + * any (non-view) methods not explicitly marked idempotent are not idempotent. + */ +contract YesComplianceTokenV1 is ERC721Token /*, ERC165 :should: */ { + + uint256 public constant OWNER_ENTITY_ID = 1; + + uint8 public constant YESMARK_OWNER = 128; + uint8 public constant YESMARK_VALIDATOR = 129; + + /* + todo events: entity updated, destroyed, ???? + Finalized + Attested + + */ + + /** + * @notice query api: returns true if the specified address has the given country/yes attestation. this + * is the primary method partners will use to query the active qualifications of any particular + * address. + */ + function isYes(uint256 _validatorEntityId, address _address, uint16 _countryCode, uint8 _yes) external view returns(bool) ; + + /** @notice same as isYes except as an imperative */ + function requireYes(uint256 _validatorEntityId, address _address, uint16 _countryCode, uint8 _yes) external view ; + + /** + * @notice retrieve all YES marks for an address in a particular country + * @param _validatorEntityId the validator ID to consider. or, use 0 for any of them + * @param _address the validator ID to consider, or 0 for any of them + * @param _countryCode the ISO-3166-1 country code + * @return (non-duplicate) array of YES marks present + */ + function getYes(uint256 _validatorEntityId, address _address, uint16 _countryCode) external view returns(uint8[] /* memory */); + + // function getCountries(uint256 _validatorEntityId, address _address) external view returns(uint16[] /* memory */); + + /** + * @notice create new tokens. fail if _to already + * belongs to a different entity and caller is not validator + * @param _control true if the new token is a control token (can mint, burn). aka NOT limited. + * @param _entityId the entity to mint for, supply 0 to use the entity tied to the caller + * @return the newly created token ID + */ + function mint(address _to, uint256 _entityId, bool _control) external returns (uint256); + + /** @notice shortcut to mint() + setYes() in one call, for a single country */ + function mint(address _to, uint256 _entityId, bool _control, uint16 _countryCode, uint8[] _yes) external returns (uint256); + + /** @notice destroys a specific token */ + function burn(uint256 _tokenId) external; + + /** @notice destroys the entire entity and all tokens */ + function burnEntity(uint256 _entityId) external; + + /** + * @notice adds a specific attestations (yes) to an entity. idempotent: will return normally even if the mark + * was already set by this validator + */ + function setYes(uint256 _entityId, uint16 _countryCode, uint8 _yes) external; + + /** + * @notice removes a attestation(s) from a specific validator for an entity. idempotent + */ + function clearYes(uint256 _entityId, uint16 _countryCode, uint8 _yes) external; + + /** @notice removes all attestations in a given country for a particular entity. idempotent */ + function clearYes(uint256 _entityId, uint16 _countryCode) external; + + /** @notice removes all attestations for a particular entity. idempotent */ + function clearYes(uint256 _entityId) external; + + /** @notice assigns a lock to an entity, rendering all isYes queries false. idempotent */ + function setLocked(uint256 _entityId, bool _lock) external; + + /** @notice checks whether or not a particular entity is locked */ + function isLocked(uint256 _entityId) external view returns(bool); + + /** @notice returns true if the specified token has been finalized (cannot be moved) */ + function isFinalized(uint256 _tokenId) external view returns(bool); + + /** @notice finalizes a token by ID preventing it from getting moved. idempotent */ + function finalize(uint256 _tokenId) external; + + /** @return the entity ID associated with an address (or fail if there is not one) */ + function getEntityId(address _address) external view returns(uint256); + +} \ No newline at end of file -- cgit From f104d91595397a4e7ce82a36de92b09e76d9d507 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 26 Nov 2018 17:29:32 -0800 Subject: Broken commit -- saving current state of getting wyre compliance token contract setup --- .../YesComplianceToken/IYesComplianceToken.sol | 118 ++++ .../YesComplianceToken/WyreERC721Token/ERC721.sol | 40 ++ .../WyreERC721Token/ERC721Basic.sol | 47 ++ .../WyreERC721Token/ERC721BasicToken.sol | 353 +++++++++++ .../WyreERC721Token/ERC721Token.sol | 218 +++++++ .../YesComplianceToken/YesComplianceToken.sol | 693 ++++++++++++++++++--- .../test/extensions/compliant_forwarder.ts | 191 ++++++ 7 files changed, 1579 insertions(+), 81 deletions(-) create mode 100644 packages/contracts/contracts/tokens/YesComplianceToken/IYesComplianceToken.sol create mode 100644 packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721.sol create mode 100644 packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721Basic.sol create mode 100644 packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721BasicToken.sol create mode 100644 packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721Token.sol create mode 100644 packages/contracts/test/extensions/compliant_forwarder.ts (limited to 'packages') diff --git a/packages/contracts/contracts/tokens/YesComplianceToken/IYesComplianceToken.sol b/packages/contracts/contracts/tokens/YesComplianceToken/IYesComplianceToken.sol new file mode 100644 index 000000000..1573c6bac --- /dev/null +++ b/packages/contracts/contracts/tokens/YesComplianceToken/IYesComplianceToken.sol @@ -0,0 +1,118 @@ +pragma solidity ^0.4.24; + + +/** + * @notice an ERC721 "yes" compliance token supporting a collection of country-specific attributions which answer specific + * compliance-related queries with YES. (attestations) + * + * primarily ERC721 is useful for the self-management of claiming addresses. a single token is more useful + * than a non-ERC721 interface because of interop with other 721-supporting systems/ui; it allows users to + * manage their financial stamp with flexibility using a well-established simple concept of non-fungible tokens. + * this interface is for anyone needing to carry around and otherwise manage their proof of compliance. + * + * the financial systems these users authenticate against have a different set of API requirements. they need + * more contextualization ability than a balance check to support distinctions of attestations, as well as geographic + * distinction. these integrations are made simpler as the language of the query more closely match the language of compliance. + * + * this interface describes, beyond 721, these simple compliance-specific interfaces (and their management tools) + * + * notes: + * - no address can be associated with more than one identity (though addresses may have more than token). issuance + * in this circumstance will fail + * - one person or business = one entity + * - one entity may have many tokens across many addresses; they can mint and burn tokens tied to their identity at will + * - two token types: control & non-control. both carry compliance proof + * - control tokens let their holders mint and burn (within the same entity) + * - non-control tokens are solely for compliance queries + * - a lock on the entity is used instead of token revocation to remove the cash burden assumed by a customer to + * redistribute a fleet of coins + * - all country codes should be via ISO-3166-1 + * + * any (non-view) methods not explicitly marked idempotent are not idempotent. + */ +contract YesComplianceTokenV1 /*is ERC721Token*/ /*, ERC165 :should: */ { + + uint256 public constant OWNER_ENTITY_ID = 1; + + uint8 public constant YESMARK_OWNER = 128; + uint8 public constant YESMARK_VALIDATOR = 129; + + /* + todo events: entity updated, destroyed, ???? + Finalized + Attested + + */ + + /** + * @notice query api: returns true if the specified address has the given country/yes attestation. this + * is the primary method partners will use to query the active qualifications of any particular + * address. + */ + function isYes(uint256 _validatorEntityId, address _address, uint16 _countryCode, uint8 _yes) external view returns(bool) ; + + /** @notice same as isYes except as an imperative */ + function requireYes(uint256 _validatorEntityId, address _address, uint16 _countryCode, uint8 _yes) external view ; + + /** + * @notice retrieve all YES marks for an address in a particular country + * @param _validatorEntityId the validator ID to consider. or, use 0 for any of them + * @param _address the validator ID to consider, or 0 for any of them + * @param _countryCode the ISO-3166-1 country code + * @return (non-duplicate) array of YES marks present + */ + function getYes(uint256 _validatorEntityId, address _address, uint16 _countryCode) external view returns(uint8[] /* memory */); + + // function getCountries(uint256 _validatorEntityId, address _address) external view returns(uint16[] /* memory */); + + /** + * @notice create new tokens. fail if _to already + * belongs to a different entity and caller is not validator + * @param _control true if the new token is a control token (can mint, burn). aka NOT limited. + * @param _entityId the entity to mint for, supply 0 to use the entity tied to the caller + * @return the newly created token ID + */ + function mint(address _to, uint256 _entityId, bool _control) external returns (uint256); + + /** @notice shortcut to mint() + setYes() in one call, for a single country */ + function mint(address _to, uint256 _entityId, bool _control, uint16 _countryCode, uint8[] _yes) external returns (uint256); + + /** @notice destroys a specific token */ + function burn(uint256 _tokenId) external; + + /** @notice destroys the entire entity and all tokens */ + function burnEntity(uint256 _entityId) external; + + /** + * @notice adds a specific attestations (yes) to an entity. idempotent: will return normally even if the mark + * was already set by this validator + */ + function setYes(uint256 _entityId, uint16 _countryCode, uint8 _yes) external; + + /** + * @notice removes a attestation(s) from a specific validator for an entity. idempotent + */ + function clearYes(uint256 _entityId, uint16 _countryCode, uint8 _yes) external; + + /** @notice removes all attestations in a given country for a particular entity. idempotent */ + function clearYes(uint256 _entityId, uint16 _countryCode) external; + + /** @notice removes all attestations for a particular entity. idempotent */ + function clearYes(uint256 _entityId) external; + + /** @notice assigns a lock to an entity, rendering all isYes queries false. idempotent */ + function setLocked(uint256 _entityId, bool _lock) external; + + /** @notice checks whether or not a particular entity is locked */ + function isLocked(uint256 _entityId) external view returns(bool); + + /** @notice returns true if the specified token has been finalized (cannot be moved) */ + function isFinalized(uint256 _tokenId) external view returns(bool); + + /** @notice finalizes a token by ID preventing it from getting moved. idempotent */ + function finalize(uint256 _tokenId) external; + + /** @return the entity ID associated with an address (or fail if there is not one) */ + function getEntityId(address _address) external view returns(uint256); + +} \ No newline at end of file diff --git a/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721.sol b/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721.sol new file mode 100644 index 000000000..5b4907f13 --- /dev/null +++ b/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721.sol @@ -0,0 +1,40 @@ +pragma solidity ^0.4.21; + +import "./ERC721Basic.sol"; + + +/** + * @title ERC-721 Non-Fungible Token Standard, optional enumeration extension + * @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md + */ +contract ERC721Enumerable is ERC721Basic { + function totalSupply() public view returns (uint256); + function tokenOfOwnerByIndex( + address _owner, + uint256 _index + ) + public + view + returns (uint256 _tokenId); + + function tokenByIndex(uint256 _index) public view returns (uint256); +} + + +/** + * @title ERC-721 Non-Fungible Token Standard, optional metadata extension + * @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md + */ +contract ERC721Metadata is ERC721Basic { + function name() external view returns (string _name); + function symbol() external view returns (string _symbol); + function tokenURI(uint256 _tokenId) public view returns (string); +} + + +/** + * @title ERC-721 Non-Fungible Token Standard, full implementation interface + * @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md + */ +contract ERC721 is ERC721Basic, ERC721Enumerable, ERC721Metadata { +} \ No newline at end of file diff --git a/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721Basic.sol b/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721Basic.sol new file mode 100644 index 000000000..20f3c5812 --- /dev/null +++ b/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721Basic.sol @@ -0,0 +1,47 @@ +pragma solidity ^0.4.21; + +/** + * @title ERC721 Non-Fungible Token Standard basic interface + * @dev see https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md + */ +contract ERC721Basic { + event Transfer( + address indexed _from, + address indexed _to, + uint256 indexed _tokenId + ); + event Approval( + address indexed _owner, + address indexed _approved, + uint256 indexed _tokenId + ); + event ApprovalForAll( + address indexed _owner, + address indexed _operator, + bool _approved + ); + + function balanceOf(address _owner) public view returns (uint256 _balance); + function ownerOf(uint256 _tokenId) public view returns (address _owner); + function exists(uint256 _tokenId) public view returns (bool _exists); + + function approve(address _to, uint256 _tokenId) public; + function getApproved(uint256 _tokenId) + public view returns (address _operator); + + function setApprovalForAll(address _operator, bool _approved) public; + function isApprovedForAll(address _owner, address _operator) + public view returns (bool); + + function transferFrom(address _from, address _to, uint256 _tokenId) public; + function safeTransferFrom(address _from, address _to, uint256 _tokenId) + public; + + function safeTransferFrom( + address _from, + address _to, + uint256 _tokenId, + bytes _data + ) + public; +} \ No newline at end of file diff --git a/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721BasicToken.sol b/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721BasicToken.sol new file mode 100644 index 000000000..1d3fa37b8 --- /dev/null +++ b/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721BasicToken.sol @@ -0,0 +1,353 @@ +pragma solidity ^0.4.21; + +import "./ERC721Basic.sol"; +import "./ERC721Receiver.sol"; +import "../../math/SafeMath.sol"; +import "../../AddressUtils.sol"; +import "../../introspection/ERC165Support.sol"; + + +/** + * @title ERC721 Non-Fungible Token Standard basic implementation + * @dev see https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md + */ +contract ERC721BasicToken is ERC165Support, ERC721Basic { + + bytes4 private constant InterfaceId_ERC721 = 0x80ac58cd; + /* + * 0x80ac58cd === + * bytes4(keccak256('balanceOf(address)')) ^ + * bytes4(keccak256('ownerOf(uint256)')) ^ + * bytes4(keccak256('approve(address,uint256)')) ^ + * bytes4(keccak256('getApproved(uint256)')) ^ + * bytes4(keccak256('setApprovalForAll(address,bool)')) ^ + * bytes4(keccak256('isApprovedForAll(address,address)')) ^ + * bytes4(keccak256('transferFrom(address,address,uint256)')) ^ + * bytes4(keccak256('safeTransferFrom(address,address,uint256)')) ^ + * bytes4(keccak256('safeTransferFrom(address,address,uint256,bytes)')) + */ + + bytes4 private constant InterfaceId_ERC721Exists = 0x4f558e79; + /* + * 0x4f558e79 === + * bytes4(keccak256('exists(uint256)')) + */ + + using SafeMath for uint256; + using AddressUtils for address; + + // Equals to `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))` + // which can be also obtained as `ERC721Receiver(0).onERC721Received.selector` + bytes4 private constant ERC721_RECEIVED = 0x150b7a02; + + // Mapping from token ID to owner + mapping (uint256 => address) internal tokenOwner; + + // Mapping from token ID to approved address + mapping (uint256 => address) internal tokenApprovals; + + // Mapping from owner to number of owned token + mapping (address => uint256) internal ownedTokensCount; + + // Mapping from owner to operator approvals + mapping (address => mapping (address => bool)) internal operatorApprovals; + + /** + * @dev Guarantees msg.sender is owner of the given token + * @param _tokenId uint256 ID of the token to validate its ownership belongs to msg.sender + */ + modifier onlyOwnerOf(uint256 _tokenId) { + require(ownerOf(_tokenId) == msg.sender); + _; + } + + /** + * @dev Checks msg.sender can transfer a token, by being owner, approved, or operator + * @param _tokenId uint256 ID of the token to validate + */ + modifier canTransfer(uint256 _tokenId) { + require(isApprovedOrOwner(msg.sender, _tokenId)); + _; + } + + function _supportsInterface(bytes4 _interfaceId) + internal + view + returns (bool) + { + return super._supportsInterface(_interfaceId) || + _interfaceId == InterfaceId_ERC721 || _interfaceId == InterfaceId_ERC721Exists; + } + + /** + * @dev Gets the balance of the specified address + * @param _owner address to query the balance of + * @return uint256 representing the amount owned by the passed address + */ + function balanceOf(address _owner) public view returns (uint256) { + require(_owner != address(0)); + return ownedTokensCount[_owner]; + } + + /** + * @dev Gets the owner of the specified token ID + * @param _tokenId uint256 ID of the token to query the owner of + * @return owner address currently marked as the owner of the given token ID + */ + function ownerOf(uint256 _tokenId) public view returns (address) { + address owner = tokenOwner[_tokenId]; + require(owner != address(0)); + return owner; + } + + /** + * @dev Returns whether the specified token exists + * @param _tokenId uint256 ID of the token to query the existence of + * @return whether the token exists + */ + function exists(uint256 _tokenId) public view returns (bool) { + address owner = tokenOwner[_tokenId]; + return owner != address(0); + } + + /** + * @dev Approves another address to transfer the given token ID + * The zero address indicates there is no approved address. + * There can only be one approved address per token at a given time. + * Can only be called by the token owner or an approved operator. + * @param _to address to be approved for the given token ID + * @param _tokenId uint256 ID of the token to be approved + */ + function approve(address _to, uint256 _tokenId) public { + address owner = ownerOf(_tokenId); + require(_to != owner); + require(msg.sender == owner || isApprovedForAll(owner, msg.sender)); + + tokenApprovals[_tokenId] = _to; + emit Approval(owner, _to, _tokenId); + } + + /** + * @dev Gets the approved address for a token ID, or zero if no address set + * @param _tokenId uint256 ID of the token to query the approval of + * @return address currently approved for the given token ID + */ + function getApproved(uint256 _tokenId) public view returns (address) { + return tokenApprovals[_tokenId]; + } + + /** + * @dev Sets or unsets the approval of a given operator + * An operator is allowed to transfer all tokens of the sender on their behalf + * @param _to operator address to set the approval + * @param _approved representing the status of the approval to be set + */ + function setApprovalForAll(address _to, bool _approved) public { + require(_to != msg.sender); + operatorApprovals[msg.sender][_to] = _approved; + emit ApprovalForAll(msg.sender, _to, _approved); + } + + /** + * @dev Tells whether an operator is approved by a given owner + * @param _owner owner address which you want to query the approval of + * @param _operator operator address which you want to query the approval of + * @return bool whether the given operator is approved by the given owner + */ + function isApprovedForAll( + address _owner, + address _operator + ) + public + view + returns (bool) + { + return operatorApprovals[_owner][_operator]; + } + + /** + * @dev Transfers the ownership of a given token ID to another address + * Usage of this method is discouraged, use `safeTransferFrom` whenever possible + * Requires the msg sender to be the owner, approved, or operator + * @param _from current owner of the token + * @param _to address to receive the ownership of the given token ID + * @param _tokenId uint256 ID of the token to be transferred + */ + function transferFrom( + address _from, + address _to, + uint256 _tokenId + ) + public + canTransfer(_tokenId) + { + require(_from != address(0)); + require(_to != address(0)); + + clearApproval(_from, _tokenId); + removeTokenFrom(_from, _tokenId); + addTokenTo(_to, _tokenId); + + emit Transfer(_from, _to, _tokenId); + } + + /** + * @dev Safely transfers the ownership of a given token ID to another address + * If the target address is a contract, it must implement `onERC721Received`, + * which is called upon a safe transfer, and return the magic value + * `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`; otherwise, + * the transfer is reverted. + * + * Requires the msg sender to be the owner, approved, or operator + * @param _from current owner of the token + * @param _to address to receive the ownership of the given token ID + * @param _tokenId uint256 ID of the token to be transferred + */ + function safeTransferFrom( + address _from, + address _to, + uint256 _tokenId + ) + public + canTransfer(_tokenId) + { + // solium-disable-next-line arg-overflow + safeTransferFrom(_from, _to, _tokenId, ""); + } + + /** + * @dev Safely transfers the ownership of a given token ID to another address + * If the target address is a contract, it must implement `onERC721Received`, + * which is called upon a safe transfer, and return the magic value + * `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`; otherwise, + * the transfer is reverted. + * Requires the msg sender to be the owner, approved, or operator + * @param _from current owner of the token + * @param _to address to receive the ownership of the given token ID + * @param _tokenId uint256 ID of the token to be transferred + * @param _data bytes data to send along with a safe transfer check + */ + function safeTransferFrom( + address _from, + address _to, + uint256 _tokenId, + bytes _data + ) + public + canTransfer(_tokenId) + { + transferFrom(_from, _to, _tokenId); + // solium-disable-next-line arg-overflow + require(checkAndCallSafeTransfer(_from, _to, _tokenId, _data)); + } + + /** + * @dev Returns whether the given spender can transfer a given token ID + * @param _spender address of the spender to query + * @param _tokenId uint256 ID of the token to be transferred + * @return bool whether the msg.sender is approved for the given token ID, + * is an operator of the owner, or is the owner of the token + */ + function isApprovedOrOwner( + address _spender, + uint256 _tokenId + ) + internal + view + returns (bool) + { + address owner = ownerOf(_tokenId); + // Disable solium check because of + // https://github.com/duaraghav8/Solium/issues/175 + // solium-disable-next-line operator-whitespace + return ( + _spender == owner || + getApproved(_tokenId) == _spender || + isApprovedForAll(owner, _spender) + ); + } + + /** + * @dev Internal function to mint a new token + * Reverts if the given token ID already exists + * @param _to The address that will own the minted token + * @param _tokenId uint256 ID of the token to be minted by the msg.sender + */ + function _mint(address _to, uint256 _tokenId) internal { + require(_to != address(0)); + addTokenTo(_to, _tokenId); + emit Transfer(address(0), _to, _tokenId); + } + + /** + * @dev Internal function to burn a specific token + * Reverts if the token does not exist + * @param _tokenId uint256 ID of the token being burned by the msg.sender + */ + function _burn(address _owner, uint256 _tokenId) internal { + clearApproval(_owner, _tokenId); + removeTokenFrom(_owner, _tokenId); + emit Transfer(_owner, address(0), _tokenId); + } + + /** + * @dev Internal function to clear current approval of a given token ID + * Reverts if the given address is not indeed the owner of the token + * @param _owner owner of the token + * @param _tokenId uint256 ID of the token to be transferred + */ + function clearApproval(address _owner, uint256 _tokenId) internal { + require(ownerOf(_tokenId) == _owner); + if (tokenApprovals[_tokenId] != address(0)) { + tokenApprovals[_tokenId] = address(0); + } + } + + /** + * @dev Internal function to add a token ID to the list of a given address + * @param _to address representing the new owner of the given token ID + * @param _tokenId uint256 ID of the token to be added to the tokens list of the given address + */ + function addTokenTo(address _to, uint256 _tokenId) internal { + require(tokenOwner[_tokenId] == address(0)); + tokenOwner[_tokenId] = _to; + ownedTokensCount[_to] = ownedTokensCount[_to].add(1); + } + + /** + * @dev Internal function to remove a token ID from the list of a given address + * @param _from address representing the previous owner of the given token ID + * @param _tokenId uint256 ID of the token to be removed from the tokens list of the given address + */ + function removeTokenFrom(address _from, uint256 _tokenId) internal { + require(ownerOf(_tokenId) == _from); + ownedTokensCount[_from] = ownedTokensCount[_from].sub(1); + tokenOwner[_tokenId] = address(0); + } + + /** + * @dev Internal function to invoke `onERC721Received` on a target address + * The call is not executed if the target address is not a contract + * @param _from address representing the previous owner of the given token ID + * @param _to target address that will receive the tokens + * @param _tokenId uint256 ID of the token to be transferred + * @param _data bytes optional data to send along with the call + * @return whether the call correctly returned the expected magic value + */ + function checkAndCallSafeTransfer( + address _from, + address _to, + uint256 _tokenId, + bytes _data + ) + internal + returns (bool) + { + if (!_to.isContract()) { + return true; + } + bytes4 retval = ERC721Receiver(_to).onERC721Received( + msg.sender, _from, _tokenId, _data); + return (retval == ERC721_RECEIVED); + } +} \ No newline at end of file diff --git a/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721Token.sol b/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721Token.sol new file mode 100644 index 000000000..c7acee6df --- /dev/null +++ b/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721Token.sol @@ -0,0 +1,218 @@ +pragma solidity ^0.4.21; + +import "./ERC721.sol"; +import "./ERC721BasicToken.sol"; + + +/** + * @title Full ERC721 Token + * This implementation includes all the required and some optional functionality of the ERC721 standard + * Moreover, it includes approve all functionality using operator terminology + * @dev see https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md + */ +contract ERC721Token is ERC721BasicToken, ERC721 { + + bytes4 private constant InterfaceId_ERC721Enumerable = 0x780e9d63; + /** + * 0x780e9d63 === + * bytes4(keccak256('totalSupply()')) ^ + * bytes4(keccak256('tokenOfOwnerByIndex(address,uint256)')) ^ + * bytes4(keccak256('tokenByIndex(uint256)')) + */ + + bytes4 private constant InterfaceId_ERC721Metadata = 0x5b5e139f; + /** + * 0x5b5e139f === + * bytes4(keccak256('name()')) ^ + * bytes4(keccak256('symbol()')) ^ + * bytes4(keccak256('tokenURI(uint256)')) + */ + + // Token name + string internal name_; + + // Token symbol + string internal symbol_; + + // Mapping from owner to list of owned token IDs + mapping(address => uint256[]) internal ownedTokens; + + // Mapping from token ID to index of the owner tokens list + mapping(uint256 => uint256) internal ownedTokensIndex; + + // Array with all token ids, used for enumeration + uint256[] internal allTokens; + + // Mapping from token id to position in the allTokens array + mapping(uint256 => uint256) internal allTokensIndex; + + // Optional mapping for token URIs + mapping(uint256 => string) internal tokenURIs; + + /** + * @dev Constructor function + */ + function initialize(string _name, string _symbol) public isInitializer("ERC721Token", "1.9.0") { + name_ = _name; + symbol_ = _symbol; + } + + function _supportsInterface(bytes4 _interfaceId) + internal + view + returns (bool) + { + return super._supportsInterface(_interfaceId) || + _interfaceId == InterfaceId_ERC721Enumerable || _interfaceId == InterfaceId_ERC721Metadata; + } + + /** + * @dev Gets the token name + * @return string representing the token name + */ + function name() external view returns (string) { + return name_; + } + + /** + * @dev Gets the token symbol + * @return string representing the token symbol + */ + function symbol() external view returns (string) { + return symbol_; + } + + /** + * @dev Returns an URI for a given token ID + * Throws if the token ID does not exist. May return an empty string. + * @param _tokenId uint256 ID of the token to query + */ + function tokenURI(uint256 _tokenId) public view returns (string) { + require(exists(_tokenId)); + return tokenURIs[_tokenId]; + } + + /** + * @dev Gets the token ID at a given index of the tokens list of the requested owner + * @param _owner address owning the tokens list to be accessed + * @param _index uint256 representing the index to be accessed of the requested tokens list + * @return uint256 token ID at the given index of the tokens list owned by the requested address + */ + function tokenOfOwnerByIndex( + address _owner, + uint256 _index + ) + public + view + returns (uint256) + { + require(_index < balanceOf(_owner)); + return ownedTokens[_owner][_index]; + } + + /** + * @dev Gets the total amount of tokens stored by the contract + * @return uint256 representing the total amount of tokens + */ + function totalSupply() public view returns (uint256) { + return allTokens.length; + } + + /** + * @dev Gets the token ID at a given index of all the tokens in this contract + * Reverts if the index is greater or equal to the total number of tokens + * @param _index uint256 representing the index to be accessed of the tokens list + * @return uint256 token ID at the given index of the tokens list + */ + function tokenByIndex(uint256 _index) public view returns (uint256) { + require(_index < totalSupply()); + return allTokens[_index]; + } + + /** + * @dev Internal function to set the token URI for a given token + * Reverts if the token ID does not exist + * @param _tokenId uint256 ID of the token to set its URI + * @param _uri string URI to assign + */ + function _setTokenURI(uint256 _tokenId, string _uri) internal { + require(exists(_tokenId)); + tokenURIs[_tokenId] = _uri; + } + + /** + * @dev Internal function to add a token ID to the list of a given address + * @param _to address representing the new owner of the given token ID + * @param _tokenId uint256 ID of the token to be added to the tokens list of the given address + */ + function addTokenTo(address _to, uint256 _tokenId) internal { + super.addTokenTo(_to, _tokenId); + uint256 length = ownedTokens[_to].length; + ownedTokens[_to].push(_tokenId); + ownedTokensIndex[_tokenId] = length; + } + + /** + * @dev Internal function to remove a token ID from the list of a given address + * @param _from address representing the previous owner of the given token ID + * @param _tokenId uint256 ID of the token to be removed from the tokens list of the given address + */ + function removeTokenFrom(address _from, uint256 _tokenId) internal { + super.removeTokenFrom(_from, _tokenId); + + uint256 tokenIndex = ownedTokensIndex[_tokenId]; + uint256 lastTokenIndex = ownedTokens[_from].length.sub(1); + uint256 lastToken = ownedTokens[_from][lastTokenIndex]; + + ownedTokens[_from][tokenIndex] = lastToken; + ownedTokens[_from][lastTokenIndex] = 0; + // Note that this will handle single-element arrays. In that case, both tokenIndex and lastTokenIndex are going to + // be zero. Then we can make sure that we will remove _tokenId from the ownedTokens list since we are first swapping + // the lastToken to the first position, and then dropping the element placed in the last position of the list + + ownedTokens[_from].length--; + ownedTokensIndex[_tokenId] = 0; + ownedTokensIndex[lastToken] = tokenIndex; + } + + /** + * @dev Internal function to mint a new token + * Reverts if the given token ID already exists + * @param _to address the beneficiary that will own the minted token + * @param _tokenId uint256 ID of the token to be minted by the msg.sender + */ + function _mint(address _to, uint256 _tokenId) internal { + super._mint(_to, _tokenId); + + allTokensIndex[_tokenId] = allTokens.length; + allTokens.push(_tokenId); + } + + /** + * @dev Internal function to burn a specific token + * Reverts if the token does not exist + * @param _owner owner of the token to burn + * @param _tokenId uint256 ID of the token being burned by the msg.sender + */ + function _burn(address _owner, uint256 _tokenId) internal { + super._burn(_owner, _tokenId); + + // Clear metadata (if any) + if (bytes(tokenURIs[_tokenId]).length != 0) { + delete tokenURIs[_tokenId]; + } + + // Reorg all tokens array + uint256 tokenIndex = allTokensIndex[_tokenId]; + uint256 lastTokenIndex = allTokens.length.sub(1); + uint256 lastToken = allTokens[lastTokenIndex]; + + allTokens[tokenIndex] = lastToken; + allTokens[lastTokenIndex] = 0; + + allTokens.length--; + allTokensIndex[_tokenId] = 0; + allTokensIndex[lastToken] = tokenIndex; + } + +} \ No newline at end of file diff --git a/packages/contracts/contracts/tokens/YesComplianceToken/YesComplianceToken.sol b/packages/contracts/contracts/tokens/YesComplianceToken/YesComplianceToken.sol index abd04219d..b810d9f31 100644 --- a/packages/contracts/contracts/tokens/YesComplianceToken/YesComplianceToken.sol +++ b/packages/contracts/contracts/tokens/YesComplianceToken/YesComplianceToken.sol @@ -1,119 +1,650 @@ pragma solidity ^0.4.24; -import "../ERC721Token/ERC721Token.sol"; +import "./IYesComplianceToken.sol"; /** - * @notice an ERC721 "yes" compliance token supporting a collection of country-specific attributions which answer specific - * compliance-related queries with YES. (attestations) + * draft implementation of YES compliance token * - * primarily ERC721 is useful for the self-management of claiming addresses. a single token is more useful - * than a non-ERC721 interface because of interop with other 721-supporting systems/ui; it allows users to - * manage their financial stamp with flexibility using a well-established simple concept of non-fungible tokens. - * this interface is for anyone needing to carry around and otherwise manage their proof of compliance. + * NOTE: i have done relatively few gas optimization tweaks (beyond using the sturctures necessary to avoid any + * linear time procedures). + * in some cases i am using a call structure which replicates some checks. this is for code clarity/security - + * i marked a few obvious ones which could be optimized for gas, but :meh: * - * the financial systems these users authenticate against have a different set of API requirements. they need - * more contextualization ability than a balance check to support distinctions of attestations, as well as geographic - * distinction. these integrations are made simpler as the language of the query more closely match the language of compliance. - * - * this interface describes, beyond 721, these simple compliance-specific interfaces (and their management tools) - * - * notes: - * - no address can be associated with more than one identity (though addresses may have more than token). issuance - * in this circumstance will fail - * - one person or business = one entity - * - one entity may have many tokens across many addresses; they can mint and burn tokens tied to their identity at will - * - two token types: control & non-control. both carry compliance proof - * - control tokens let their holders mint and burn (within the same entity) - * - non-control tokens are solely for compliance queries - * - a lock on the entity is used instead of token revocation to remove the cash burden assumed by a customer to - * redistribute a fleet of coins - * - all country codes should be via ISO-3166-1 - * - * any (non-view) methods not explicitly marked idempotent are not idempotent. + * todo static owner should follow owner token? remove static owner? :security: :should: + * @author Tyson Malchow */ -contract YesComplianceTokenV1 is ERC721Token /*, ERC165 :should: */ { +contract YesComplianceToken is YesComplianceTokenV1 { - uint256 public constant OWNER_ENTITY_ID = 1; + uint64 private constant MAX_TOKENS_PER_ENTITY = 10240; // completely arbitrary limit + uint64 private constant MAX_ENTITIES = 2**32-1; // bc using 32 bit index tracking + uint64 private constant MAX_VALIDATORS_PER_MARK = 2**32-1; // bc using 32 bit index tracking + uint64 private constant TOTAL_YES_MARKS = 255; // bc 'uint8 yes' - uint8 public constant YESMARK_OWNER = 128; - uint8 public constant YESMARK_VALIDATOR = 129; + // todo could shorten the entity IDs to anything 160+ to make this cheaper? - /* - todo events: entity updated, destroyed, ???? - Finalized - Attested + /** @notice a single YES attestation */ + struct YesMark { - */ + /** @notice ISO-3166-1 country codes */ + uint16 countryCode; - /** - * @notice query api: returns true if the specified address has the given country/yes attestation. this - * is the primary method partners will use to query the active qualifications of any particular - * address. - */ - function isYes(uint256 _validatorEntityId, address _address, uint16 _countryCode, uint8 _yes) external view returns(bool) ; + /** @notice the possibly-country-speicifc YES being marked. */ + uint8 yes; + + // 8 bits more space in this slot.. could upgrade yes to uint16? + + /** @notice the index of this mark in EntityRecord.yesMarks */ + uint32 yesMarkIdx; + + /** a list of the validator entities which have attested to this mark */ + uint256[] validatorEntityIds; - /** @notice same as isYes except as an imperative */ - function requireYes(uint256 _validatorEntityId, address _address, uint16 _countryCode, uint8 _yes) external view ; + /** @notice index of each validator entity ID in validatorEntityIds */ + mapping(uint256 => uint32) validatorEntityIdIdx; + + // uint8 entityListIdx; + } /** - * @notice retrieve all YES marks for an address in a particular country - * @param _validatorEntityId the validator ID to consider. or, use 0 for any of them - * @param _address the validator ID to consider, or 0 for any of them - * @param _countryCode the ISO-3166-1 country code - * @return (non-duplicate) array of YES marks present + * tracks the state for a single recognized entity */ - function getYes(uint256 _validatorEntityId, address _address, uint16 _countryCode) external view returns(uint8[] /* memory */); + struct EntityRecord { + + /** true marking this entity ID has been encountered */ + bool init; + + /** when true, this entity is effectively useless */ + bool locked; + + // 30 bits more space in this slot + + /** position of the entityId in allEntityIds */ + uint32 entityIdIdx; + + /** used for creating reliable token IDs, monotonically increasing */ + uint64 tokenIdCounter; + + /** indexed YES mark lookups */ + mapping(bytes4 => YesMark) yesMarkByKey; + + /** raw collection of all marks keys */ + bytes4[] yesMarkKeys; - // function getCountries(uint256 _validatorEntityId, address _address) external view returns(uint16[] /* memory */); + /** all tokens associated with this identity */ + uint256[] tokenIds; + + // trellis/tower connection ? + // civic connection ? + // erc725/735 connection ? + } /** - * @notice create new tokens. fail if _to already - * belongs to a different entity and caller is not validator - * @param _control true if the new token is a control token (can mint, burn). aka NOT limited. - * @param _entityId the entity to mint for, supply 0 to use the entity tied to the caller - * @return the newly created token ID + * @notice all fields we want to add per-token. + * + * there may never be more than just control flag, in which case it may make sense to collapse this + * to just a mapping(uint256 => bool) ? */ - function mint(address _to, uint256 _entityId, bool _control) external returns (uint256); + struct TokenRecord { + + /** position of the tokenId in EntityRecord.tokenIds */ + uint32 tokenIdIdx; + + /** true if this token has administrative superpowers (aka is _not_ limited) */ + bool control; + + /** true if this token cannot move */ + bool finalized; + + // 30 bits more in this slot - /** @notice shortcut to mint() + setYes() in one call, for a single country */ - function mint(address _to, uint256 _entityId, bool _control, uint16 _countryCode, uint8[] _yes) external returns (uint256); + // limitations: in/out? + } - /** @notice destroys a specific token */ - function burn(uint256 _tokenId) external; + address public ownerAddress; - /** @notice destroys the entire entity and all tokens */ - function burnEntity(uint256 _entityId) external; + mapping(uint256 => TokenRecord) public tokenRecordById; + mapping(uint256 => EntityRecord) public entityRecordById; + mapping(uint256 => uint256) public entityIdByTokenId; + + /** for entity enumeration. maximum of 2^256-1 total entities (i think we'll be ok) */ + uint256[] entityIds; + + constructor() public { + /* this space intentionally left blank */ + } /** - * @notice adds a specific attestations (yes) to an entity. idempotent: will return normally even if the mark - * was already set by this validator + * constructor alternative: first-time initialization the contract/token (required because of upgradeability) */ - function setYes(uint256 _entityId, uint16 _countryCode, uint8 _yes) external; + function initialize(string _name, string _symbol) { + // require(super._symbol.length == 0 || _symbol == super._symbol); // cannot change symbol after first init bc that could fuck shit up + _upgradeable_initialize(); // basically for security + super.initialize(_name, _symbol); // init token info + + // grant the owner token + mint_I(ownerAddress, OWNER_ENTITY_ID, true); + + // ecosystem owner gets both owner and validator marks (self-attested) + setYes_I(OWNER_ENTITY_ID, OWNER_ENTITY_ID, 0, YESMARK_OWNER); + setYes_I(OWNER_ENTITY_ID, OWNER_ENTITY_ID, 0, YESMARK_VALIDATOR); + } /** - * @notice removes a attestation(s) from a specific validator for an entity. idempotent + * executed in lieu of a constructor in a delegated context */ - function clearYes(uint256 _entityId, uint16 _countryCode, uint8 _yes) external; + function _upgradeable_initialize() public { + super._upgradeable_initialize(); // provides require(msg.sender == _upgradeable_delegate_owner); + + // some things are still tied to the owner (instead of the yesmark_owner :notsureif:) + ownerAddress = msg.sender; + } + + // YesComplianceTokenV1 Interface Methods -------------------------------------------------------------------------- + + function isYes(uint256 _validatorEntityId, address _address, uint16 _countryCode, uint8 _yes) external view returns(bool) { + return isYes_I(_validatorEntityId, _address, _countryCode, _yes); + } + + function requireYes(uint256 _validatorEntityId, address _address, uint16 _countryCode, uint8 _yes) external view { + require(isYes_I(_validatorEntityId, _address, _countryCode, _yes)); + } + + function getYes(uint256 _validatorEntityId, address _address, uint16 _countryCode) external view returns(uint8[] memory) { + if(balanceOf(_address) == 0) + return new uint8[](0); + + uint256 entityId = entityIdByTokenId[tokenOfOwnerByIndex(_address, 0)]; + EntityRecord storage e = entityRecordById[entityId]; + uint256 j = 0; + uint256 i; + + // locked always bails + if(e.locked) + return new uint8[](0); + + uint8[] memory r = new uint8[](e.yesMarkKeys.length); + + for(i = 0; i < e.yesMarkKeys.length; i++) { + YesMark storage m = e.yesMarkByKey[e.yesMarkKeys[i]]; + + // filter country code + if(m.countryCode != _countryCode) + continue; + + // filter explicit validator entity + if(_validatorEntityId > 0 + && m.validatorEntityIdIdx[_validatorEntityId] == 0 + && (m.validatorEntityIds.length == 0 || m.validatorEntityIds[0] == _validatorEntityId)) + continue; + + // matched, chyess + r[j++] = m.yes; + } + + // reduce array length + assembly { mstore(r, j) } + + return r; + } + + function mint(address _to, uint256 _entityId, bool _control) external returns (uint256) /* internally protected */{ + uint256 callerTokenId = tokenOfOwnerByIndex(msg.sender, 0); + uint256 callerEntityId = entityIdByTokenId[callerTokenId]; + + // make sure caller has a control token, at the least + require(tokenRecordById[callerTokenId].control, 'control token required'); + + // determine/validate the entity being minted for + uint256 realEntityId; + if(_entityId == 0 || _entityId == callerEntityId) { + // unspecified entity, or caller entity, can do! + realEntityId = callerEntityId; + + } else { + // otherwise make sure caller is a VALIDATOR, else fail + require(senderIsControlValidator(), 'illegal entity id'); // some duplicate checks/lookups, gas leak + realEntityId = _entityId; + } + + return mint_I(_to, realEntityId, _control); + } + + function mint(address _to, uint256 _entityId, bool _control, uint16 _countryCode, uint8[] _yes) external returns (uint256) /* internally protected */ { + // lazy warning: this is a 90% copy/paste job from the mint directly above this + + uint256 callerTokenId = tokenOfOwnerByIndex(msg.sender, 0); + uint256 callerEntityId = entityIdByTokenId[callerTokenId]; + + // make sure caller has a control token, at the least + require(tokenRecordById[callerTokenId].control, 'control token required'); + + // determine/validate the entity being minted for + uint256 realEntityId; + if(_entityId == 0 || _entityId == callerEntityId) { + // unspecified entity, or caller entity, can do! + realEntityId = callerEntityId; + + } else { + // otherwise make sure caller is a VALIDATOR, else fail + require(senderIsControlValidator()); // some duplicate checks/lookups, gas leak + realEntityId = _entityId; + } + + // mint the coin + uint256 tokenId = mint_I(_to, realEntityId, _control); + + // now set the attestations + require(_yes.length <= TOTAL_YES_MARKS); // safety + for(uint256 i = 0; i<_yes.length; i++) { + setYes_I(_entityId, _countryCode, _yes[i]); + } + + return tokenId; + } + + function getEntityId(address _address) external view returns (uint256) { + return entityIdByTokenId[tokenOfOwnerByIndex(_address, 0)]; + } + + function burn(uint256 _tokenId) external permission_control_tokenId(_tokenId) { + uint256 entityId = entityIdByTokenId[_tokenId]; + + EntityRecord storage e = entity(entityId); + TokenRecord storage t = tokenRecordById[_tokenId]; + + // remove token from entity + e.tokenIds[t.tokenIdIdx] = e.tokenIds[e.tokenIds.length - 1]; + e.tokenIds.length--; + + // update tracked index (of swapped, if present) + if(e.tokenIds.length > t.tokenIdIdx) + tokenRecordById[e.tokenIds[t.tokenIdIdx]].tokenIdIdx = t.tokenIdIdx; + + // remove token record + delete tokenRecordById[_tokenId]; + + // burn the actual token + super._burn(tokenOwner[_tokenId], _tokenId); + } + + function burnEntity(uint256 _entityId) external permission_control_entityId(_entityId) { // self-burn allowed + EntityRecord storage e = entity(_entityId); + + // burn all the tokens + for(uint256 i = 0; i < e.tokenIds.length; i++) { + uint256 tokenId = e.tokenIds[i]; + super._burn(tokenOwner[tokenId], tokenId); + } + + // clear all the marks + clearYes_I(_entityId); + + // clear out entity record + e.init = false; + e.locked = false; + e.entityIdIdx = 0; + e.tokenIdCounter = 0; + + assert(e.yesMarkKeys.length == 0); + assert(e.tokenIds.length == 0); + } + + function setYes(uint256 _entityId, uint16 _countryCode, uint8 _yes) external permission_validator { + setYes_I(_entityId, _countryCode, _yes); + } + + function clearYes(uint256 _entityId, uint16 _countryCode, uint8 _yes) external permission_validator { + require(_yes > 0); + require(_yes != 128); + + // special check against reserved country code 0 + if(_countryCode == 0) + require(senderIsEcosystemControl(), 'not authorized as ecosystem control'); // this is duplicating some things, gas leak + + EntityRecord storage e = entity(_entityId); + + uint256 callerTokenId = tokenOfOwnerByIndex(msg.sender, 0); + uint256 callerEntityId = entityIdByTokenId[callerTokenId]; + bytes4 key = yesKey(_countryCode, _yes); + + YesMark storage mark = e.yesMarkByKey[key]; + if(mark.yes == 0) + return; // not set by anyone, bail happily + + if(mark.validatorEntityIdIdx[callerEntityId] == 0 && + (mark.validatorEntityIds.length == 0 || mark.validatorEntityIds[0] != callerEntityId)) { + // set, but not by this validator, bail happily + return; + } + + clearYes_I(mark, e, callerEntityId); + } + + function clearYes(uint256 _entityId, uint16 _countryCode) external permission_validator { + // special check against 129 validator mark + if(_countryCode == 0) + require(senderIsEcosystemControl(), 'not authorized as ecosystem control (129)'); // this is duplicating some things, gas leak + + EntityRecord storage e = entity(_entityId); + + uint256 callerTokenId = tokenOfOwnerByIndex(msg.sender, 0); + uint256 callerEntityId = entityIdByTokenId[callerTokenId]; + uint256 i; + + for(i =0; i idx) + mark.validatorEntityIdIdx[mark.validatorEntityIds[idx]] = idx; + + // check if the entire mark needs deleting + if(mark.validatorEntityIds.length == 0) { + // yes, it does. swap/delete + idx = mark.yesMarkIdx; + e.yesMarkKeys[idx] = e.yesMarkKeys[e.yesMarkKeys.length - 1]; + e.yesMarkKeys.length--; + + // remap + if(e.yesMarkKeys.length > idx) + e.yesMarkByKey[e.yesMarkKeys[idx]].yesMarkIdx = idx; + + // delete mark + mark.countryCode = 0; + mark.yes = 0; + mark.yesMarkIdx = 0; + // assert(mark.validatorEntityIds.length == 0); + + return true; + } + + return false; + } + + function clearYes_I(uint256 _entityId) internal { + require(_entityId != OWNER_ENTITY_ID); + + EntityRecord storage e = entity(_entityId); + + // only ecosystem control can touch validators + if(!senderIsEcosystemControl()) + require(e.yesMarkByKey[yesKey(0, YESMARK_VALIDATOR)].yes == 0); + + uint256 callerTokenId = tokenOfOwnerByIndex(msg.sender, 0); + uint256 callerEntityId = entityIdByTokenId[callerTokenId]; + uint256 i; + + for(i =0; i 0 + || m.validatorEntityIds.length > 0 && m.validatorEntityIds[0] == _validatorEntityId; + } + + function setYes_I(uint256 _entityId, uint16 _countryCode, uint8 _yes) internal { + require(_yes > 0); + require(_yes != 128); + + // special check against 129 validator mark + if(_yes == 129) + require(senderIsEcosystemControl()); // this is duplicating some checks, gas leak + + uint256 callerTokenId = tokenOfOwnerByIndex(msg.sender, 0); + uint256 callerEntityId = entityIdByTokenId[callerTokenId]; + + setYes_I(callerEntityId, _entityId, _countryCode, _yes); + } + + function setYes_I(uint256 _validatorEntityId, uint256 _entityId, uint16 _countryCode, uint8 _yes) internal { + // assert(_yes > 0); + EntityRecord storage targetEntity = entity(_entityId); + + // locate existing mark + bytes4 key = yesKey(_countryCode, _yes); + YesMark storage mark = targetEntity.yesMarkByKey[key]; + + if(mark.yes == 0) { + require(targetEntity.yesMarkKeys.length < TOTAL_YES_MARKS); + + // new mark on the entity + mark.countryCode = _countryCode; + mark.yes = _yes; + mark.yesMarkIdx = uint32(targetEntity.yesMarkKeys.length); + targetEntity.yesMarkKeys.push(key); + + } else if(mark.validatorEntityIdIdx[_validatorEntityId] > 0 || + (mark.validatorEntityIds.length > 0 && mark.validatorEntityIds[0] == _validatorEntityId)) { + + // existing mark and the caller is already on it + /* + i'm inclined to make it do nothing in this case (instead of failing) since i'm not at this point positive how best + to distinguish error types to a caller, which would be required for a caller to know wtf to do in this case + (otherwise they need to query blockchain again) + (but that costs gas... :notsureif:) + */ + return; + } + + require(mark.validatorEntityIds.length < MAX_VALIDATORS_PER_MARK); + + // add this validator to the mark + mark.validatorEntityIdIdx[_validatorEntityId] = uint32(mark.validatorEntityIds.length); + mark.validatorEntityIds.push(_validatorEntityId); + } + + /** non-permissed internal minting impl */ + function mint_I(address _to, uint256 _entityId, bool _control) internal returns (uint256) { + EntityRecord storage e = entity(_entityId); + require(e.tokenIds.length < MAX_TOKENS_PER_ENTITY, 'token limit reached'); + require(e.tokenIdCounter < 2**64-1); // kind of ridiculous but whatever, safety first! + uint256 tokenId = uint256(keccak256(abi.encodePacked(_entityId, e.tokenIdCounter++))); + super._mint(_to, tokenId); + tokenRecordById[tokenId].tokenIdIdx = uint32(e.tokenIds.length); + tokenRecordById[tokenId].control = _control; + e.tokenIds.push(tokenId); + entityIdByTokenId[tokenId] = _entityId; + return tokenId; + } + + /** entity resolution (creation when needed) */ + function entity(uint256 _entityId) internal returns (EntityRecord storage) { + require(_entityId > 0); + EntityRecord storage e = entityRecordById[_entityId]; + if(e.init) return e; + require(entityIds.length < MAX_ENTITIES); + e.init = true; + e.entityIdIdx = uint32(entityIds.length); + entityIds.push(_entityId); + return e; + } + + /** override default addTokenTo for additional transaction limitations */ + function addTokenTo(address _to, uint256 _tokenId) internal { + uint256 entityId = entityIdByTokenId[_tokenId]; + + // ensure one owner cannot be associated with multiple entities + // NOTE: this breaks hotwallet integrations, at this point necessarily so + if(balanceOf(_to) > 0) { + uint256 prevEntityId = entityIdByTokenId[tokenOfOwnerByIndex(_to, 0)]; + require(prevEntityId == entityId, 'conflicting entities'); + } + + require(!tokenRecordById[_tokenId].finalized, 'token is finalized'); + + super.addTokenTo(_to, _tokenId); + } + + /** the sender is the same entity as the one specified */ + function senderIsEntity_ByEntityId(uint256 _entityId) internal view returns (bool) { + return _entityId == entityIdByTokenId[tokenOfOwnerByIndex(msg.sender, 0)]; + } + + /** the sender is the same entity as the one specified, and the sender is a control for that entity */ + function senderIsControl_ByEntityId(uint256 _entityId) internal view returns (bool) { + if(balanceOf(msg.sender) == 0) + return false; + uint256 tokenId = tokenOfOwnerByIndex(msg.sender, 0); + uint256 senderEntityId = entityIdByTokenId[tokenId]; + return _entityId == senderEntityId && tokenRecordById[tokenId].control; + } + + /** the sender is a non-locked validator via control token */ + function senderIsControlValidator() internal view returns (bool) { + if(balanceOf(msg.sender) == 0) + return false; + uint256 tokenId = tokenOfOwnerByIndex(msg.sender, 0); + uint256 senderEntityId = entityIdByTokenId[tokenId]; + EntityRecord storage e = entityRecordById[senderEntityId]; + return tokenRecordById[tokenId].control + && !e.locked + && entityRecordById[senderEntityId].yesMarkByKey[yesKey(0, YESMARK_VALIDATOR)].yes > 0; + } + + /** the sender is the same entity as the one tied to the token specified */ + function senderIsEntity_ByTokenId(uint256 _tokenId) internal view returns (bool) { + if(balanceOf(msg.sender) == 0) + return false; + return entityIdByTokenId[_tokenId] == entityIdByTokenId[tokenOfOwnerByIndex(msg.sender, 0)]; + } + + /** the sender is the same entity as the one tied to the token specified, and the sender is a control for that entity */ + function senderIsControl_ByTokenId(uint256 _tokenId) internal view returns (bool) { + if(balanceOf(msg.sender) == 0) + return false; + uint256 senderEntityId = entityIdByTokenId[tokenOfOwnerByIndex(msg.sender, 0)]; + return entityIdByTokenId[_tokenId] == senderEntityId && tokenRecordById[_tokenId].control; + } + + /** checks if sender is the singular ecosystem owner */ + function senderIsEcosystemControl() internal view returns (bool) { + // todo deprecate ownerAddress ?! + return msg.sender == ownerAddress || senderIsControl_ByEntityId(OWNER_ENTITY_ID); + } + + /** a key for a YES attestation mark */ + function yesKey(uint16 _countryCode, uint8 _yes) internal pure returns(bytes4) { + return bytes4(keccak256(abi.encodePacked(_countryCode, _yes))); + } - /** @notice removes all attestations in a given country for a particular entity. idempotent */ - function clearYes(uint256 _entityId, uint16 _countryCode) external; + // PERMISSIONS MODIFIERS ---------------------------------------------------------------- - /** @notice removes all attestations for a particular entity. idempotent */ - function clearYes(uint256 _entityId) external; + modifier permission_validator { + require(senderIsControlValidator(), 'not authorized as validator'); + _; + } - /** @notice assigns a lock to an entity, rendering all isYes queries false. idempotent */ - function setLocked(uint256 _entityId, bool _lock) external; + modifier permission_super { + require(senderIsEcosystemControl(), 'not authorized as ecosystem control'); + _; + } - /** @notice checks whether or not a particular entity is locked */ - function isLocked(uint256 _entityId) external view returns(bool); +// modifier permission_access_entityId(uint256 _entityId) { +// require(senderIsEcosystemControl() || senderIsEntity_ByEntityId(_entityId)); +// _; +// } - /** @notice returns true if the specified token has been finalized (cannot be moved) */ - function isFinalized(uint256 _tokenId) external view returns(bool); + modifier permission_control_entityId(uint256 _entityId) { + require(senderIsEcosystemControl() || senderIsControl_ByEntityId(_entityId), 'not authorized entity controller'); + _; + } - /** @notice finalizes a token by ID preventing it from getting moved. idempotent */ - function finalize(uint256 _tokenId) external; + modifier permission_access_tokenId(uint256 _tokenId) { + require(senderIsEcosystemControl() || senderIsEntity_ByTokenId(_tokenId)); + _; + } - /** @return the entity ID associated with an address (or fail if there is not one) */ - function getEntityId(address _address) external view returns(uint256); + modifier permission_control_tokenId(uint256 _tokenId) { + require(senderIsEcosystemControl() || senderIsControl_ByTokenId(_tokenId), 'not authorized token controller'); + _; + } } \ No newline at end of file diff --git a/packages/contracts/test/extensions/compliant_forwarder.ts b/packages/contracts/test/extensions/compliant_forwarder.ts new file mode 100644 index 000000000..41603e3c2 --- /dev/null +++ b/packages/contracts/test/extensions/compliant_forwarder.ts @@ -0,0 +1,191 @@ +import { BlockchainLifecycle } from '@0x/dev-utils'; +import { assetDataUtils } from '@0x/order-utils'; +import { RevertReason, SignedOrder } from '@0x/types'; +import { BigNumber } from '@0x/utils'; +import { Web3Wrapper } from '@0x/web3-wrapper'; +import * as chai from 'chai'; +import { TransactionReceiptWithDecodedLogs } from 'ethereum-types'; + +import { DummyERC20TokenContract } from '../../generated-wrappers/dummy_erc20_token'; +import { ExchangeContract } from '../../generated-wrappers/exchange'; +import { DummyYesComplianceContract } from '../../generated-wrappers/forwarder'; +import { WETH9Contract } from '../../generated-wrappers/weth9'; +import { artifacts } from '../../src/artifacts'; +import { + expectContractCreationFailedAsync, + expectTransactionFailedAsync, + sendTransactionResult, +} from '../utils/assertions'; +import { chaiSetup } from '../utils/chai_setup'; +import { constants } from '../utils/constants'; +import { ERC20Wrapper } from '../utils/erc20_wrapper'; +import { ERC721Wrapper } from '../utils/erc721_wrapper'; +import { ExchangeWrapper } from '../utils/exchange_wrapper'; +import { ForwarderWrapper } from '../utils/forwarder_wrapper'; +import { OrderFactory } from '../utils/order_factory'; +import { ContractName, ERC20BalancesByOwner } from '../utils/types'; +import { provider, txDefaults, web3Wrapper } from '../utils/web3_wrapper'; + +chaiSetup.configure(); +const expect = chai.expect; +const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); +const DECIMALS_DEFAULT = 18; +const MAX_WETH_FILL_PERCENTAGE = 95; + +describe(ContractName.Forwarder, () => { + let makerAddress: string; + let owner: string; + let takerAddress: string; + let feeRecipientAddress: string; + let otherAddress: string; + let defaultMakerAssetAddress: string; + let zrxAssetData: string; + let wethAssetData: string; + + let weth: DummyERC20TokenContract; + let zrxToken: DummyERC20TokenContract; + let erc20TokenA: DummyERC20TokenContract; + let erc721Token: DummyERC721TokenContract; + let forwarderContract: ForwarderContract; + let wethContract: WETH9Contract; + let forwarderWrapper: ForwarderWrapper; + let exchangeWrapper: ExchangeWrapper; + + let orderWithoutFee: SignedOrder; + let orderWithFee: SignedOrder; + let feeOrder: SignedOrder; + let orderFactory: OrderFactory; + let erc20Wrapper: ERC20Wrapper; + let erc20Balances: ERC20BalancesByOwner; + let tx: TransactionReceiptWithDecodedLogs; + + let erc721MakerAssetIds: BigNumber[]; + let takerEthBalanceBefore: BigNumber; + let feePercentage: BigNumber; + let gasPrice: BigNumber; + + before(async () => { + await blockchainLifecycle.startAsync(); + const accounts = await web3Wrapper.getAvailableAddressesAsync(); + const usedAddresses = ([owner, makerAddress, takerAddress, feeRecipientAddress, otherAddress] = accounts); + + const txHash = await web3Wrapper.sendTransactionAsync({ from: accounts[0], to: accounts[0], value: 0 }); + const transaction = await web3Wrapper.getTransactionByHashAsync(txHash); + gasPrice = new BigNumber(transaction.gasPrice); + + const erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner); + erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner); + + const numDummyErc20ToDeploy = 3; + [erc20TokenA, zrxToken] = await erc20Wrapper.deployDummyTokensAsync( + numDummyErc20ToDeploy, + constants.DUMMY_TOKEN_DECIMALS, + ); + const erc20Proxy = await erc20Wrapper.deployProxyAsync(); + await erc20Wrapper.setBalancesAndAllowancesAsync(); + + [erc721Token] = await erc721Wrapper.deployDummyTokensAsync(); + const erc721Proxy = await erc721Wrapper.deployProxyAsync(); + await erc721Wrapper.setBalancesAndAllowancesAsync(); + const erc721Balances = await erc721Wrapper.getBalancesAsync(); + erc721MakerAssetIds = erc721Balances[makerAddress][erc721Token.address]; + + wethContract = await WETH9Contract.deployFrom0xArtifactAsync(artifacts.WETH9, provider, txDefaults); + weth = new DummyERC20TokenContract(wethContract.abi, wethContract.address, provider); + erc20Wrapper.addDummyTokenContract(weth); + + wethAssetData = assetDataUtils.encodeERC20AssetData(wethContract.address); + zrxAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address); + const exchangeInstance = await ExchangeContract.deployFrom0xArtifactAsync( + artifacts.Exchange, + provider, + txDefaults, + zrxAssetData, + ); + exchangeWrapper = new ExchangeWrapper(exchangeInstance, provider); + await exchangeWrapper.registerAssetProxyAsync(erc20Proxy.address, owner); + await exchangeWrapper.registerAssetProxyAsync(erc721Proxy.address, owner); + + await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(exchangeInstance.address, { + from: owner, + }); + await erc721Proxy.addAuthorizedAddress.sendTransactionAsync(exchangeInstance.address, { + from: owner, + }); + + defaultMakerAssetAddress = erc20TokenA.address; + const defaultTakerAssetAddress = wethContract.address; + const defaultOrderParams = { + exchangeAddress: exchangeInstance.address, + makerAddress, + feeRecipientAddress, + makerAssetData: assetDataUtils.encodeERC20AssetData(defaultMakerAssetAddress), + takerAssetData: assetDataUtils.encodeERC20AssetData(defaultTakerAssetAddress), + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(200), DECIMALS_DEFAULT), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), DECIMALS_DEFAULT), + makerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT), + takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(0), DECIMALS_DEFAULT), + }; + const privateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddress)]; + orderFactory = new OrderFactory(privateKey, defaultOrderParams); + + const forwarderInstance = await ForwarderContract.deployFrom0xArtifactAsync( + artifacts.Forwarder, + provider, + txDefaults, + exchangeInstance.address, + zrxAssetData, + wethAssetData, + ); + forwarderContract = new ForwarderContract(forwarderInstance.abi, forwarderInstance.address, provider); + forwarderWrapper = new ForwarderWrapper(forwarderContract, provider); + const zrxDepositAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(10000), 18); + await web3Wrapper.awaitTransactionSuccessAsync( + await zrxToken.transfer.sendTransactionAsync(forwarderContract.address, zrxDepositAmount), + ); + erc20Wrapper.addTokenOwnerAddress(forwarderInstance.address); + }); + after(async () => { + await blockchainLifecycle.revertAsync(); + }); + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + erc20Balances = await erc20Wrapper.getBalancesAsync(); + takerEthBalanceBefore = await web3Wrapper.getBalanceInWeiAsync(takerAddress); + orderWithoutFee = await orderFactory.newSignedOrderAsync(); + feeOrder = await orderFactory.newSignedOrderAsync({ + makerAssetData: assetDataUtils.encodeERC20AssetData(zrxToken.address), + takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT), + }); + orderWithFee = await orderFactory.newSignedOrderAsync({ + takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT), + }); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + }); + + describe('constructor', () => { + it('should revert if assetProxy is unregistered', async () => { + const exchangeInstance = await ExchangeContract.deployFrom0xArtifactAsync( + artifacts.Exchange, + provider, + txDefaults, + zrxAssetData, + ); + return expectContractCreationFailedAsync( + (ForwarderContract.deployFrom0xArtifactAsync( + artifacts.Forwarder, + provider, + txDefaults, + exchangeInstance.address, + zrxAssetData, + wethAssetData, + ) as any) as sendTransactionResult, + RevertReason.UnregisteredAssetProxy, + ); + }); + }); +}); +// tslint:disable:max-file-line-count +// tslint:enable:no-unnecessary-type-assertion -- cgit From 88595718c3f78d3facbd4ac67ba8328ce9b2bc8a Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Wed, 28 Nov 2018 15:37:11 -0800 Subject: Yes Compliance Token --- packages/contracts/contracts/tokens/README.md | 2 ++ .../YesComplianceToken/IYesComplianceToken.sol | 3 +- .../WyreERC721Token/ERC721BasicToken.sol | 32 ++++++++-------------- .../WyreERC721Token/ERC721Token.sol | 15 ++-------- .../YesComplianceToken/YesComplianceToken.sol | 2 -- .../test/extensions/compliant_forwarder.ts | 4 ++- 6 files changed, 21 insertions(+), 37 deletions(-) create mode 100644 packages/contracts/contracts/tokens/README.md (limited to 'packages') diff --git a/packages/contracts/contracts/tokens/README.md b/packages/contracts/contracts/tokens/README.md new file mode 100644 index 000000000..b54f0e046 --- /dev/null +++ b/packages/contracts/contracts/tokens/README.md @@ -0,0 +1,2 @@ +Contracts from https://github.com/sendwyre/yes-compliance-token +Modified to compile in our codebase. diff --git a/packages/contracts/contracts/tokens/YesComplianceToken/IYesComplianceToken.sol b/packages/contracts/contracts/tokens/YesComplianceToken/IYesComplianceToken.sol index 1573c6bac..a1c9b9671 100644 --- a/packages/contracts/contracts/tokens/YesComplianceToken/IYesComplianceToken.sol +++ b/packages/contracts/contracts/tokens/YesComplianceToken/IYesComplianceToken.sol @@ -1,5 +1,6 @@ pragma solidity ^0.4.24; +import "./WyreERC721Token/ERC721Token.sol"; /** * @notice an ERC721 "yes" compliance token supporting a collection of country-specific attributions which answer specific @@ -30,7 +31,7 @@ pragma solidity ^0.4.24; * * any (non-view) methods not explicitly marked idempotent are not idempotent. */ -contract YesComplianceTokenV1 /*is ERC721Token*/ /*, ERC165 :should: */ { +contract YesComplianceTokenV1 is ERC721Token /*, ERC165 :should: */ { uint256 public constant OWNER_ENTITY_ID = 1; diff --git a/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721BasicToken.sol b/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721BasicToken.sol index 1d3fa37b8..788e31580 100644 --- a/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721BasicToken.sol +++ b/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721BasicToken.sol @@ -1,17 +1,15 @@ pragma solidity ^0.4.21; import "./ERC721Basic.sol"; -import "./ERC721Receiver.sol"; -import "../../math/SafeMath.sol"; -import "../../AddressUtils.sol"; -import "../../introspection/ERC165Support.sol"; +import "../../ERC721Token/IERC721Receiver.sol"; +import "../../../utils/SafeMath/SafeMath.sol"; /** * @title ERC721 Non-Fungible Token Standard basic implementation * @dev see https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md */ -contract ERC721BasicToken is ERC165Support, ERC721Basic { +contract ERC721BasicToken is ERC721Basic, SafeMath { bytes4 private constant InterfaceId_ERC721 = 0x80ac58cd; /* @@ -33,9 +31,6 @@ contract ERC721BasicToken is ERC165Support, ERC721Basic { * bytes4(keccak256('exists(uint256)')) */ - using SafeMath for uint256; - using AddressUtils for address; - // Equals to `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))` // which can be also obtained as `ERC721Receiver(0).onERC721Received.selector` bytes4 private constant ERC721_RECEIVED = 0x150b7a02; @@ -70,15 +65,6 @@ contract ERC721BasicToken is ERC165Support, ERC721Basic { _; } - function _supportsInterface(bytes4 _interfaceId) - internal - view - returns (bool) - { - return super._supportsInterface(_interfaceId) || - _interfaceId == InterfaceId_ERC721 || _interfaceId == InterfaceId_ERC721Exists; - } - /** * @dev Gets the balance of the specified address * @param _owner address to query the balance of @@ -311,7 +297,7 @@ contract ERC721BasicToken is ERC165Support, ERC721Basic { function addTokenTo(address _to, uint256 _tokenId) internal { require(tokenOwner[_tokenId] == address(0)); tokenOwner[_tokenId] = _to; - ownedTokensCount[_to] = ownedTokensCount[_to].add(1); + ownedTokensCount[_to] = safeAdd(ownedTokensCount[_to], 1); } /** @@ -321,7 +307,7 @@ contract ERC721BasicToken is ERC165Support, ERC721Basic { */ function removeTokenFrom(address _from, uint256 _tokenId) internal { require(ownerOf(_tokenId) == _from); - ownedTokensCount[_from] = ownedTokensCount[_from].sub(1); + ownedTokensCount[_from] = safeSub(ownedTokensCount[_from], 1); tokenOwner[_tokenId] = address(0); } @@ -343,10 +329,14 @@ contract ERC721BasicToken is ERC165Support, ERC721Basic { internal returns (bool) { - if (!_to.isContract()) { + uint256 receiverCodeSize; + assembly { + receiverCodeSize := extcodesize(_to) + } + if (receiverCodeSize == 0) { return true; } - bytes4 retval = ERC721Receiver(_to).onERC721Received( + bytes4 retval = IERC721Receiver(_to).onERC721Received( msg.sender, _from, _tokenId, _data); return (retval == ERC721_RECEIVED); } diff --git a/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721Token.sol b/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721Token.sol index c7acee6df..832ff3784 100644 --- a/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721Token.sol +++ b/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721Token.sol @@ -52,20 +52,11 @@ contract ERC721Token is ERC721BasicToken, ERC721 { /** * @dev Constructor function */ - function initialize(string _name, string _symbol) public isInitializer("ERC721Token", "1.9.0") { + function initialize(string _name, string _symbol) public { name_ = _name; symbol_ = _symbol; } - function _supportsInterface(bytes4 _interfaceId) - internal - view - returns (bool) - { - return super._supportsInterface(_interfaceId) || - _interfaceId == InterfaceId_ERC721Enumerable || _interfaceId == InterfaceId_ERC721Metadata; - } - /** * @dev Gets the token name * @return string representing the token name @@ -161,7 +152,7 @@ contract ERC721Token is ERC721BasicToken, ERC721 { super.removeTokenFrom(_from, _tokenId); uint256 tokenIndex = ownedTokensIndex[_tokenId]; - uint256 lastTokenIndex = ownedTokens[_from].length.sub(1); + uint256 lastTokenIndex = safeSub(ownedTokens[_from].length, 1); uint256 lastToken = ownedTokens[_from][lastTokenIndex]; ownedTokens[_from][tokenIndex] = lastToken; @@ -204,7 +195,7 @@ contract ERC721Token is ERC721BasicToken, ERC721 { // Reorg all tokens array uint256 tokenIndex = allTokensIndex[_tokenId]; - uint256 lastTokenIndex = allTokens.length.sub(1); + uint256 lastTokenIndex = safeSub(allTokens.length, 1); uint256 lastToken = allTokens[lastTokenIndex]; allTokens[tokenIndex] = lastToken; diff --git a/packages/contracts/contracts/tokens/YesComplianceToken/YesComplianceToken.sol b/packages/contracts/contracts/tokens/YesComplianceToken/YesComplianceToken.sol index b810d9f31..65ea99d0c 100644 --- a/packages/contracts/contracts/tokens/YesComplianceToken/YesComplianceToken.sol +++ b/packages/contracts/contracts/tokens/YesComplianceToken/YesComplianceToken.sol @@ -118,7 +118,6 @@ contract YesComplianceToken is YesComplianceTokenV1 { */ function initialize(string _name, string _symbol) { // require(super._symbol.length == 0 || _symbol == super._symbol); // cannot change symbol after first init bc that could fuck shit up - _upgradeable_initialize(); // basically for security super.initialize(_name, _symbol); // init token info // grant the owner token @@ -133,7 +132,6 @@ contract YesComplianceToken is YesComplianceTokenV1 { * executed in lieu of a constructor in a delegated context */ function _upgradeable_initialize() public { - super._upgradeable_initialize(); // provides require(msg.sender == _upgradeable_delegate_owner); // some things are still tied to the owner (instead of the yesmark_owner :notsureif:) ownerAddress = msg.sender; diff --git a/packages/contracts/test/extensions/compliant_forwarder.ts b/packages/contracts/test/extensions/compliant_forwarder.ts index 41603e3c2..d26bbd8ec 100644 --- a/packages/contracts/test/extensions/compliant_forwarder.ts +++ b/packages/contracts/test/extensions/compliant_forwarder.ts @@ -1,3 +1,4 @@ +/* import { BlockchainLifecycle } from '@0x/dev-utils'; import { assetDataUtils } from '@0x/order-utils'; import { RevertReason, SignedOrder } from '@0x/types'; @@ -8,7 +9,7 @@ import { TransactionReceiptWithDecodedLogs } from 'ethereum-types'; import { DummyERC20TokenContract } from '../../generated-wrappers/dummy_erc20_token'; import { ExchangeContract } from '../../generated-wrappers/exchange'; -import { DummyYesComplianceContract } from '../../generated-wrappers/forwarder'; + import { WETH9Contract } from '../../generated-wrappers/weth9'; import { artifacts } from '../../src/artifacts'; import { @@ -189,3 +190,4 @@ describe(ContractName.Forwarder, () => { }); // tslint:disable:max-file-line-count // tslint:enable:no-unnecessary-type-assertion +*/ \ No newline at end of file -- cgit From 0e0e05e0e07aca3cfbfd13b3fdc00183b7ac5a87 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Wed, 28 Nov 2018 16:05:18 -0800 Subject: Compile Compliant Forwarder contract --- .../contracts/extensions/CompliantForwarder/CompliantForwarder.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol index 646a9f3b8..2febc5cce 100644 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -76,7 +76,7 @@ contract CompliantForwarder { * takerAssetData * signature ------------------------------ - * Context-dependent offsets, unknown at compile time. + * Context-dependent offsets; unknown at compile time. */ // Add 0x4 to a given offset to account for the fillOrder selector prepended to `signedFillOrderTransaction`. // Add 0xc to the makerAddress since abi-encoded addresses are left padded with 12 bytes. -- cgit From c854c99f20e399bd36f86f69738913c8e5819a9f Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Wed, 28 Nov 2018 16:16:55 -0800 Subject: template for Compliant Forwarder tests --- .../test/extensions/compliant_forwarder.ts | 59 +++++++--------------- .../test/utils/compliant_forwarder_wrapper.ts | 0 2 files changed, 18 insertions(+), 41 deletions(-) create mode 100644 packages/contracts/test/utils/compliant_forwarder_wrapper.ts (limited to 'packages') diff --git a/packages/contracts/test/extensions/compliant_forwarder.ts b/packages/contracts/test/extensions/compliant_forwarder.ts index d26bbd8ec..bb9493aaa 100644 --- a/packages/contracts/test/extensions/compliant_forwarder.ts +++ b/packages/contracts/test/extensions/compliant_forwarder.ts @@ -1,4 +1,3 @@ -/* import { BlockchainLifecycle } from '@0x/dev-utils'; import { assetDataUtils } from '@0x/order-utils'; import { RevertReason, SignedOrder } from '@0x/types'; @@ -9,6 +8,7 @@ import { TransactionReceiptWithDecodedLogs } from 'ethereum-types'; import { DummyERC20TokenContract } from '../../generated-wrappers/dummy_erc20_token'; import { ExchangeContract } from '../../generated-wrappers/exchange'; +import { CompliantForwarderContract } from '../../generated-wrappers/compliant_forwarder'; import { WETH9Contract } from '../../generated-wrappers/weth9'; import { artifacts } from '../../src/artifacts'; @@ -46,8 +46,7 @@ describe(ContractName.Forwarder, () => { let weth: DummyERC20TokenContract; let zrxToken: DummyERC20TokenContract; let erc20TokenA: DummyERC20TokenContract; - let erc721Token: DummyERC721TokenContract; - let forwarderContract: ForwarderContract; + let compliantForwarderContract: CompliantForwarderContract; let wethContract: WETH9Contract; let forwarderWrapper: ForwarderWrapper; let exchangeWrapper: ExchangeWrapper; @@ -85,12 +84,6 @@ describe(ContractName.Forwarder, () => { const erc20Proxy = await erc20Wrapper.deployProxyAsync(); await erc20Wrapper.setBalancesAndAllowancesAsync(); - [erc721Token] = await erc721Wrapper.deployDummyTokensAsync(); - const erc721Proxy = await erc721Wrapper.deployProxyAsync(); - await erc721Wrapper.setBalancesAndAllowancesAsync(); - const erc721Balances = await erc721Wrapper.getBalancesAsync(); - erc721MakerAssetIds = erc721Balances[makerAddress][erc721Token.address]; - wethContract = await WETH9Contract.deployFrom0xArtifactAsync(artifacts.WETH9, provider, txDefaults); weth = new DummyERC20TokenContract(wethContract.abi, wethContract.address, provider); erc20Wrapper.addDummyTokenContract(weth); @@ -105,14 +98,10 @@ describe(ContractName.Forwarder, () => { ); exchangeWrapper = new ExchangeWrapper(exchangeInstance, provider); await exchangeWrapper.registerAssetProxyAsync(erc20Proxy.address, owner); - await exchangeWrapper.registerAssetProxyAsync(erc721Proxy.address, owner); await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(exchangeInstance.address, { from: owner, }); - await erc721Proxy.addAuthorizedAddress.sendTransactionAsync(exchangeInstance.address, { - from: owner, - }); defaultMakerAssetAddress = erc20TokenA.address; const defaultTakerAssetAddress = wethContract.address; @@ -130,21 +119,28 @@ describe(ContractName.Forwarder, () => { const privateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddress)]; orderFactory = new OrderFactory(privateKey, defaultOrderParams); - const forwarderInstance = await ForwarderContract.deployFrom0xArtifactAsync( - artifacts.Forwarder, + const compliantForwarderInstance = await CompliantForwarderContract.deployFrom0xArtifactAsync( + artifacts.CompliantForwarder, provider, txDefaults, exchangeInstance.address, - zrxAssetData, - wethAssetData, + exchangeInstance.address, // @TODO CHANGE to Yes Token ); - forwarderContract = new ForwarderContract(forwarderInstance.abi, forwarderInstance.address, provider); - forwarderWrapper = new ForwarderWrapper(forwarderContract, provider); + + compliantForwarderContract = new CompliantForwarderContract( + compliantForwarderInstance.abi, + compliantForwarderInstance.address, + provider, + ); + /* + forwarderWrapper = new ForwarderWrapper(compliantForwarderContract, provider); + */ + const zrxDepositAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(10000), 18); await web3Wrapper.awaitTransactionSuccessAsync( - await zrxToken.transfer.sendTransactionAsync(forwarderContract.address, zrxDepositAmount), + await zrxToken.transfer.sendTransactionAsync(compliantForwarderContract.address, zrxDepositAmount), ); - erc20Wrapper.addTokenOwnerAddress(forwarderInstance.address); + erc20Wrapper.addTokenOwnerAddress(compliantForwarderInstance.address); }); after(async () => { await blockchainLifecycle.revertAsync(); @@ -167,27 +163,8 @@ describe(ContractName.Forwarder, () => { }); describe('constructor', () => { - it('should revert if assetProxy is unregistered', async () => { - const exchangeInstance = await ExchangeContract.deployFrom0xArtifactAsync( - artifacts.Exchange, - provider, - txDefaults, - zrxAssetData, - ); - return expectContractCreationFailedAsync( - (ForwarderContract.deployFrom0xArtifactAsync( - artifacts.Forwarder, - provider, - txDefaults, - exchangeInstance.address, - zrxAssetData, - wethAssetData, - ) as any) as sendTransactionResult, - RevertReason.UnregisteredAssetProxy, - ); - }); + it('should revert if assetProxy is unregistered', async () => {}); }); }); // tslint:disable:max-file-line-count // tslint:enable:no-unnecessary-type-assertion -*/ \ No newline at end of file diff --git a/packages/contracts/test/utils/compliant_forwarder_wrapper.ts b/packages/contracts/test/utils/compliant_forwarder_wrapper.ts new file mode 100644 index 000000000..e69de29bb -- cgit From b4aca370defb9dfe6c01b60d1b522d4a7b731f43 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Wed, 28 Nov 2018 18:09:50 -0800 Subject: Writing tests for Compliant Forwarder --- .../test/extensions/compliant_forwarder.ts | 172 +++++++++++++++------ 1 file changed, 125 insertions(+), 47 deletions(-) (limited to 'packages') diff --git a/packages/contracts/test/extensions/compliant_forwarder.ts b/packages/contracts/test/extensions/compliant_forwarder.ts index bb9493aaa..9100c32f8 100644 --- a/packages/contracts/test/extensions/compliant_forwarder.ts +++ b/packages/contracts/test/extensions/compliant_forwarder.ts @@ -9,6 +9,7 @@ import { TransactionReceiptWithDecodedLogs } from 'ethereum-types'; import { DummyERC20TokenContract } from '../../generated-wrappers/dummy_erc20_token'; import { ExchangeContract } from '../../generated-wrappers/exchange'; import { CompliantForwarderContract } from '../../generated-wrappers/compliant_forwarder'; +import { YesComplianceTokenContract } from '../../generated-wrappers/yes_compliance_token'; import { WETH9Contract } from '../../generated-wrappers/weth9'; import { artifacts } from '../../src/artifacts'; @@ -24,7 +25,9 @@ import { ERC721Wrapper } from '../utils/erc721_wrapper'; import { ExchangeWrapper } from '../utils/exchange_wrapper'; import { ForwarderWrapper } from '../utils/forwarder_wrapper'; import { OrderFactory } from '../utils/order_factory'; -import { ContractName, ERC20BalancesByOwner } from '../utils/types'; +import { orderUtils } from '../utils/order_utils'; +import { TransactionFactory } from '../utils/transaction_factory'; +import { ContractName, ERC20BalancesByOwner, SignedTransaction } from '../utils/types'; import { provider, txDefaults, web3Wrapper } from '../utils/web3_wrapper'; chaiSetup.configure(); @@ -33,12 +36,12 @@ const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); const DECIMALS_DEFAULT = 18; const MAX_WETH_FILL_PERCENTAGE = 95; -describe(ContractName.Forwarder, () => { - let makerAddress: string; +describe.only(ContractName.Forwarder, () => { + let compliantMakerAddress: string; let owner: string; - let takerAddress: string; + let compliantTakerAddress: string; let feeRecipientAddress: string; - let otherAddress: string; + let noncompliantAddress: string; let defaultMakerAssetAddress: string; let zrxAssetData: string; let wethAssetData: string; @@ -46,6 +49,7 @@ describe(ContractName.Forwarder, () => { let weth: DummyERC20TokenContract; let zrxToken: DummyERC20TokenContract; let erc20TokenA: DummyERC20TokenContract; + let yesComplianceToken: YesComplianceTokenContract; let compliantForwarderContract: CompliantForwarderContract; let wethContract: WETH9Contract; let forwarderWrapper: ForwarderWrapper; @@ -59,37 +63,73 @@ describe(ContractName.Forwarder, () => { let erc20Balances: ERC20BalancesByOwner; let tx: TransactionReceiptWithDecodedLogs; + let makerAssetAddress: string; + let takerAssetAddress: string; + let erc721MakerAssetIds: BigNumber[]; let takerEthBalanceBefore: BigNumber; let feePercentage: BigNumber; - let gasPrice: BigNumber; + + let compliantSignedOrder: SignedOrder; + let compliantSignedFillOrderTx: SignedTransaction; + let noncompliantSignedFillOrderTx: SignedTransaction; + + const compliantMakerCountryCode = new BigNumber(519); + const compliantMakerYesMark = new BigNumber(1); + const compliantMakerEntityId = new BigNumber(2); + let compliantMakerYesTokenId; + + const compliantTakerCountryCode = new BigNumber(519); + const compliantTakerYesMark = new BigNumber(1); + const compliantTakerEntityId = new BigNumber(2); + let compliantTakerYesTokenId; + + const takerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(500), DECIMALS_DEFAULT); + const makerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(1000), DECIMALS_DEFAULT); + const takerAssetFillAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(250), DECIMALS_DEFAULT); + const salt = new BigNumber(0); + + let compliantForwarderInstance: CompliantForwarderContract; before(async () => { + // Create accounts await blockchainLifecycle.startAsync(); const accounts = await web3Wrapper.getAvailableAddressesAsync(); - const usedAddresses = ([owner, makerAddress, takerAddress, feeRecipientAddress, otherAddress] = accounts); - - const txHash = await web3Wrapper.sendTransactionAsync({ from: accounts[0], to: accounts[0], value: 0 }); - const transaction = await web3Wrapper.getTransactionByHashAsync(txHash); - gasPrice = new BigNumber(transaction.gasPrice); - + const usedAddresses = ([owner, compliantMakerAddress, compliantTakerAddress, feeRecipientAddress, noncompliantAddress] = accounts); + // Create wrappers const erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner); erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner); - + // Deploy ERC20 tokens const numDummyErc20ToDeploy = 3; - [erc20TokenA, zrxToken] = await erc20Wrapper.deployDummyTokensAsync( + let erc20TokenA: DummyERC20TokenContract; + let erc20TokenB: DummyERC20TokenContract; + [erc20TokenA, erc20TokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync( numDummyErc20ToDeploy, constants.DUMMY_TOKEN_DECIMALS, ); + makerAssetAddress = erc20TokenA.address; + takerAssetAddress = erc20TokenB.address; + // Deploy Yes Token + const yesTokenInstance = await YesComplianceTokenContract.deployFrom0xArtifactAsync( + artifacts.YesComplianceToken, + provider, + txDefaults, + ); + compliantForwarderContract = new CompliantForwarderContract( + yesTokenInstance.abi, + yesTokenInstance.address, + provider, + ); + // Create proxies const erc20Proxy = await erc20Wrapper.deployProxyAsync(); await erc20Wrapper.setBalancesAndAllowancesAsync(); - + // Deploy tokens & set asset data wethContract = await WETH9Contract.deployFrom0xArtifactAsync(artifacts.WETH9, provider, txDefaults); weth = new DummyERC20TokenContract(wethContract.abi, wethContract.address, provider); erc20Wrapper.addDummyTokenContract(weth); - wethAssetData = assetDataUtils.encodeERC20AssetData(wethContract.address); zrxAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address); + // Deploy Exchange congtract const exchangeInstance = await ExchangeContract.deployFrom0xArtifactAsync( artifacts.Exchange, provider, @@ -97,36 +137,35 @@ describe(ContractName.Forwarder, () => { zrxAssetData, ); exchangeWrapper = new ExchangeWrapper(exchangeInstance, provider); + // Register proxies await exchangeWrapper.registerAssetProxyAsync(erc20Proxy.address, owner); - await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(exchangeInstance.address, { from: owner, }); - + // Default order parameters defaultMakerAssetAddress = erc20TokenA.address; const defaultTakerAssetAddress = wethContract.address; const defaultOrderParams = { exchangeAddress: exchangeInstance.address, - makerAddress, + compliantMakerAddress, feeRecipientAddress, makerAssetData: assetDataUtils.encodeERC20AssetData(defaultMakerAssetAddress), takerAssetData: assetDataUtils.encodeERC20AssetData(defaultTakerAssetAddress), - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(200), DECIMALS_DEFAULT), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), DECIMALS_DEFAULT), + makerAssetAmount: makerAssetAmount, + takerAssetAmount: takerAssetAmount, makerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT), - takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(0), DECIMALS_DEFAULT), + takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT), }; - const privateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddress)]; + const privateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantMakerAddress)]; orderFactory = new OrderFactory(privateKey, defaultOrderParams); - - const compliantForwarderInstance = await CompliantForwarderContract.deployFrom0xArtifactAsync( + // Deploy Compliant Forwarder + compliantForwarderInstance = await CompliantForwarderContract.deployFrom0xArtifactAsync( artifacts.CompliantForwarder, provider, txDefaults, exchangeInstance.address, - exchangeInstance.address, // @TODO CHANGE to Yes Token + yesTokenInstance.address, ); - compliantForwarderContract = new CompliantForwarderContract( compliantForwarderInstance.abi, compliantForwarderInstance.address, @@ -135,35 +174,74 @@ describe(ContractName.Forwarder, () => { /* forwarderWrapper = new ForwarderWrapper(compliantForwarderContract, provider); */ - - const zrxDepositAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(10000), 18); - await web3Wrapper.awaitTransactionSuccessAsync( - await zrxToken.transfer.sendTransactionAsync(compliantForwarderContract.address, zrxDepositAmount), + // Verify Maker / Taker + const addressesCanControlTheirToken = true; + compliantMakerYesTokenId = await yesTokenInstance.mint2.sendTransactionAsync(compliantMakerAddress, compliantMakerEntityId, addressesCanControlTheirToken, compliantMakerCountryCode, [compliantMakerYesMark]); + compliantTakerYesTokenId = await yesTokenInstance.mint2.sendTransactionAsync(compliantTakerAddress, compliantTakerEntityId, addressesCanControlTheirToken, compliantTakerCountryCode, [compliantTakerYesMark]); + // Create Valid/Invalid orders + const takerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantTakerAddress)]; + const takerTransactionFactory = new TransactionFactory(takerPrivateKey, exchangeInstance.address); + compliantSignedOrder = await orderFactory.newSignedOrderAsync({ + senderAddress: compliantForwarderContract.address, + }); + const compliantSignedOrderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress(compliantSignedOrder); + const compliantSignedOrderWithoutExchangeAddressData = exchangeInstance.fillOrder.getABIEncodedTransactionData( + compliantSignedOrderWithoutExchangeAddress, + takerAssetFillAmount, + compliantSignedOrder.signature, ); - erc20Wrapper.addTokenOwnerAddress(compliantForwarderInstance.address); - }); - after(async () => { - await blockchainLifecycle.revertAsync(); + compliantSignedFillOrderTx = takerTransactionFactory.newSignedTransaction(compliantSignedOrderWithoutExchangeAddressData); }); beforeEach(async () => { await blockchainLifecycle.startAsync(); - erc20Balances = await erc20Wrapper.getBalancesAsync(); - takerEthBalanceBefore = await web3Wrapper.getBalanceInWeiAsync(takerAddress); - orderWithoutFee = await orderFactory.newSignedOrderAsync(); - feeOrder = await orderFactory.newSignedOrderAsync({ - makerAssetData: assetDataUtils.encodeERC20AssetData(zrxToken.address), - takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT), - }); - orderWithFee = await orderFactory.newSignedOrderAsync({ - takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT), - }); }); afterEach(async () => { await blockchainLifecycle.revertAsync(); }); - describe('constructor', () => { - it('should revert if assetProxy is unregistered', async () => {}); + describe.only('fillOrder', () => { + let takerAssetFillAmount: BigNumber; + beforeEach(async () => { + erc20Balances = await erc20Wrapper.getBalancesAsync(); + }); + + // @TODO: Should fail if order's senderAddress is not set to the compliant forwarding contract + // @TODO: Should fail if the + + it.only('should transfer the correct amounts when maker and taker are verified', async () => { + await compliantForwarderInstance.fillOrder.sendTransactionAsync(compliantSignedFillOrderTx.salt, compliantSignedFillOrderTx.signerAddress, compliantSignedFillOrderTx.data, compliantSignedFillOrderTx.signature); + const newBalances = await erc20Wrapper.getBalancesAsync(); + const makerAssetFillAmount = takerAssetFillAmount + .times(compliantSignedOrder.makerAssetAmount) + .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); + const makerFeePaid = compliantSignedOrder.makerFee + .times(makerAssetFillAmount) + .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); + const takerFeePaid = compliantSignedOrder.takerFee + .times(makerAssetFillAmount) + .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); + expect(newBalances[compliantMakerAddress][makerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][makerAssetAddress].minus(makerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress][takerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][takerAssetAddress].add(takerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), + ); + expect(newBalances[compliantTakerAddress][takerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][takerAssetAddress].minus(takerAssetFillAmount), + ); + expect(newBalances[compliantTakerAddress][makerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][makerAssetAddress].add(makerAssetFillAmount), + ); + expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), + ); + expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)), + ); + }); }); }); // tslint:disable:max-file-line-count -- cgit From 003075a8a594060817f2ddcb2124580e27b6b2a8 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 29 Nov 2018 11:11:06 -0800 Subject: WIP - Tests for compliant forwarder --- .../extensions/CompliantForwarder/CompliantForwarder.sol | 1 + .../contracts/test/extensions/compliant_forwarder.ts | 16 +++++++++++----- 2 files changed, 12 insertions(+), 5 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol index 2febc5cce..0ecf44006 100644 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -51,6 +51,7 @@ contract CompliantForwarder { if (selector != EXCHANGE_FILL_ORDER_SELECTOR) { revert("EXCHANGE_TRANSACTION_NOT_FILL_ORDER"); } + // Extract maker address from fill order transaction // Below is the table of calldata offsets into a fillOrder transaction. diff --git a/packages/contracts/test/extensions/compliant_forwarder.ts b/packages/contracts/test/extensions/compliant_forwarder.ts index 9100c32f8..da023a192 100644 --- a/packages/contracts/test/extensions/compliant_forwarder.ts +++ b/packages/contracts/test/extensions/compliant_forwarder.ts @@ -147,7 +147,7 @@ describe.only(ContractName.Forwarder, () => { const defaultTakerAssetAddress = wethContract.address; const defaultOrderParams = { exchangeAddress: exchangeInstance.address, - compliantMakerAddress, + makerAddress: compliantMakerAddress, feeRecipientAddress, makerAssetData: assetDataUtils.encodeERC20AssetData(defaultMakerAssetAddress), takerAssetData: assetDataUtils.encodeERC20AssetData(defaultTakerAssetAddress), @@ -174,10 +174,15 @@ describe.only(ContractName.Forwarder, () => { /* forwarderWrapper = new ForwarderWrapper(compliantForwarderContract, provider); */ + // Initialize Yes Token + await yesTokenInstance._upgradeable_initialize.sendTransactionAsync({from: owner}); + const yesTokenName = "YesToken"; + const yesTokenTicker = "YEET"; + await yesTokenInstance.initialize.sendTransactionAsync(yesTokenName, yesTokenTicker, {from: owner}); // Verify Maker / Taker const addressesCanControlTheirToken = true; - compliantMakerYesTokenId = await yesTokenInstance.mint2.sendTransactionAsync(compliantMakerAddress, compliantMakerEntityId, addressesCanControlTheirToken, compliantMakerCountryCode, [compliantMakerYesMark]); - compliantTakerYesTokenId = await yesTokenInstance.mint2.sendTransactionAsync(compliantTakerAddress, compliantTakerEntityId, addressesCanControlTheirToken, compliantTakerCountryCode, [compliantTakerYesMark]); + compliantMakerYesTokenId = await yesTokenInstance.mint2.sendTransactionAsync(compliantMakerAddress, compliantMakerEntityId, addressesCanControlTheirToken, compliantMakerCountryCode, [compliantMakerYesMark], {from: owner}); + compliantTakerYesTokenId = await yesTokenInstance.mint2.sendTransactionAsync(compliantTakerAddress, compliantTakerEntityId, addressesCanControlTheirToken, compliantTakerCountryCode, [compliantTakerYesMark], {from: owner}); // Create Valid/Invalid orders const takerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantTakerAddress)]; const takerTransactionFactory = new TransactionFactory(takerPrivateKey, exchangeInstance.address); @@ -200,13 +205,14 @@ describe.only(ContractName.Forwarder, () => { }); describe.only('fillOrder', () => { - let takerAssetFillAmount: BigNumber; beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); }); // @TODO: Should fail if order's senderAddress is not set to the compliant forwarding contract - // @TODO: Should fail if the + // @TODO: Should fail if the signed transaction is not intended for fillOrder + // @TODO: Should fail if maker is not verified + // @TODO: Should fail it taker is not verified it.only('should transfer the correct amounts when maker and taker are verified', async () => { await compliantForwarderInstance.fillOrder.sendTransactionAsync(compliantSignedFillOrderTx.salt, compliantSignedFillOrderTx.signerAddress, compliantSignedFillOrderTx.data, compliantSignedFillOrderTx.signature); -- cgit From e81f92bffaf7748e710b342224004007261d2991 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 29 Nov 2018 12:58:52 -0800 Subject: End-to-end test for compliant forwarder - works --- .../test/extensions/compliant_forwarder.ts | 56 +++++++++++----------- 1 file changed, 28 insertions(+), 28 deletions(-) (limited to 'packages') diff --git a/packages/contracts/test/extensions/compliant_forwarder.ts b/packages/contracts/test/extensions/compliant_forwarder.ts index da023a192..3e8b6a776 100644 --- a/packages/contracts/test/extensions/compliant_forwarder.ts +++ b/packages/contracts/test/extensions/compliant_forwarder.ts @@ -30,19 +30,24 @@ import { TransactionFactory } from '../utils/transaction_factory'; import { ContractName, ERC20BalancesByOwner, SignedTransaction } from '../utils/types'; import { provider, txDefaults, web3Wrapper } from '../utils/web3_wrapper'; +import { AbiEncoder } from '@0x/utils'; +import { MethodAbi } from 'ethereum-types'; + + chaiSetup.configure(); const expect = chai.expect; const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); const DECIMALS_DEFAULT = 18; const MAX_WETH_FILL_PERCENTAGE = 95; -describe.only(ContractName.Forwarder, () => { +describe.only(ContractName.CompliantForwarder, () => { let compliantMakerAddress: string; let owner: string; let compliantTakerAddress: string; let feeRecipientAddress: string; let noncompliantAddress: string; let defaultMakerAssetAddress: string; + let defaultTakerAssetAddress: string; let zrxAssetData: string; let wethAssetData: string; @@ -52,19 +57,11 @@ describe.only(ContractName.Forwarder, () => { let yesComplianceToken: YesComplianceTokenContract; let compliantForwarderContract: CompliantForwarderContract; let wethContract: WETH9Contract; - let forwarderWrapper: ForwarderWrapper; let exchangeWrapper: ExchangeWrapper; - let orderWithoutFee: SignedOrder; - let orderWithFee: SignedOrder; - let feeOrder: SignedOrder; let orderFactory: OrderFactory; let erc20Wrapper: ERC20Wrapper; let erc20Balances: ERC20BalancesByOwner; - let tx: TransactionReceiptWithDecodedLogs; - - let makerAssetAddress: string; - let takerAssetAddress: string; let erc721MakerAssetIds: BigNumber[]; let takerEthBalanceBefore: BigNumber; @@ -107,8 +104,8 @@ describe.only(ContractName.Forwarder, () => { numDummyErc20ToDeploy, constants.DUMMY_TOKEN_DECIMALS, ); - makerAssetAddress = erc20TokenA.address; - takerAssetAddress = erc20TokenB.address; + defaultMakerAssetAddress = erc20TokenA.address; + defaultTakerAssetAddress = erc20TokenB.address; // Deploy Yes Token const yesTokenInstance = await YesComplianceTokenContract.deployFrom0xArtifactAsync( artifacts.YesComplianceToken, @@ -143,18 +140,16 @@ describe.only(ContractName.Forwarder, () => { from: owner, }); // Default order parameters - defaultMakerAssetAddress = erc20TokenA.address; - const defaultTakerAssetAddress = wethContract.address; const defaultOrderParams = { exchangeAddress: exchangeInstance.address, makerAddress: compliantMakerAddress, feeRecipientAddress, makerAssetData: assetDataUtils.encodeERC20AssetData(defaultMakerAssetAddress), takerAssetData: assetDataUtils.encodeERC20AssetData(defaultTakerAssetAddress), - makerAssetAmount: makerAssetAmount, - takerAssetAmount: takerAssetAmount, - makerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT), - takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT), + makerAssetAmount, + takerAssetAmount, + makerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), DECIMALS_DEFAULT), + takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(150), DECIMALS_DEFAULT), }; const privateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantMakerAddress)]; orderFactory = new OrderFactory(privateKey, defaultOrderParams); @@ -187,7 +182,7 @@ describe.only(ContractName.Forwarder, () => { const takerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantTakerAddress)]; const takerTransactionFactory = new TransactionFactory(takerPrivateKey, exchangeInstance.address); compliantSignedOrder = await orderFactory.newSignedOrderAsync({ - senderAddress: compliantForwarderContract.address, + senderAddress: compliantForwarderInstance.address, }); const compliantSignedOrderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress(compliantSignedOrder); const compliantSignedOrderWithoutExchangeAddressData = exchangeInstance.fillOrder.getABIEncodedTransactionData( @@ -215,8 +210,13 @@ describe.only(ContractName.Forwarder, () => { // @TODO: Should fail it taker is not verified it.only('should transfer the correct amounts when maker and taker are verified', async () => { - await compliantForwarderInstance.fillOrder.sendTransactionAsync(compliantSignedFillOrderTx.salt, compliantSignedFillOrderTx.signerAddress, compliantSignedFillOrderTx.data, compliantSignedFillOrderTx.signature); - const newBalances = await erc20Wrapper.getBalancesAsync(); + await compliantForwarderInstance.fillOrder.sendTransactionAsync( + compliantSignedFillOrderTx.salt, + compliantSignedFillOrderTx.signerAddress, + compliantSignedFillOrderTx.data, + compliantSignedFillOrderTx.signature + ); + const newBalances = await erc20Wrapper.getBalancesAsync(); const makerAssetFillAmount = takerAssetFillAmount .times(compliantSignedOrder.makerAssetAmount) .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); @@ -226,20 +226,20 @@ describe.only(ContractName.Forwarder, () => { const takerFeePaid = compliantSignedOrder.takerFee .times(makerAssetFillAmount) .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); - expect(newBalances[compliantMakerAddress][makerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][makerAssetAddress].minus(makerAssetFillAmount), + expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), ); - expect(newBalances[compliantMakerAddress][takerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][takerAssetAddress].add(takerAssetFillAmount), + expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount), ); expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), ); - expect(newBalances[compliantTakerAddress][takerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][takerAssetAddress].minus(takerAssetFillAmount), + expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount), ); - expect(newBalances[compliantTakerAddress][makerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][makerAssetAddress].add(makerAssetFillAmount), + expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount), ); expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), -- cgit From a4ab038aa850f53539d0e4652297e52ecd45a8d3 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 29 Nov 2018 13:07:59 -0800 Subject: Ran prettier --- .../test/extensions/compliant_forwarder.ts | 83 +++++++++++++--------- 1 file changed, 50 insertions(+), 33 deletions(-) (limited to 'packages') diff --git a/packages/contracts/test/extensions/compliant_forwarder.ts b/packages/contracts/test/extensions/compliant_forwarder.ts index 3e8b6a776..91729d158 100644 --- a/packages/contracts/test/extensions/compliant_forwarder.ts +++ b/packages/contracts/test/extensions/compliant_forwarder.ts @@ -30,15 +30,10 @@ import { TransactionFactory } from '../utils/transaction_factory'; import { ContractName, ERC20BalancesByOwner, SignedTransaction } from '../utils/types'; import { provider, txDefaults, web3Wrapper } from '../utils/web3_wrapper'; -import { AbiEncoder } from '@0x/utils'; -import { MethodAbi } from 'ethereum-types'; - - chaiSetup.configure(); const expect = chai.expect; const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); const DECIMALS_DEFAULT = 18; -const MAX_WETH_FILL_PERCENTAGE = 95; describe.only(ContractName.CompliantForwarder, () => { let compliantMakerAddress: string; @@ -81,9 +76,9 @@ describe.only(ContractName.CompliantForwarder, () => { const compliantTakerEntityId = new BigNumber(2); let compliantTakerYesTokenId; - const takerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(500), DECIMALS_DEFAULT); - const makerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(1000), DECIMALS_DEFAULT); - const takerAssetFillAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(250), DECIMALS_DEFAULT); + const takerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(500), DECIMALS_DEFAULT); + const makerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(1000), DECIMALS_DEFAULT); + const takerAssetFillAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(250), DECIMALS_DEFAULT); const salt = new BigNumber(0); let compliantForwarderInstance: CompliantForwarderContract; @@ -92,7 +87,13 @@ describe.only(ContractName.CompliantForwarder, () => { // Create accounts await blockchainLifecycle.startAsync(); const accounts = await web3Wrapper.getAvailableAddressesAsync(); - const usedAddresses = ([owner, compliantMakerAddress, compliantTakerAddress, feeRecipientAddress, noncompliantAddress] = accounts); + const usedAddresses = ([ + owner, + compliantMakerAddress, + compliantTakerAddress, + feeRecipientAddress, + noncompliantAddress, + ] = accounts); // Create wrappers const erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner); erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner); @@ -107,7 +108,7 @@ describe.only(ContractName.CompliantForwarder, () => { defaultMakerAssetAddress = erc20TokenA.address; defaultTakerAssetAddress = erc20TokenB.address; // Deploy Yes Token - const yesTokenInstance = await YesComplianceTokenContract.deployFrom0xArtifactAsync( + const yesTokenInstance = await YesComplianceTokenContract.deployFrom0xArtifactAsync( artifacts.YesComplianceToken, provider, txDefaults, @@ -169,28 +170,46 @@ describe.only(ContractName.CompliantForwarder, () => { /* forwarderWrapper = new ForwarderWrapper(compliantForwarderContract, provider); */ - // Initialize Yes Token - await yesTokenInstance._upgradeable_initialize.sendTransactionAsync({from: owner}); - const yesTokenName = "YesToken"; - const yesTokenTicker = "YEET"; - await yesTokenInstance.initialize.sendTransactionAsync(yesTokenName, yesTokenTicker, {from: owner}); - // Verify Maker / Taker - const addressesCanControlTheirToken = true; - compliantMakerYesTokenId = await yesTokenInstance.mint2.sendTransactionAsync(compliantMakerAddress, compliantMakerEntityId, addressesCanControlTheirToken, compliantMakerCountryCode, [compliantMakerYesMark], {from: owner}); - compliantTakerYesTokenId = await yesTokenInstance.mint2.sendTransactionAsync(compliantTakerAddress, compliantTakerEntityId, addressesCanControlTheirToken, compliantTakerCountryCode, [compliantTakerYesMark], {from: owner}); + // Initialize Yes Token + await yesTokenInstance._upgradeable_initialize.sendTransactionAsync({ from: owner }); + const yesTokenName = 'YesToken'; + const yesTokenTicker = 'YEET'; + await yesTokenInstance.initialize.sendTransactionAsync(yesTokenName, yesTokenTicker, { from: owner }); + // Verify Maker / Taker + const addressesCanControlTheirToken = true; + compliantMakerYesTokenId = await yesTokenInstance.mint2.sendTransactionAsync( + compliantMakerAddress, + compliantMakerEntityId, + addressesCanControlTheirToken, + compliantMakerCountryCode, + [compliantMakerYesMark], + { from: owner }, + ); + compliantTakerYesTokenId = await yesTokenInstance.mint2.sendTransactionAsync( + compliantTakerAddress, + compliantTakerEntityId, + addressesCanControlTheirToken, + compliantTakerCountryCode, + [compliantTakerYesMark], + { from: owner }, + ); // Create Valid/Invalid orders const takerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantTakerAddress)]; const takerTransactionFactory = new TransactionFactory(takerPrivateKey, exchangeInstance.address); compliantSignedOrder = await orderFactory.newSignedOrderAsync({ senderAddress: compliantForwarderInstance.address, }); - const compliantSignedOrderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress(compliantSignedOrder); + const compliantSignedOrderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress( + compliantSignedOrder, + ); const compliantSignedOrderWithoutExchangeAddressData = exchangeInstance.fillOrder.getABIEncodedTransactionData( compliantSignedOrderWithoutExchangeAddress, takerAssetFillAmount, compliantSignedOrder.signature, ); - compliantSignedFillOrderTx = takerTransactionFactory.newSignedTransaction(compliantSignedOrderWithoutExchangeAddressData); + compliantSignedFillOrderTx = takerTransactionFactory.newSignedTransaction( + compliantSignedOrderWithoutExchangeAddressData, + ); }); beforeEach(async () => { await blockchainLifecycle.startAsync(); @@ -198,25 +217,19 @@ describe.only(ContractName.CompliantForwarder, () => { afterEach(async () => { await blockchainLifecycle.revertAsync(); }); - describe.only('fillOrder', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); }); - // @TODO: Should fail if order's senderAddress is not set to the compliant forwarding contract - // @TODO: Should fail if the signed transaction is not intended for fillOrder - // @TODO: Should fail if maker is not verified - // @TODO: Should fail it taker is not verified - it.only('should transfer the correct amounts when maker and taker are verified', async () => { - await compliantForwarderInstance.fillOrder.sendTransactionAsync( - compliantSignedFillOrderTx.salt, - compliantSignedFillOrderTx.signerAddress, - compliantSignedFillOrderTx.data, - compliantSignedFillOrderTx.signature + await compliantForwarderInstance.fillOrder.sendTransactionAsync( + compliantSignedFillOrderTx.salt, + compliantSignedFillOrderTx.signerAddress, + compliantSignedFillOrderTx.data, + compliantSignedFillOrderTx.signature, ); - const newBalances = await erc20Wrapper.getBalancesAsync(); + const newBalances = await erc20Wrapper.getBalancesAsync(); const makerAssetFillAmount = takerAssetFillAmount .times(compliantSignedOrder.makerAssetAmount) .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); @@ -248,6 +261,10 @@ describe.only(ContractName.CompliantForwarder, () => { erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)), ); }); + // @TODO: Should fail if order's senderAddress is not set to the compliant forwarding contract + // @TODO: Should fail if the signed transaction is not intended for fillOrder + // @TODO: Should fail if maker is not verified + // @TODO: Should fail it taker is not verified }); }); // tslint:disable:max-file-line-count -- cgit From 989b5b0a98c578321e37439066f9a45287824a6d Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 29 Nov 2018 13:13:52 -0800 Subject: Ran linter --- .../CompliantForwarder/CompliantForwarder.sol | 1 - .../test/extensions/compliant_forwarder.ts | 53 +++++----------------- 2 files changed, 11 insertions(+), 43 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol index 0ecf44006..2febc5cce 100644 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -51,7 +51,6 @@ contract CompliantForwarder { if (selector != EXCHANGE_FILL_ORDER_SELECTOR) { revert("EXCHANGE_TRANSACTION_NOT_FILL_ORDER"); } - // Extract maker address from fill order transaction // Below is the table of calldata offsets into a fillOrder transaction. diff --git a/packages/contracts/test/extensions/compliant_forwarder.ts b/packages/contracts/test/extensions/compliant_forwarder.ts index 91729d158..932012c0d 100644 --- a/packages/contracts/test/extensions/compliant_forwarder.ts +++ b/packages/contracts/test/extensions/compliant_forwarder.ts @@ -4,14 +4,12 @@ import { RevertReason, SignedOrder } from '@0x/types'; import { BigNumber } from '@0x/utils'; import { Web3Wrapper } from '@0x/web3-wrapper'; import * as chai from 'chai'; -import { TransactionReceiptWithDecodedLogs } from 'ethereum-types'; import { DummyERC20TokenContract } from '../../generated-wrappers/dummy_erc20_token'; import { ExchangeContract } from '../../generated-wrappers/exchange'; import { CompliantForwarderContract } from '../../generated-wrappers/compliant_forwarder'; import { YesComplianceTokenContract } from '../../generated-wrappers/yes_compliance_token'; -import { WETH9Contract } from '../../generated-wrappers/weth9'; import { artifacts } from '../../src/artifacts'; import { expectContractCreationFailedAsync, @@ -21,9 +19,7 @@ import { import { chaiSetup } from '../utils/chai_setup'; import { constants } from '../utils/constants'; import { ERC20Wrapper } from '../utils/erc20_wrapper'; -import { ERC721Wrapper } from '../utils/erc721_wrapper'; import { ExchangeWrapper } from '../utils/exchange_wrapper'; -import { ForwarderWrapper } from '../utils/forwarder_wrapper'; import { OrderFactory } from '../utils/order_factory'; import { orderUtils } from '../utils/order_utils'; import { TransactionFactory } from '../utils/transaction_factory'; @@ -44,42 +40,20 @@ describe.only(ContractName.CompliantForwarder, () => { let defaultMakerAssetAddress: string; let defaultTakerAssetAddress: string; let zrxAssetData: string; - let wethAssetData: string; - - let weth: DummyERC20TokenContract; let zrxToken: DummyERC20TokenContract; - let erc20TokenA: DummyERC20TokenContract; - let yesComplianceToken: YesComplianceTokenContract; - let compliantForwarderContract: CompliantForwarderContract; - let wethContract: WETH9Contract; let exchangeWrapper: ExchangeWrapper; let orderFactory: OrderFactory; let erc20Wrapper: ERC20Wrapper; let erc20Balances: ERC20BalancesByOwner; - let erc721MakerAssetIds: BigNumber[]; - let takerEthBalanceBefore: BigNumber; - let feePercentage: BigNumber; - let compliantSignedOrder: SignedOrder; let compliantSignedFillOrderTx: SignedTransaction; let noncompliantSignedFillOrderTx: SignedTransaction; - const compliantMakerCountryCode = new BigNumber(519); - const compliantMakerYesMark = new BigNumber(1); - const compliantMakerEntityId = new BigNumber(2); - let compliantMakerYesTokenId; - - const compliantTakerCountryCode = new BigNumber(519); - const compliantTakerYesMark = new BigNumber(1); - const compliantTakerEntityId = new BigNumber(2); - let compliantTakerYesTokenId; - const takerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(500), DECIMALS_DEFAULT); const makerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(1000), DECIMALS_DEFAULT); const takerAssetFillAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(250), DECIMALS_DEFAULT); - const salt = new BigNumber(0); let compliantForwarderInstance: CompliantForwarderContract; @@ -95,7 +69,6 @@ describe.only(ContractName.CompliantForwarder, () => { noncompliantAddress, ] = accounts); // Create wrappers - const erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner); erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner); // Deploy ERC20 tokens const numDummyErc20ToDeploy = 3; @@ -107,26 +80,16 @@ describe.only(ContractName.CompliantForwarder, () => { ); defaultMakerAssetAddress = erc20TokenA.address; defaultTakerAssetAddress = erc20TokenB.address; + zrxAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address); // Deploy Yes Token const yesTokenInstance = await YesComplianceTokenContract.deployFrom0xArtifactAsync( artifacts.YesComplianceToken, provider, txDefaults, ); - compliantForwarderContract = new CompliantForwarderContract( - yesTokenInstance.abi, - yesTokenInstance.address, - provider, - ); // Create proxies const erc20Proxy = await erc20Wrapper.deployProxyAsync(); await erc20Wrapper.setBalancesAndAllowancesAsync(); - // Deploy tokens & set asset data - wethContract = await WETH9Contract.deployFrom0xArtifactAsync(artifacts.WETH9, provider, txDefaults); - weth = new DummyERC20TokenContract(wethContract.abi, wethContract.address, provider); - erc20Wrapper.addDummyTokenContract(weth); - wethAssetData = assetDataUtils.encodeERC20AssetData(wethContract.address); - zrxAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address); // Deploy Exchange congtract const exchangeInstance = await ExchangeContract.deployFrom0xArtifactAsync( artifacts.Exchange, @@ -162,12 +125,12 @@ describe.only(ContractName.CompliantForwarder, () => { exchangeInstance.address, yesTokenInstance.address, ); - compliantForwarderContract = new CompliantForwarderContract( + /* + const compliantForwarderContract = new CompliantForwarderContract( compliantForwarderInstance.abi, compliantForwarderInstance.address, provider, ); - /* forwarderWrapper = new ForwarderWrapper(compliantForwarderContract, provider); */ // Initialize Yes Token @@ -177,7 +140,10 @@ describe.only(ContractName.CompliantForwarder, () => { await yesTokenInstance.initialize.sendTransactionAsync(yesTokenName, yesTokenTicker, { from: owner }); // Verify Maker / Taker const addressesCanControlTheirToken = true; - compliantMakerYesTokenId = await yesTokenInstance.mint2.sendTransactionAsync( + const compliantMakerCountryCode = new BigNumber(519); + const compliantMakerYesMark = new BigNumber(1); + const compliantMakerEntityId = new BigNumber(2); + await yesTokenInstance.mint2.sendTransactionAsync( compliantMakerAddress, compliantMakerEntityId, addressesCanControlTheirToken, @@ -185,7 +151,10 @@ describe.only(ContractName.CompliantForwarder, () => { [compliantMakerYesMark], { from: owner }, ); - compliantTakerYesTokenId = await yesTokenInstance.mint2.sendTransactionAsync( + const compliantTakerCountryCode = new BigNumber(519); + const compliantTakerYesMark = new BigNumber(1); + const compliantTakerEntityId = new BigNumber(2); + await yesTokenInstance.mint2.sendTransactionAsync( compliantTakerAddress, compliantTakerEntityId, addressesCanControlTheirToken, -- cgit From 33e41dd500960fde6bf1f5b1f4cf650731086963 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 29 Nov 2018 13:56:39 -0800 Subject: More tests + require instead of revert in compliance contract --- .../CompliantForwarder/CompliantForwarder.sol | 19 +++-- .../test/extensions/compliant_forwarder.ts | 89 +++++++++++++++++++--- packages/types/src/index.ts | 2 + 3 files changed, 92 insertions(+), 18 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol index 2febc5cce..f34ee699d 100644 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -51,8 +51,14 @@ contract CompliantForwarder { if (selector != EXCHANGE_FILL_ORDER_SELECTOR) { revert("EXCHANGE_TRANSACTION_NOT_FILL_ORDER"); } + + // Taker must be compliant + require( + COMPLIANCE_TOKEN.balanceOf(signerAddress) > 0, + "TAKER_UNVERIFIED" + ); - // Extract maker address from fill order transaction + // Extract maker address from fill order transaction and ensure maker is compliant // Below is the table of calldata offsets into a fillOrder transaction. /** ### parameters @@ -82,13 +88,10 @@ contract CompliantForwarder { // Add 0xc to the makerAddress since abi-encoded addresses are left padded with 12 bytes. // Putting this together: makerAddress = 0x60 + 0x4 + 0xc = 0x70 address makerAddress = signedFillOrderTransaction.readAddress(0x70); - - // Verify maker/taker have been verified by the compliance token. - if (COMPLIANCE_TOKEN.balanceOf(makerAddress) == 0) { - revert("MAKER_UNVERIFIED"); - } else if (COMPLIANCE_TOKEN.balanceOf(signerAddress) == 0) { - revert("TAKER_UNVERIFIED"); - } + require( + COMPLIANCE_TOKEN.balanceOf(makerAddress) > 0, + "MAKER_UNVERIFIED" + ); // All entities are verified. Execute fillOrder. EXCHANGE.executeTransaction( diff --git a/packages/contracts/test/extensions/compliant_forwarder.ts b/packages/contracts/test/extensions/compliant_forwarder.ts index 932012c0d..311ad78e9 100644 --- a/packages/contracts/test/extensions/compliant_forwarder.ts +++ b/packages/contracts/test/extensions/compliant_forwarder.ts @@ -4,6 +4,7 @@ import { RevertReason, SignedOrder } from '@0x/types'; import { BigNumber } from '@0x/utils'; import { Web3Wrapper } from '@0x/web3-wrapper'; import * as chai from 'chai'; +import * as ethUtil from 'ethereumjs-util'; import { DummyERC20TokenContract } from '../../generated-wrappers/dummy_erc20_token'; import { ExchangeContract } from '../../generated-wrappers/exchange'; @@ -12,9 +13,8 @@ import { YesComplianceTokenContract } from '../../generated-wrappers/yes_complia import { artifacts } from '../../src/artifacts'; import { - expectContractCreationFailedAsync, expectTransactionFailedAsync, - sendTransactionResult, + expectTransactionFailedWithoutReasonAsync, } from '../utils/assertions'; import { chaiSetup } from '../utils/chai_setup'; import { constants } from '../utils/constants'; @@ -36,17 +36,19 @@ describe.only(ContractName.CompliantForwarder, () => { let owner: string; let compliantTakerAddress: string; let feeRecipientAddress: string; - let noncompliantAddress: string; + let nonCompliantAddress: string; let defaultMakerAssetAddress: string; let defaultTakerAssetAddress: string; let zrxAssetData: string; let zrxToken: DummyERC20TokenContract; + let exchangeInstance: ExchangeContract; let exchangeWrapper: ExchangeWrapper; let orderFactory: OrderFactory; let erc20Wrapper: ERC20Wrapper; let erc20Balances: ERC20BalancesByOwner; + let takerTransactionFactory: TransactionFactory; let compliantSignedOrder: SignedOrder; let compliantSignedFillOrderTx: SignedTransaction; let noncompliantSignedFillOrderTx: SignedTransaction; @@ -66,7 +68,7 @@ describe.only(ContractName.CompliantForwarder, () => { compliantMakerAddress, compliantTakerAddress, feeRecipientAddress, - noncompliantAddress, + nonCompliantAddress, ] = accounts); // Create wrappers erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner); @@ -91,7 +93,7 @@ describe.only(ContractName.CompliantForwarder, () => { const erc20Proxy = await erc20Wrapper.deployProxyAsync(); await erc20Wrapper.setBalancesAndAllowancesAsync(); // Deploy Exchange congtract - const exchangeInstance = await ExchangeContract.deployFrom0xArtifactAsync( + exchangeInstance = await ExchangeContract.deployFrom0xArtifactAsync( artifacts.Exchange, provider, txDefaults, @@ -164,7 +166,7 @@ describe.only(ContractName.CompliantForwarder, () => { ); // Create Valid/Invalid orders const takerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantTakerAddress)]; - const takerTransactionFactory = new TransactionFactory(takerPrivateKey, exchangeInstance.address); + takerTransactionFactory = new TransactionFactory(takerPrivateKey, exchangeInstance.address); compliantSignedOrder = await orderFactory.newSignedOrderAsync({ senderAddress: compliantForwarderInstance.address, }); @@ -190,8 +192,7 @@ describe.only(ContractName.CompliantForwarder, () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); }); - - it.only('should transfer the correct amounts when maker and taker are verified', async () => { + it('should transfer the correct amounts when maker and taker are compliant', async () => { await compliantForwarderInstance.fillOrder.sendTransactionAsync( compliantSignedFillOrderTx.salt, compliantSignedFillOrderTx.signerAddress, @@ -230,8 +231,76 @@ describe.only(ContractName.CompliantForwarder, () => { erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)), ); }); - // @TODO: Should fail if order's senderAddress is not set to the compliant forwarding contract - // @TODO: Should fail if the signed transaction is not intended for fillOrder + it('should revert if the signed transaction is not intended for fillOrder', async () => { + // Create signed order without the fillOrder function selector + const txDataBuf = ethUtil.toBuffer(compliantSignedFillOrderTx.data); + const selectorLengthInBytes = 4; + const txDataBufMinusSelector = txDataBuf.slice(selectorLengthInBytes); + const badSelector = '0x00000000'; + const badSelectorBuf = ethUtil.toBuffer(badSelector); + const txDataBufWithBadSelector = Buffer.concat([badSelectorBuf, txDataBufMinusSelector]); + const txDataBufWithBadSelectorHex = ethUtil.bufferToHex(txDataBufWithBadSelector); + // Call compliant forwarder + return expectTransactionFailedWithoutReasonAsync(compliantForwarderInstance.fillOrder.sendTransactionAsync( + compliantSignedFillOrderTx.salt, + compliantSignedFillOrderTx.signerAddress, + txDataBufWithBadSelectorHex, + compliantSignedFillOrderTx.signature, + )); + }); + it('should revert if senderAddress is not set to the compliant forwarding contract', async () => { + // Create signed order with incorrect senderAddress + const notCompliantForwarderAddress = zrxToken.address; + const signedOrderWithBadSenderAddress = await orderFactory.newSignedOrderAsync({ + senderAddress: notCompliantForwarderAddress, + }); + const signedOrderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress( + signedOrderWithBadSenderAddress, + ); + const signedOrderWithoutExchangeAddressData = exchangeInstance.fillOrder.getABIEncodedTransactionData( + signedOrderWithoutExchangeAddress, + takerAssetFillAmount, + compliantSignedOrder.signature, + ); + const signedFillOrderTx = takerTransactionFactory.newSignedTransaction( + signedOrderWithoutExchangeAddressData, + ); + // Call compliant forwarder + return expectTransactionFailedWithoutReasonAsync(compliantForwarderInstance.fillOrder.sendTransactionAsync( + signedFillOrderTx.salt, + signedFillOrderTx.signerAddress, + signedFillOrderTx.data, + signedFillOrderTx.signature, + )); + }); + it('should revert if maker address is not compliant (does not hold a Yes Token)', async () => { + // Create signed order with non-compliant maker address + const signedOrderWithBadSenderAddress = await orderFactory.newSignedOrderAsync({ + senderAddress: compliantForwarderInstance.address, + makerAddress: nonCompliantAddress + }); + const signedOrderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress( + signedOrderWithBadSenderAddress, + ); + const signedOrderWithoutExchangeAddressData = exchangeInstance.fillOrder.getABIEncodedTransactionData( + signedOrderWithoutExchangeAddress, + takerAssetFillAmount, + compliantSignedOrder.signature, + ); + const signedFillOrderTx = takerTransactionFactory.newSignedTransaction( + signedOrderWithoutExchangeAddressData, + ); + // Call compliant forwarder + return expectTransactionFailedAsync( + compliantForwarderInstance.fillOrder.sendTransactionAsync( + signedFillOrderTx.salt, + signedFillOrderTx.signerAddress, + signedFillOrderTx.data, + signedFillOrderTx.signature, + ), + RevertReason.MakerUnverified + ); + }); // @TODO: Should fail if maker is not verified // @TODO: Should fail it taker is not verified }); diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 6b728af71..0c6fd7fd7 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -243,6 +243,8 @@ export enum RevertReason { AuctionNotStarted = 'AUCTION_NOT_STARTED', AuctionInvalidBeginTime = 'INVALID_BEGIN_TIME', InvalidAssetData = 'INVALID_ASSET_DATA', + MakerUnverified = 'MAKER_UNVERIFED', + TakerUnverified = 'TAKER_UNVERIFIED', } export enum StatusCodes { -- cgit From 3f7bd24250d0a965e4f4c95b731b2c6e5a481fcb Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 29 Nov 2018 14:05:04 -0800 Subject: Wrapped up tests for compliant forwarder --- .../CompliantForwarder/CompliantForwarder.sol | 3 ++- .../contracts/test/extensions/compliant_forwarder.ts | 17 +++++++++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol index f34ee699d..aaa2d93d6 100644 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -93,12 +93,13 @@ contract CompliantForwarder { "MAKER_UNVERIFIED" ); + /* // All entities are verified. Execute fillOrder. EXCHANGE.executeTransaction( salt, signerAddress, signedFillOrderTransaction, signature - ); + );*/ } } \ No newline at end of file diff --git a/packages/contracts/test/extensions/compliant_forwarder.ts b/packages/contracts/test/extensions/compliant_forwarder.ts index 311ad78e9..61bbe020c 100644 --- a/packages/contracts/test/extensions/compliant_forwarder.ts +++ b/packages/contracts/test/extensions/compliant_forwarder.ts @@ -273,14 +273,25 @@ describe.only(ContractName.CompliantForwarder, () => { signedFillOrderTx.signature, )); }); + it('should revert if taker address is not compliant (does not hold a Yes Token)', async () => { + return expectTransactionFailedAsync( + compliantForwarderInstance.fillOrder.sendTransactionAsync( + compliantSignedFillOrderTx.salt, + nonCompliantAddress, + compliantSignedFillOrderTx.data, + compliantSignedFillOrderTx.signature, + ), + RevertReason.TakerUnverified + ); + }); it('should revert if maker address is not compliant (does not hold a Yes Token)', async () => { // Create signed order with non-compliant maker address - const signedOrderWithBadSenderAddress = await orderFactory.newSignedOrderAsync({ + const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ senderAddress: compliantForwarderInstance.address, makerAddress: nonCompliantAddress }); const signedOrderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress( - signedOrderWithBadSenderAddress, + signedOrderWithBadMakerAddress, ); const signedOrderWithoutExchangeAddressData = exchangeInstance.fillOrder.getABIEncodedTransactionData( signedOrderWithoutExchangeAddress, @@ -301,8 +312,6 @@ describe.only(ContractName.CompliantForwarder, () => { RevertReason.MakerUnverified ); }); - // @TODO: Should fail if maker is not verified - // @TODO: Should fail it taker is not verified }); }); // tslint:disable:max-file-line-count -- cgit From 743c4c36eb6300866a0bd0c8a4bf39a3ce7b31c2 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 29 Nov 2018 14:06:09 -0800 Subject: Removed unnecessary comments in compliant forwarder --- .../contracts/extensions/CompliantForwarder/CompliantForwarder.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol index aaa2d93d6..f34ee699d 100644 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -93,13 +93,12 @@ contract CompliantForwarder { "MAKER_UNVERIFIED" ); - /* // All entities are verified. Execute fillOrder. EXCHANGE.executeTransaction( salt, signerAddress, signedFillOrderTransaction, signature - );*/ + ); } } \ No newline at end of file -- cgit From 37a1271af256af2c0a64b947b3c212a749b540b0 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 29 Nov 2018 16:30:51 -0800 Subject: fillOrder -> executeTransaction rename --- .../extensions/CompliantForwarder/CompliantForwarder.sol | 2 +- packages/contracts/test/extensions/compliant_forwarder.ts | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol index f34ee699d..7a4c8d2f6 100644 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -38,7 +38,7 @@ contract CompliantForwarder { COMPLIANCE_TOKEN = IERC721Token(complianceToken); } - function fillOrder( + function executeTransaction( uint256 salt, address signerAddress, bytes signedFillOrderTransaction, diff --git a/packages/contracts/test/extensions/compliant_forwarder.ts b/packages/contracts/test/extensions/compliant_forwarder.ts index 61bbe020c..c962d82ac 100644 --- a/packages/contracts/test/extensions/compliant_forwarder.ts +++ b/packages/contracts/test/extensions/compliant_forwarder.ts @@ -193,7 +193,7 @@ describe.only(ContractName.CompliantForwarder, () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); }); it('should transfer the correct amounts when maker and taker are compliant', async () => { - await compliantForwarderInstance.fillOrder.sendTransactionAsync( + await compliantForwarderInstance.executeTransaction.sendTransactionAsync( compliantSignedFillOrderTx.salt, compliantSignedFillOrderTx.signerAddress, compliantSignedFillOrderTx.data, @@ -241,7 +241,7 @@ describe.only(ContractName.CompliantForwarder, () => { const txDataBufWithBadSelector = Buffer.concat([badSelectorBuf, txDataBufMinusSelector]); const txDataBufWithBadSelectorHex = ethUtil.bufferToHex(txDataBufWithBadSelector); // Call compliant forwarder - return expectTransactionFailedWithoutReasonAsync(compliantForwarderInstance.fillOrder.sendTransactionAsync( + return expectTransactionFailedWithoutReasonAsync(compliantForwarderInstance.executeTransaction.sendTransactionAsync( compliantSignedFillOrderTx.salt, compliantSignedFillOrderTx.signerAddress, txDataBufWithBadSelectorHex, @@ -266,7 +266,7 @@ describe.only(ContractName.CompliantForwarder, () => { signedOrderWithoutExchangeAddressData, ); // Call compliant forwarder - return expectTransactionFailedWithoutReasonAsync(compliantForwarderInstance.fillOrder.sendTransactionAsync( + return expectTransactionFailedWithoutReasonAsync(compliantForwarderInstance.executeTransaction.sendTransactionAsync( signedFillOrderTx.salt, signedFillOrderTx.signerAddress, signedFillOrderTx.data, @@ -275,7 +275,7 @@ describe.only(ContractName.CompliantForwarder, () => { }); it('should revert if taker address is not compliant (does not hold a Yes Token)', async () => { return expectTransactionFailedAsync( - compliantForwarderInstance.fillOrder.sendTransactionAsync( + compliantForwarderInstance.executeTransaction.sendTransactionAsync( compliantSignedFillOrderTx.salt, nonCompliantAddress, compliantSignedFillOrderTx.data, @@ -303,7 +303,7 @@ describe.only(ContractName.CompliantForwarder, () => { ); // Call compliant forwarder return expectTransactionFailedAsync( - compliantForwarderInstance.fillOrder.sendTransactionAsync( + compliantForwarderInstance.executeTransaction.sendTransactionAsync( signedFillOrderTx.salt, signedFillOrderTx.signerAddress, signedFillOrderTx.data, -- cgit From 4e341582ae74d7ab1c52c4e888d72c0c1c78e890 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 29 Nov 2018 18:24:19 -0800 Subject: Extract makerAddress in assembly --- .../CompliantForwarder/CompliantForwarder.sol | 42 ++++++++++++++++++---- .../test/extensions/compliant_forwarder.ts | 16 ++++++++- 2 files changed, 50 insertions(+), 8 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol index 7a4c8d2f6..b8ba43b15 100644 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -31,6 +31,8 @@ contract CompliantForwarder { IExchange internal EXCHANGE; IERC721Token internal COMPLIANCE_TOKEN; + bytes4 constant internal EXCHANGE_FILL_ORDER_SELECTOR_2 = 0xb4be83d5; + constructor(address exchange, address complianceToken) public { @@ -41,17 +43,43 @@ contract CompliantForwarder { function executeTransaction( uint256 salt, address signerAddress, - bytes signedFillOrderTransaction, + bytes signedExchangeTransaction, bytes signature ) - public + external { // Validate `signedFillOrderTransaction` - bytes4 selector = signedFillOrderTransaction.readBytes4(0); - if (selector != EXCHANGE_FILL_ORDER_SELECTOR) { - revert("EXCHANGE_TRANSACTION_NOT_FILL_ORDER"); + bytes4 selector = signedExchangeTransaction.readBytes4(0); + address makerAddress = 0x00; + assembly { + function getMakerAddress(orderPtr) -> makerAddress { + let orderOffset := calldataload(orderPtr) + makerAddress := calldataload(orderOffset) + } + + switch selector + case 0xb4be83d500000000000000000000000000000000000000000000000000000000 { + let exchangeTxPtr := calldataload(0x44) + + // Add 0x20 for length offset and 0x04 for selector offset + let orderPtrRelativeToExchangeTx := calldataload(add(0x4, add(exchangeTxPtr, 0x24))) // 0x60 + let orderPtr := add(0x4,add(exchangeTxPtr, add(0x24, orderPtrRelativeToExchangeTx))) + + makerAddress := calldataload(orderPtr) + + + //makerAddress := getMakerAddress(orderPtr) + } + default { + // revert(0, 100) + } } + /* + if (selector != 0xb4be83d5) { + revert("EXCHANGE_TRANSACTION_NOT_FILL_ORDER"); + }*/ + // Taker must be compliant require( COMPLIANCE_TOKEN.balanceOf(signerAddress) > 0, @@ -87,7 +115,7 @@ contract CompliantForwarder { // Add 0x4 to a given offset to account for the fillOrder selector prepended to `signedFillOrderTransaction`. // Add 0xc to the makerAddress since abi-encoded addresses are left padded with 12 bytes. // Putting this together: makerAddress = 0x60 + 0x4 + 0xc = 0x70 - address makerAddress = signedFillOrderTransaction.readAddress(0x70); + //address makerAddress = signedExchangeTransaction.readAddress(0x70); require( COMPLIANCE_TOKEN.balanceOf(makerAddress) > 0, "MAKER_UNVERIFIED" @@ -97,7 +125,7 @@ contract CompliantForwarder { EXCHANGE.executeTransaction( salt, signerAddress, - signedFillOrderTransaction, + signedExchangeTransaction, signature ); } diff --git a/packages/contracts/test/extensions/compliant_forwarder.ts b/packages/contracts/test/extensions/compliant_forwarder.ts index c962d82ac..b916f54c9 100644 --- a/packages/contracts/test/extensions/compliant_forwarder.ts +++ b/packages/contracts/test/extensions/compliant_forwarder.ts @@ -26,6 +26,9 @@ import { TransactionFactory } from '../utils/transaction_factory'; import { ContractName, ERC20BalancesByOwner, SignedTransaction } from '../utils/types'; import { provider, txDefaults, web3Wrapper } from '../utils/web3_wrapper'; +import { MethodAbi } from 'ethereum-types'; +import { AbiEncoder } from '@0x/utils'; + chaiSetup.configure(); const expect = chai.expect; const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); @@ -192,7 +195,18 @@ describe.only(ContractName.CompliantForwarder, () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); }); - it('should transfer the correct amounts when maker and taker are compliant', async () => { + it.only('should transfer the correct amounts when maker and taker are compliant', async () => { + + + const method = new AbiEncoder.Method(compliantForwarderInstance.abi[0] as MethodAbi); + const args = [ + compliantSignedFillOrderTx.salt, + compliantSignedFillOrderTx.signerAddress, + compliantSignedFillOrderTx.data, + compliantSignedFillOrderTx.signature + ]; + console.log(method.encode(args, {annotate: true})); + await compliantForwarderInstance.executeTransaction.sendTransactionAsync( compliantSignedFillOrderTx.salt, compliantSignedFillOrderTx.signerAddress, -- cgit From 9f68ac7bbecea109692d62d5555bac67e86c123a Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Fri, 30 Nov 2018 16:43:04 -0800 Subject: Making progress on generalized forwarder --- .../CompliantForwarder/CompliantForwarder.sol | 140 +++++++++++---------- .../test/extensions/compliant_forwarder.ts | 35 +++--- 2 files changed, 98 insertions(+), 77 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol index b8ba43b15..739f55024 100644 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -22,16 +22,20 @@ pragma experimental ABIEncoderV2; import "../../protocol/Exchange/interfaces/IExchange.sol"; import "../../tokens/ERC721Token/IERC721Token.sol"; import "../../utils/LibBytes/LibBytes.sol"; +import "../../utils/ExchangeSelectors/ExchangeSelectors.sol"; -contract CompliantForwarder { +contract CompliantForwarder is ExchangeSelectors{ using LibBytes for bytes; - bytes4 constant internal EXCHANGE_FILL_ORDER_SELECTOR = bytes4(keccak256("fillOrder((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),uint256,bytes)")); IExchange internal EXCHANGE; IERC721Token internal COMPLIANCE_TOKEN; - bytes4 constant internal EXCHANGE_FILL_ORDER_SELECTOR_2 = 0xb4be83d5; + event ValidatedAddresses ( + bytes32 selector, + address one, + address[] addresses + ); constructor(address exchange, address complianceToken) public @@ -49,84 +53,94 @@ contract CompliantForwarder { external { // Validate `signedFillOrderTransaction` - bytes4 selector = signedExchangeTransaction.readBytes4(0); - address makerAddress = 0x00; + address[] memory validatedAddresses; + bytes32 selectorS; + address one; assembly { - function getMakerAddress(orderPtr) -> makerAddress { - let orderOffset := calldataload(orderPtr) - makerAddress := calldataload(orderOffset) + // Adds address to validate + function addAddressToValidate(addressToValidate) { + // Compute `addressesToValidate` memory location + let addressesToValidate_ := mload(0x40) + let nAddressesToValidate_ := mload(addressesToValidate_) + + // Increment length + nAddressesToValidate_ := add(mload(addressesToValidate_), 1) + mstore(addressesToValidate_, nAddressesToValidate_) + + // Append address to validate + let offset := mul(32, nAddressesToValidate_) + mstore(add(addressesToValidate_, offset), addressToValidate) } - switch selector - case 0xb4be83d500000000000000000000000000000000000000000000000000000000 { + function appendMakerAddressFromOrder(paramIndex) -> makerAddress { let exchangeTxPtr := calldataload(0x44) - // Add 0x20 for length offset and 0x04 for selector offset let orderPtrRelativeToExchangeTx := calldataload(add(0x4, add(exchangeTxPtr, 0x24))) // 0x60 let orderPtr := add(0x4,add(exchangeTxPtr, add(0x24, orderPtrRelativeToExchangeTx))) - makerAddress := calldataload(orderPtr) - - - //makerAddress := getMakerAddress(orderPtr) + addAddressToValidate(makerAddress) + } + + + // Extract addresses to validate + let exchangeTxPtr1 := calldataload(0x44) + let selector := and(calldataload(add(0x4, add(0x20, exchangeTxPtr1))), 0xffffffff00000000000000000000000000000000000000000000000000000000) + switch selector + case 0x097bb70b00000000000000000000000000000000000000000000000000000000 /* batchFillOrders */ + { + + } + case 0x3c28d86100000000000000000000000000000000000000000000000000000000 /* matchOrders */ + { + + } + case 0xb4be83d500000000000000000000000000000000000000000000000000000000 /* fillOrder */ + { + one := appendMakerAddressFromOrder(0) + //appendSignerAddress() } + case 0xd46b02c300000000000000000000000000000000000000000000000000000000 /* cancelOrder */ {} default { - // revert(0, 100) + revert(0, 100) } + + let addressesToValidate := mload(0x40) + let nAddressesToValidate := mload(addressesToValidate) + let newMemFreePtr := add(addressesToValidate, add(0x20, mul(mload(addressesToValidate), 0x20))) + mstore(0x40, newMemFreePtr) + + // Validate addresses + /* + let complianceTokenAddress := sload(COMPLIANCE_TOKEN_slot) + for {let i := add(32, mload(addressesToValidate))} lt(i, add(addressesToValidate, add(32, mul(nAddressesToValidate, 32)))) {i := add(i, 32)} { + // call `COMPLIANCE_TOKEN.balanceOf` + let success := call( + gas, // forward all gas + complianceTokenAddress, // call address of asset proxy + 0, // don't send any ETH + i, // pointer to start of input + 32, // length of input (one padded address) + 0, // write output over memory that won't be reused + 0 // don't copy output to memory + ) + if eq(success, 0) { + revert(0, 100) + } + }*/ + + validatedAddresses := addressesToValidate + selectorS := selector } - - /* - if (selector != 0xb4be83d5) { - revert("EXCHANGE_TRANSACTION_NOT_FILL_ORDER"); - }*/ - - // Taker must be compliant - require( - COMPLIANCE_TOKEN.balanceOf(signerAddress) > 0, - "TAKER_UNVERIFIED" - ); - - // Extract maker address from fill order transaction and ensure maker is compliant - // Below is the table of calldata offsets into a fillOrder transaction. - /** - ### parameters - 0x00 ptr - 0x20 takerAssetFillAmount - 0x40 ptr - ### order - 0x60 makerAddress - 0x80 takerAddress - 0xa0 feeRecipientAddress - 0xc0 senderAddress - 0xe0 makerAssetAmount - 0x100 takerAssetAmount - 0x120 makerFee - 0x140 takerFee - 0x160 expirationTimeSeconds - 0x180 salt - 0x1a0 ptr - 0x1c0 ptr - 0x1e0 makerAssetData - * takerAssetData - * signature - ------------------------------ - * Context-dependent offsets; unknown at compile time. - */ - // Add 0x4 to a given offset to account for the fillOrder selector prepended to `signedFillOrderTransaction`. - // Add 0xc to the makerAddress since abi-encoded addresses are left padded with 12 bytes. - // Putting this together: makerAddress = 0x60 + 0x4 + 0xc = 0x70 - //address makerAddress = signedExchangeTransaction.readAddress(0x70); - require( - COMPLIANCE_TOKEN.balanceOf(makerAddress) > 0, - "MAKER_UNVERIFIED" - ); + + emit ValidatedAddresses(selectorS, one, validatedAddresses); // All entities are verified. Execute fillOrder. + /* EXCHANGE.executeTransaction( salt, signerAddress, signedExchangeTransaction, signature - ); + );*/ } } \ No newline at end of file diff --git a/packages/contracts/test/extensions/compliant_forwarder.ts b/packages/contracts/test/extensions/compliant_forwarder.ts index b916f54c9..4eedffe05 100644 --- a/packages/contracts/test/extensions/compliant_forwarder.ts +++ b/packages/contracts/test/extensions/compliant_forwarder.ts @@ -5,6 +5,7 @@ import { BigNumber } from '@0x/utils'; import { Web3Wrapper } from '@0x/web3-wrapper'; import * as chai from 'chai'; import * as ethUtil from 'ethereumjs-util'; +import * as _ from 'lodash'; import { DummyERC20TokenContract } from '../../generated-wrappers/dummy_erc20_token'; import { ExchangeContract } from '../../generated-wrappers/exchange'; @@ -26,8 +27,10 @@ import { TransactionFactory } from '../utils/transaction_factory'; import { ContractName, ERC20BalancesByOwner, SignedTransaction } from '../utils/types'; import { provider, txDefaults, web3Wrapper } from '../utils/web3_wrapper'; -import { MethodAbi } from 'ethereum-types'; +import { MethodAbi, AbiDefinition } from 'ethereum-types'; import { AbiEncoder } from '@0x/utils'; +import { Method } from '@0x/utils/lib/src/abi_encoder'; +import { LogDecoder } from '../utils/log_decoder'; chaiSetup.configure(); const expect = chai.expect; @@ -184,6 +187,18 @@ describe.only(ContractName.CompliantForwarder, () => { compliantSignedFillOrderTx = takerTransactionFactory.newSignedTransaction( compliantSignedOrderWithoutExchangeAddressData, ); + + /* generate selectors for every exchange method + _.each(exchangeInstance.abi, (abiDefinition: AbiDefinition) => { + try { + const method = new Method(abiDefinition as MethodAbi); + console.log('\n', `// ${method.getDataItem().name}`); + console.log(`bytes4 constant ${method.getDataItem().name}Selector = ${method.getSelector()};`); + console.log(`bytes4 constant ${method.getDataItem().name}SelectorGenerator = byes4(keccak256('${method.getSignature()}'));`); + } catch(e) { + _.noop(); + } + });*/ }); beforeEach(async () => { await blockchainLifecycle.startAsync(); @@ -196,23 +211,15 @@ describe.only(ContractName.CompliantForwarder, () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); }); it.only('should transfer the correct amounts when maker and taker are compliant', async () => { - - - const method = new AbiEncoder.Method(compliantForwarderInstance.abi[0] as MethodAbi); - const args = [ - compliantSignedFillOrderTx.salt, - compliantSignedFillOrderTx.signerAddress, - compliantSignedFillOrderTx.data, - compliantSignedFillOrderTx.signature - ]; - console.log(method.encode(args, {annotate: true})); - - await compliantForwarderInstance.executeTransaction.sendTransactionAsync( + const txHash = await compliantForwarderInstance.executeTransaction.sendTransactionAsync( compliantSignedFillOrderTx.salt, compliantSignedFillOrderTx.signerAddress, compliantSignedFillOrderTx.data, compliantSignedFillOrderTx.signature, ); + const decoder = new LogDecoder(web3Wrapper); + const tx = await decoder.getTxWithDecodedLogsAsync(txHash); + console.log(JSON.stringify(tx, null, 4)); const newBalances = await erc20Wrapper.getBalancesAsync(); const makerAssetFillAmount = takerAssetFillAmount .times(compliantSignedOrder.makerAssetAmount) @@ -298,7 +305,7 @@ describe.only(ContractName.CompliantForwarder, () => { RevertReason.TakerUnverified ); }); - it('should revert if maker address is not compliant (does not hold a Yes Token)', async () => { + it.only('should revert if maker address is not compliant (does not hold a Yes Token)', async () => { // Create signed order with non-compliant maker address const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ senderAddress: compliantForwarderInstance.address, -- cgit From 8c9d48477d47fdb974e03e1beb9d6752d1149ea1 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Fri, 30 Nov 2018 16:43:22 -0800 Subject: Exchange selector contract - autogenerated --- .../utils/ExchangeSelectors/ExchangeSelectors.sol | 162 +++++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 packages/contracts/contracts/utils/ExchangeSelectors/ExchangeSelectors.sol (limited to 'packages') diff --git a/packages/contracts/contracts/utils/ExchangeSelectors/ExchangeSelectors.sol b/packages/contracts/contracts/utils/ExchangeSelectors/ExchangeSelectors.sol new file mode 100644 index 000000000..d4c9bef28 --- /dev/null +++ b/packages/contracts/contracts/utils/ExchangeSelectors/ExchangeSelectors.sol @@ -0,0 +1,162 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; + + +contract ExchangeSelectors { + // filled + bytes4 constant filledSelector = 0x288cdc91; + bytes4 constant filledSelectorGenerator = bytes4(keccak256('filled(bytes32)')); + + // batchFillOrders + bytes4 constant batchFillOrdersSelector = 0x297bb70b; + bytes4 constant batchFillOrdersSelectorGenerator = bytes4(keccak256('batchFillOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256[],bytes[])')); + + // cancelled + bytes4 constant cancelledSelector = 0x2ac12622; + bytes4 constant cancelledSelectorGenerator = bytes4(keccak256('cancelled(bytes32)')); + + // preSign + bytes4 constant preSignSelector = 0x3683ef8e; + bytes4 constant preSignSelectorGenerator = bytes4(keccak256('preSign(bytes32,address,bytes)')); + + // matchOrders + bytes4 constant matchOrdersSelector = 0x3c28d861; + bytes4 constant matchOrdersSelectorGenerator = bytes4(keccak256('matchOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),(address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),bytes,bytes)')); + + // fillOrderNoThrow + bytes4 constant fillOrderNoThrowSelector = 0x3e228bae; + bytes4 constant fillOrderNoThrowSelectorGenerator = bytes4(keccak256('fillOrderNoThrow((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),uint256,bytes)')); + + // assetProxies + bytes4 constant assetProxiesSelector = 0x3fd3c997; + bytes4 constant assetProxiesSelectorGenerator = bytes4(keccak256('assetProxies(bytes4)')); + + // batchCancelOrders + bytes4 constant batchCancelOrdersSelector = 0x4ac14782; + bytes4 constant batchCancelOrdersSelectorGenerator = bytes4(keccak256('batchCancelOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[])')); + + // batchFillOrKillOrders + bytes4 constant batchFillOrKillOrdersSelector = 0x4d0ae546; + bytes4 constant batchFillOrKillOrdersSelectorGenerator = bytes4(keccak256('batchFillOrKillOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256[],bytes[])')); + + // cancelOrdersUpTo + bytes4 constant cancelOrdersUpToSelector = 0x4f9559b1; + bytes4 constant cancelOrdersUpToSelectorGenerator = bytes4(keccak256('cancelOrdersUpTo(uint256)')); + + // batchFillOrdersNoThrow + bytes4 constant batchFillOrdersNoThrowSelector = 0x50dde190; + bytes4 constant batchFillOrdersNoThrowSelectorGenerator = bytes4(keccak256('batchFillOrdersNoThrow((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256[],bytes[])')); + + // getAssetProxy + bytes4 constant getAssetProxySelector = 0x60704108; + bytes4 constant getAssetProxySelectorGenerator = bytes4(keccak256('getAssetProxy(bytes4)')); + + // transactions + bytes4 constant transactionsSelector = 0x642f2eaf; + bytes4 constant transactionsSelectorGenerator = bytes4(keccak256('transactions(bytes32)')); + + // fillOrKillOrder + bytes4 constant fillOrKillOrderSelector = 0x64a3bc15; + bytes4 constant fillOrKillOrderSelectorGenerator = bytes4(keccak256('fillOrKillOrder((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),uint256,bytes)')); + + // setSignatureValidatorApproval + bytes4 constant setSignatureValidatorApprovalSelector = 0x77fcce68; + bytes4 constant setSignatureValidatorApprovalSelectorGenerator = bytes4(keccak256('setSignatureValidatorApproval(address,bool)')); + + // allowedValidators + bytes4 constant allowedValidatorsSelector = 0x7b8e3514; + bytes4 constant allowedValidatorsSelectorGenerator = bytes4(keccak256('allowedValidators(address,address)')); + + // marketSellOrders + bytes4 constant marketSellOrdersSelector = 0x7e1d9808; + bytes4 constant marketSellOrdersSelectorGenerator = bytes4(keccak256('marketSellOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256,bytes[])')); + + // getOrdersInfo + bytes4 constant getOrdersInfoSelector = 0x7e9d74dc; + bytes4 constant getOrdersInfoSelectorGenerator = bytes4(keccak256('getOrdersInfo((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[])')); + + // preSigned + bytes4 constant preSignedSelector = 0x82c174d0; + bytes4 constant preSignedSelectorGenerator = bytes4(keccak256('preSigned(bytes32,address)')); + + // owner + bytes4 constant ownerSelector = 0x8da5cb5b; + bytes4 constant ownerSelectorGenerator = bytes4(keccak256('owner()')); + + // isValidSignature + bytes4 constant isValidSignatureSelector = 0x93634702; + bytes4 constant isValidSignatureSelectorGenerator = bytes4(keccak256('isValidSignature(bytes32,address,bytes)')); + + // marketBuyOrdersNoThrow + bytes4 constant marketBuyOrdersNoThrowSelector = 0xa3e20380; + bytes4 constant marketBuyOrdersNoThrowSelectorGenerator = bytes4(keccak256('marketBuyOrdersNoThrow((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256,bytes[])')); + + // fillOrder + bytes4 constant fillOrderSelector = 0xb4be83d5; + bytes4 constant fillOrderSelectorGenerator = bytes4(keccak256('fillOrder((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),uint256,bytes)')); + + // executeTransaction + bytes4 constant executeTransactionSelector = 0xbfc8bfce; + bytes4 constant executeTransactionSelectorGenerator = bytes4(keccak256('executeTransaction(uint256,address,bytes,bytes)')); + + // registerAssetProxy + bytes4 constant registerAssetProxySelector = 0xc585bb93; + bytes4 constant registerAssetProxySelectorGenerator = bytes4(keccak256('registerAssetProxy(address)')); + + // getOrderInfo + bytes4 constant getOrderInfoSelector = 0xc75e0a81; + bytes4 constant getOrderInfoSelectorGenerator = bytes4(keccak256('getOrderInfo((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes))')); + + // cancelOrder + bytes4 constant cancelOrderSelector = 0xd46b02c3; + bytes4 constant cancelOrderSelectorGenerator = bytes4(keccak256('cancelOrder((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes))')); + + // orderEpoch + bytes4 constant orderEpochSelector = 0xd9bfa73e; + bytes4 constant orderEpochSelectorGenerator = bytes4(keccak256('orderEpoch(address,address)')); + + // ZRX_ASSET_DATA + bytes4 constant ZRX_ASSET_DATASelector = 0xdb123b1a; + bytes4 constant ZRX_ASSET_DATASelectorGenerator = bytes4(keccak256('ZRX_ASSET_DATA()')); + + // marketSellOrdersNoThrow + bytes4 constant marketSellOrdersNoThrowSelector = 0xdd1c7d18; + bytes4 constant marketSellOrdersNoThrowSelectorGenerator = bytes4(keccak256('marketSellOrdersNoThrow((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256,bytes[])')); + + // EIP712_DOMAIN_HASH + bytes4 constant EIP712_DOMAIN_HASHSelector = 0xe306f779; + bytes4 constant EIP712_DOMAIN_HASHSelectorGenerator = bytes4(keccak256('EIP712_DOMAIN_HASH()')); + + // marketBuyOrders + bytes4 constant marketBuyOrdersSelector = 0xe5fa431b; + bytes4 constant marketBuyOrdersSelectorGenerator = bytes4(keccak256('marketBuyOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256,bytes[])')); + + // currentContextAddress + bytes4 constant currentContextAddressSelector = 0xeea086ba; + bytes4 constant currentContextAddressSelectorGenerator = bytes4(keccak256('currentContextAddress()')); + + // transferOwnership + bytes4 constant transferOwnershipSelector = 0xf2fde38b; + bytes4 constant transferOwnershipSelectorGenerator = bytes4(keccak256('transferOwnership(address)')); + + // VERSION + bytes4 constant VERSIONSelector = 0xffa1ad74; + bytes4 constant VERSIONSelectorGenerator = bytes4(keccak256('VERSION()')); +} \ No newline at end of file -- cgit From 61a906e9e774b40cb1c1053005fef98aa1cc7c85 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 3 Dec 2018 12:23:30 -0800 Subject: Abstract address validation in asm --- .../CompliantForwarder/CompliantForwarder.sol | 34 +++++++++++++--------- 1 file changed, 21 insertions(+), 13 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol index 739f55024..27c578eae 100644 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -72,12 +72,12 @@ contract CompliantForwarder is ExchangeSelectors{ mstore(add(addressesToValidate_, offset), addressToValidate) } - function appendMakerAddressFromOrder(paramIndex) -> makerAddress { + function appendMakerAddressFromOrder(paramIndex) { let exchangeTxPtr := calldataload(0x44) // Add 0x20 for length offset and 0x04 for selector offset let orderPtrRelativeToExchangeTx := calldataload(add(0x4, add(exchangeTxPtr, 0x24))) // 0x60 let orderPtr := add(0x4,add(exchangeTxPtr, add(0x24, orderPtrRelativeToExchangeTx))) - makerAddress := calldataload(orderPtr) + let makerAddress := calldataload(orderPtr) addAddressToValidate(makerAddress) } @@ -96,8 +96,8 @@ contract CompliantForwarder is ExchangeSelectors{ } case 0xb4be83d500000000000000000000000000000000000000000000000000000000 /* fillOrder */ { - one := appendMakerAddressFromOrder(0) - //appendSignerAddress() + appendMakerAddressFromOrder(0) + addAddressToValidate(signerAddress) } case 0xd46b02c300000000000000000000000000000000000000000000000000000000 /* cancelOrder */ {} default { @@ -110,23 +110,32 @@ contract CompliantForwarder is ExchangeSelectors{ mstore(0x40, newMemFreePtr) // Validate addresses - /* let complianceTokenAddress := sload(COMPLIANCE_TOKEN_slot) - for {let i := add(32, mload(addressesToValidate))} lt(i, add(addressesToValidate, add(32, mul(nAddressesToValidate, 32)))) {i := add(i, 32)} { + for {let i := add(0x20, addressesToValidate)} lt(i, add(addressesToValidate, add(32, mul(nAddressesToValidate, 32)))) {i := add(i, 32)} { + // Construct calldata for `COMPLIANCE_TOKEN.balanceOf` + mstore(newMemFreePtr, 0x70a0823100000000000000000000000000000000000000000000000000000000) + mstore(add(4, newMemFreePtr), mload(i)) + // call `COMPLIANCE_TOKEN.balanceOf` let success := call( gas, // forward all gas complianceTokenAddress, // call address of asset proxy 0, // don't send any ETH - i, // pointer to start of input - 32, // length of input (one padded address) - 0, // write output over memory that won't be reused - 0 // don't copy output to memory + newMemFreePtr, // pointer to start of input + 0x24, // length of input (one padded address) + newMemFreePtr, // write output to next free memory offset + 0x20 // reserve space for return balance (0x20 bytes) ) if eq(success, 0) { revert(0, 100) } - }*/ + + // Revert if balance not held + let addressBalance := mload(newMemFreePtr) + if eq(addressBalance, 0) { + revert(0, 100) + } + } validatedAddresses := addressesToValidate selectorS := selector @@ -135,12 +144,11 @@ contract CompliantForwarder is ExchangeSelectors{ emit ValidatedAddresses(selectorS, one, validatedAddresses); // All entities are verified. Execute fillOrder. - /* EXCHANGE.executeTransaction( salt, signerAddress, signedExchangeTransaction, signature - );*/ + ); } } \ No newline at end of file -- cgit From 16bd0ce7ec834029b5a6e8aa308c1d52ce6130ea Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 3 Dec 2018 14:48:10 -0800 Subject: generalized KYC extension passing all tests --- .../extensions/CompliantForwarder/CompliantForwarder.sol | 12 +++++++++++- packages/contracts/test/extensions/compliant_forwarder.ts | 8 ++++---- packages/types/src/index.ts | 1 + 3 files changed, 16 insertions(+), 5 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol index 27c578eae..b79d8db54 100644 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -127,13 +127,23 @@ contract CompliantForwarder is ExchangeSelectors{ 0x20 // reserve space for return balance (0x20 bytes) ) if eq(success, 0) { + // Revert with `Error("BALANCE_CHECK_FAILED")` + mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) + mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000) + mstore(64, 0x0000001453454e4445525f4e4f545f415554484f52495a454400000000000000) + mstore(96, 0) revert(0, 100) } // Revert if balance not held let addressBalance := mload(newMemFreePtr) if eq(addressBalance, 0) { - revert(0, 100) + // Revert with `Error("AT_LEAST_ONE_ADDRESS_HAS_ZERO_BALANCE")` + mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) + mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000) + mstore(64, 0x0000002541545f4c454153545f4f4e455f414444524553535f4841535f5a4552) + mstore(96, 0x4f5f42414c414e43450000000000000000000000000000000000000000000000) + revert(0, 109) } } diff --git a/packages/contracts/test/extensions/compliant_forwarder.ts b/packages/contracts/test/extensions/compliant_forwarder.ts index 4eedffe05..639893798 100644 --- a/packages/contracts/test/extensions/compliant_forwarder.ts +++ b/packages/contracts/test/extensions/compliant_forwarder.ts @@ -210,7 +210,7 @@ describe.only(ContractName.CompliantForwarder, () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); }); - it.only('should transfer the correct amounts when maker and taker are compliant', async () => { + it('should transfer the correct amounts when maker and taker are compliant', async () => { const txHash = await compliantForwarderInstance.executeTransaction.sendTransactionAsync( compliantSignedFillOrderTx.salt, compliantSignedFillOrderTx.signerAddress, @@ -302,10 +302,10 @@ describe.only(ContractName.CompliantForwarder, () => { compliantSignedFillOrderTx.data, compliantSignedFillOrderTx.signature, ), - RevertReason.TakerUnverified + RevertReason.AtLeastOneAddressHasZeroBalance ); }); - it.only('should revert if maker address is not compliant (does not hold a Yes Token)', async () => { + it('should revert if maker address is not compliant (does not hold a Yes Token)', async () => { // Create signed order with non-compliant maker address const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ senderAddress: compliantForwarderInstance.address, @@ -330,7 +330,7 @@ describe.only(ContractName.CompliantForwarder, () => { signedFillOrderTx.data, signedFillOrderTx.signature, ), - RevertReason.MakerUnverified + RevertReason.AtLeastOneAddressHasZeroBalance ); }); }); diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 0c6fd7fd7..022b24e70 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -245,6 +245,7 @@ export enum RevertReason { InvalidAssetData = 'INVALID_ASSET_DATA', MakerUnverified = 'MAKER_UNVERIFED', TakerUnverified = 'TAKER_UNVERIFIED', + AtLeastOneAddressHasZeroBalance = 'AT_LEAST_ONE_ADDRESS_HAS_ZERO_BALANCE', } export enum StatusCodes { -- cgit From bab7569ed9b0739a3d01c64dea17adb1bf5b82e1 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 3 Dec 2018 15:38:06 -0800 Subject: Refactoring asm --- .../CompliantForwarder/CompliantForwarder.sol | 44 ++++++++++++++++++++-- 1 file changed, 41 insertions(+), 3 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol index b79d8db54..4ad9092bd 100644 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -72,7 +72,42 @@ contract CompliantForwarder is ExchangeSelectors{ mstore(add(addressesToValidate_, offset), addressToValidate) } - function appendMakerAddressFromOrder(paramIndex) { + function toGlobalCalldataOffset(offset) -> globalOffset { + globalOffset := add(0x4, offset) + } + + function toExchangeCalldataOffset(offset, orderParamIndex) -> exchangeOffset { + // exchangeTxPtr at global level + // 0x20 for length offset into exchange TX + // 0x4 for function selector in exhcange TX + let exchangeTxPtr := calldataload(0x44) + exchangeOffset := add(0x4, add(exchangeTxPtr, add(0x24, offset))) + } + + function toOrderOffset(offset, orderParamIndex) -> orderOffset { + let exchangeOffset := calldataload( + toExchangeCalldataOffset( + offset, + orderParamIndex + ) + ) + orderOffset := toExchangeCalldataOffset(exchangeOffset, orderParamIndex) + } + + // function readMakerFieldFromOrder() + + /* + function readFieldFromOrder() + + function readMakerFieldFromOrder()*/ + + function appendMakerAddressFromOrder(orderParamIndex) { + let makerAddress := calldataload(toOrderOffset(0 /* makerAddress is at 0'th field */, 0 /*order is 1st param*/)) + addAddressToValidate(makerAddress) + } + +/* + function appendMakerAddressFromOrderSet(paramIndex) { let exchangeTxPtr := calldataload(0x44) // Add 0x20 for length offset and 0x04 for selector offset let orderPtrRelativeToExchangeTx := calldataload(add(0x4, add(exchangeTxPtr, 0x24))) // 0x60 @@ -80,6 +115,7 @@ contract CompliantForwarder is ExchangeSelectors{ let makerAddress := calldataload(orderPtr) addAddressToValidate(makerAddress) } +*/ // Extract addresses to validate @@ -88,11 +124,13 @@ contract CompliantForwarder is ExchangeSelectors{ switch selector case 0x097bb70b00000000000000000000000000000000000000000000000000000000 /* batchFillOrders */ { - + //appendMakerAddressFromOrderSet() } case 0x3c28d86100000000000000000000000000000000000000000000000000000000 /* matchOrders */ { - + // appendMakerAddressFromOrder(0) + //// appendMakerAddressFromOrder(1) + // addAddressToValidate(signerAddress) } case 0xb4be83d500000000000000000000000000000000000000000000000000000000 /* fillOrder */ { -- cgit From a332c5e5c2ff11a7d4bd534f694d7859483a33f0 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 3 Dec 2018 16:36:19 -0800 Subject: Code to validate addresses from order arrays --- .../CompliantForwarder/CompliantForwarder.sol | 29 ++++++++++++++++------ .../test/extensions/compliant_forwarder.ts | 28 ++++++++++++++++++++- 2 files changed, 49 insertions(+), 8 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol index 4ad9092bd..ee32b135e 100644 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -33,7 +33,7 @@ contract CompliantForwarder is ExchangeSelectors{ event ValidatedAddresses ( bytes32 selector, - address one, + bytes32 one, address[] addresses ); @@ -55,7 +55,7 @@ contract CompliantForwarder is ExchangeSelectors{ // Validate `signedFillOrderTransaction` address[] memory validatedAddresses; bytes32 selectorS; - address one; + bytes32 one; assembly { // Adds address to validate function addAddressToValidate(addressToValidate) { @@ -106,6 +106,17 @@ contract CompliantForwarder is ExchangeSelectors{ addAddressToValidate(makerAddress) } + function appendMakerAddressesFromOrderSet(orderSetParamIndex) -> one { + let orderSetPtr := calldataload(toExchangeCalldataOffset(0, 0)) + let orderSetPtrCalldata := toExchangeCalldataOffset(add(orderSetPtr, 0x20), 0) + let orderSetLength := calldataload(toExchangeCalldataOffset(orderSetPtr, 0)) + for {let orderPtrOffset := add(0x20, orderSetPtr)} lt(orderPtrOffset, add(0x20, add(orderSetPtr, mul(0x20, orderSetLength)))) {orderPtrOffset := add(0x20, orderPtrOffset)} { + let orderPtr := calldataload(toExchangeCalldataOffset(orderPtrOffset, 0)) + let makerAddress := calldataload(add(orderSetPtrCalldata, orderPtr)) + addAddressToValidate(makerAddress) + } + } + /* function appendMakerAddressFromOrderSet(paramIndex) { let exchangeTxPtr := calldataload(0x44) @@ -118,13 +129,15 @@ contract CompliantForwarder is ExchangeSelectors{ */ + + // Extract addresses to validate let exchangeTxPtr1 := calldataload(0x44) let selector := and(calldataload(add(0x4, add(0x20, exchangeTxPtr1))), 0xffffffff00000000000000000000000000000000000000000000000000000000) switch selector - case 0x097bb70b00000000000000000000000000000000000000000000000000000000 /* batchFillOrders */ + case 0x297bb70b00000000000000000000000000000000000000000000000000000000 /* batchFillOrders */ { - //appendMakerAddressFromOrderSet() + one := appendMakerAddressesFromOrderSet(0) } case 0x3c28d86100000000000000000000000000000000000000000000000000000000 /* matchOrders */ { @@ -147,6 +160,7 @@ contract CompliantForwarder is ExchangeSelectors{ let newMemFreePtr := add(addressesToValidate, add(0x20, mul(mload(addressesToValidate), 0x20))) mstore(0x40, newMemFreePtr) + /* // Validate addresses let complianceTokenAddress := sload(COMPLIANCE_TOKEN_slot) for {let i := add(0x20, addressesToValidate)} lt(i, add(addressesToValidate, add(32, mul(nAddressesToValidate, 32)))) {i := add(i, 32)} { @@ -165,7 +179,7 @@ contract CompliantForwarder is ExchangeSelectors{ 0x20 // reserve space for return balance (0x20 bytes) ) if eq(success, 0) { - // Revert with `Error("BALANCE_CHECK_FAILED")` + // Revert with `Error("BALANCE_CHECK_FAILED")` @TODO mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000) mstore(64, 0x0000001453454e4445525f4e4f545f415554484f52495a454400000000000000) @@ -183,7 +197,7 @@ contract CompliantForwarder is ExchangeSelectors{ mstore(96, 0x4f5f42414c414e43450000000000000000000000000000000000000000000000) revert(0, 109) } - } + }*/ validatedAddresses := addressesToValidate selectorS := selector @@ -192,11 +206,12 @@ contract CompliantForwarder is ExchangeSelectors{ emit ValidatedAddresses(selectorS, one, validatedAddresses); // All entities are verified. Execute fillOrder. + /* EXCHANGE.executeTransaction( salt, signerAddress, signedExchangeTransaction, signature - ); + );*/ } } \ No newline at end of file diff --git a/packages/contracts/test/extensions/compliant_forwarder.ts b/packages/contracts/test/extensions/compliant_forwarder.ts index 639893798..846414685 100644 --- a/packages/contracts/test/extensions/compliant_forwarder.ts +++ b/packages/contracts/test/extensions/compliant_forwarder.ts @@ -206,7 +206,7 @@ describe.only(ContractName.CompliantForwarder, () => { afterEach(async () => { await blockchainLifecycle.revertAsync(); }); - describe.only('fillOrder', () => { + describe('fillOrder', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); }); @@ -334,6 +334,32 @@ describe.only(ContractName.CompliantForwarder, () => { ); }); }); + + describe.only('batchFillOrders', () => { + beforeEach(async () => { + erc20Balances = await erc20Wrapper.getBalancesAsync(); + }); + it.only ('should transfer the correct amounts when maker and taker are compliant', async () => { + let order2 = _.cloneDeep(compliantSignedOrder); + order2.makerAddress = `0x${_.reverse(compliantSignedOrder.makerAddress.slice(2).split('')).join('')}`; + const orders = [compliantSignedOrder, order2]; + const fillAmounts = [new BigNumber(4), new BigNumber(4)]; + const signatures = ["0xabcd", "0xabcd"]; + const exchangeCalldata = exchangeInstance.batchFillOrders.getABIEncodedTransactionData(orders, fillAmounts, signatures); + console.log('*'.repeat(40), exchangeCalldata, '*'.repeat(40)); + console.log('****** MAKER ADDRESS = ', compliantSignedOrder.makerAddress); + + const txHash = await compliantForwarderInstance.executeTransaction.sendTransactionAsync( + compliantSignedFillOrderTx.salt, + compliantSignedFillOrderTx.signerAddress, + exchangeCalldata, + compliantSignedFillOrderTx.signature, + ); + const decoder = new LogDecoder(web3Wrapper); + const tx = await decoder.getTxWithDecodedLogsAsync(txHash); + console.log(JSON.stringify(tx, null, 4)); + }); + }); }); // tslint:disable:max-file-line-count // tslint:enable:no-unnecessary-type-assertion -- cgit From 8007ef6c0b08ce7d2394323f000df7baeb254ebf Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 3 Dec 2018 17:02:58 -0800 Subject: cleaning I --- .../CompliantForwarder/CompliantForwarder.sol | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol index ee32b135e..0c596dfc2 100644 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -117,6 +117,22 @@ contract CompliantForwarder is ExchangeSelectors{ } } + function exchangeCalldataload(offset) -> value { + value := calldataload(toExchangeCalldataOffset(offset, 0)) + } + + + function appendMakerAddressesFromOrderSet2(orderSetParamIndex) -> one { + let orderSetPtr := exchangeCalldataload(0) + let orderSetLength := exchangeCalldataload(orderSetPtr) + + for {let orderPtrOffset := add(0x20, orderSetPtr)} lt(orderPtrOffset, add(0x20, add(orderSetPtr, mul(0x20, orderSetLength)))) {orderPtrOffset := add(0x20, orderPtrOffset)} { + let orderPtr := exchangeCalldataload(orderPtrOffset) + let makerAddress := exchangeCalldataload(add(orderSetPtr, add(0x20, orderPtr))) + addAddressToValidate(makerAddress) + } + } + /* function appendMakerAddressFromOrderSet(paramIndex) { let exchangeTxPtr := calldataload(0x44) @@ -137,7 +153,7 @@ contract CompliantForwarder is ExchangeSelectors{ switch selector case 0x297bb70b00000000000000000000000000000000000000000000000000000000 /* batchFillOrders */ { - one := appendMakerAddressesFromOrderSet(0) + one := appendMakerAddressesFromOrderSet2(0) } case 0x3c28d86100000000000000000000000000000000000000000000000000000000 /* matchOrders */ { -- cgit From c040ad085063ec67abfd73c5cf61739f54d2a7a6 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 3 Dec 2018 17:18:34 -0800 Subject: cleaning --- .../CompliantForwarder/CompliantForwarder.sol | 60 ++++------------------ .../test/extensions/compliant_forwarder.ts | 15 +++--- 2 files changed, 19 insertions(+), 56 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol index 0c596dfc2..2d06d69e6 100644 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -84,76 +84,36 @@ contract CompliantForwarder is ExchangeSelectors{ exchangeOffset := add(0x4, add(exchangeTxPtr, add(0x24, offset))) } - function toOrderOffset(offset, orderParamIndex) -> orderOffset { - let exchangeOffset := calldataload( - toExchangeCalldataOffset( - offset, - orderParamIndex - ) - ) - orderOffset := toExchangeCalldataOffset(exchangeOffset, orderParamIndex) + function exchangeCalldataload(offset) -> value { + + value := calldataload(toExchangeCalldataOffset(offset, 0)) } - // function readMakerFieldFromOrder() - - /* - function readFieldFromOrder() - - function readMakerFieldFromOrder()*/ - function appendMakerAddressFromOrder(orderParamIndex) { - let makerAddress := calldataload(toOrderOffset(0 /* makerAddress is at 0'th field */, 0 /*order is 1st param*/)) + let orderPtr := exchangeCalldataload(0) + let makerAddress := exchangeCalldataload(orderPtr) addAddressToValidate(makerAddress) } function appendMakerAddressesFromOrderSet(orderSetParamIndex) -> one { - let orderSetPtr := calldataload(toExchangeCalldataOffset(0, 0)) - let orderSetPtrCalldata := toExchangeCalldataOffset(add(orderSetPtr, 0x20), 0) - let orderSetLength := calldataload(toExchangeCalldataOffset(orderSetPtr, 0)) - for {let orderPtrOffset := add(0x20, orderSetPtr)} lt(orderPtrOffset, add(0x20, add(orderSetPtr, mul(0x20, orderSetLength)))) {orderPtrOffset := add(0x20, orderPtrOffset)} { - let orderPtr := calldataload(toExchangeCalldataOffset(orderPtrOffset, 0)) - let makerAddress := calldataload(add(orderSetPtrCalldata, orderPtr)) - addAddressToValidate(makerAddress) - } - } - - function exchangeCalldataload(offset) -> value { - value := calldataload(toExchangeCalldataOffset(offset, 0)) - } - - - function appendMakerAddressesFromOrderSet2(orderSetParamIndex) -> one { let orderSetPtr := exchangeCalldataload(0) let orderSetLength := exchangeCalldataload(orderSetPtr) - - for {let orderPtrOffset := add(0x20, orderSetPtr)} lt(orderPtrOffset, add(0x20, add(orderSetPtr, mul(0x20, orderSetLength)))) {orderPtrOffset := add(0x20, orderPtrOffset)} { + let orderSetElementPtr := add(orderSetPtr, 0x20) + let orderSetElementEndPtr := add(orderSetElementPtr, mul(orderSetLength, 0x20)) + for {let orderPtrOffset := orderSetElementPtr} lt(orderPtrOffset, orderSetElementEndPtr) {orderPtrOffset := add(orderPtrOffset, 0x20)} { let orderPtr := exchangeCalldataload(orderPtrOffset) - let makerAddress := exchangeCalldataload(add(orderSetPtr, add(0x20, orderPtr))) + let makerAddress := exchangeCalldataload(add(orderPtr, orderSetElementPtr)) addAddressToValidate(makerAddress) } } -/* - function appendMakerAddressFromOrderSet(paramIndex) { - let exchangeTxPtr := calldataload(0x44) - // Add 0x20 for length offset and 0x04 for selector offset - let orderPtrRelativeToExchangeTx := calldataload(add(0x4, add(exchangeTxPtr, 0x24))) // 0x60 - let orderPtr := add(0x4,add(exchangeTxPtr, add(0x24, orderPtrRelativeToExchangeTx))) - let makerAddress := calldataload(orderPtr) - addAddressToValidate(makerAddress) - } -*/ - - - - // Extract addresses to validate let exchangeTxPtr1 := calldataload(0x44) let selector := and(calldataload(add(0x4, add(0x20, exchangeTxPtr1))), 0xffffffff00000000000000000000000000000000000000000000000000000000) switch selector case 0x297bb70b00000000000000000000000000000000000000000000000000000000 /* batchFillOrders */ { - one := appendMakerAddressesFromOrderSet2(0) + one := appendMakerAddressesFromOrderSet(0) } case 0x3c28d86100000000000000000000000000000000000000000000000000000000 /* matchOrders */ { diff --git a/packages/contracts/test/extensions/compliant_forwarder.ts b/packages/contracts/test/extensions/compliant_forwarder.ts index 846414685..e995e5435 100644 --- a/packages/contracts/test/extensions/compliant_forwarder.ts +++ b/packages/contracts/test/extensions/compliant_forwarder.ts @@ -206,11 +206,11 @@ describe.only(ContractName.CompliantForwarder, () => { afterEach(async () => { await blockchainLifecycle.revertAsync(); }); - describe('fillOrder', () => { + describe.only('fillOrder', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); }); - it('should transfer the correct amounts when maker and taker are compliant', async () => { + it.only('should transfer the correct amounts when maker and taker are compliant', async () => { const txHash = await compliantForwarderInstance.executeTransaction.sendTransactionAsync( compliantSignedFillOrderTx.salt, compliantSignedFillOrderTx.signerAddress, @@ -220,7 +220,10 @@ describe.only(ContractName.CompliantForwarder, () => { const decoder = new LogDecoder(web3Wrapper); const tx = await decoder.getTxWithDecodedLogsAsync(txHash); console.log(JSON.stringify(tx, null, 4)); - const newBalances = await erc20Wrapper.getBalancesAsync(); + console.log('****** MAKER ADDRESS = ', compliantSignedOrder.makerAddress); + + + /*const newBalances = await erc20Wrapper.getBalancesAsync(); const makerAssetFillAmount = takerAssetFillAmount .times(compliantSignedOrder.makerAssetAmount) .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); @@ -250,7 +253,7 @@ describe.only(ContractName.CompliantForwarder, () => { ); expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)), - ); + );*/ }); it('should revert if the signed transaction is not intended for fillOrder', async () => { // Create signed order without the fillOrder function selector @@ -335,11 +338,11 @@ describe.only(ContractName.CompliantForwarder, () => { }); }); - describe.only('batchFillOrders', () => { + describe('batchFillOrders', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); }); - it.only ('should transfer the correct amounts when maker and taker are compliant', async () => { + it('should transfer the correct amounts when maker and taker are compliant', async () => { let order2 = _.cloneDeep(compliantSignedOrder); order2.makerAddress = `0x${_.reverse(compliantSignedOrder.makerAddress.slice(2).split('')).join('')}`; const orders = [compliantSignedOrder, order2]; -- cgit From 28a5ed6a9a1e44dd298d98f9f4bcb65b1d021e26 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 3 Dec 2018 17:19:58 -0800 Subject: cleaning --- .../extensions/CompliantForwarder/CompliantForwarder.sol | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol index 2d06d69e6..687e931da 100644 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -72,21 +72,13 @@ contract CompliantForwarder is ExchangeSelectors{ mstore(add(addressesToValidate_, offset), addressToValidate) } - function toGlobalCalldataOffset(offset) -> globalOffset { - globalOffset := add(0x4, offset) - } - - function toExchangeCalldataOffset(offset, orderParamIndex) -> exchangeOffset { + function exchangeCalldataload(offset) -> value { // exchangeTxPtr at global level // 0x20 for length offset into exchange TX // 0x4 for function selector in exhcange TX let exchangeTxPtr := calldataload(0x44) - exchangeOffset := add(0x4, add(exchangeTxPtr, add(0x24, offset))) - } - - function exchangeCalldataload(offset) -> value { - - value := calldataload(toExchangeCalldataOffset(offset, 0)) + let exchangeOffset := add(0x4, add(exchangeTxPtr, add(0x24, offset))) + value := calldataload(exchangeOffset) } function appendMakerAddressFromOrder(orderParamIndex) { -- cgit From df0de071841e2953f6843a86bbce4ecb3ad7b04f Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 3 Dec 2018 17:26:24 -0800 Subject: cleaning --- .../CompliantForwarder/CompliantForwarder.sol | 28 +++++++++++++++------- 1 file changed, 19 insertions(+), 9 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol index 687e931da..b5290dc10 100644 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -72,36 +72,46 @@ contract CompliantForwarder is ExchangeSelectors{ mstore(add(addressesToValidate_, offset), addressToValidate) } + function validateAddress(addressToValidate) { + + } + function exchangeCalldataload(offset) -> value { // exchangeTxPtr at global level // 0x20 for length offset into exchange TX // 0x4 for function selector in exhcange TX let exchangeTxPtr := calldataload(0x44) - let exchangeOffset := add(0x4, add(exchangeTxPtr, add(0x24, offset))) + let exchangeOffset := add(exchangeTxPtr, add(0x24, offset)) value := calldataload(exchangeOffset) } + function loadExchangeData(offset) -> value { + value := exchangeCalldataload(add(offset, 0x4)) + } + function appendMakerAddressFromOrder(orderParamIndex) { - let orderPtr := exchangeCalldataload(0) - let makerAddress := exchangeCalldataload(orderPtr) + let orderPtr := loadExchangeData(0) + let makerAddress := loadExchangeData(orderPtr) addAddressToValidate(makerAddress) } function appendMakerAddressesFromOrderSet(orderSetParamIndex) -> one { - let orderSetPtr := exchangeCalldataload(0) - let orderSetLength := exchangeCalldataload(orderSetPtr) + let orderSetPtr := loadExchangeData(0) + let orderSetLength := loadExchangeData(orderSetPtr) let orderSetElementPtr := add(orderSetPtr, 0x20) let orderSetElementEndPtr := add(orderSetElementPtr, mul(orderSetLength, 0x20)) for {let orderPtrOffset := orderSetElementPtr} lt(orderPtrOffset, orderSetElementEndPtr) {orderPtrOffset := add(orderPtrOffset, 0x20)} { - let orderPtr := exchangeCalldataload(orderPtrOffset) - let makerAddress := exchangeCalldataload(add(orderPtr, orderSetElementPtr)) + let orderPtr := loadExchangeData(orderPtrOffset) + let makerAddress := loadExchangeData(add(orderPtr, orderSetElementPtr)) addAddressToValidate(makerAddress) } } // Extract addresses to validate - let exchangeTxPtr1 := calldataload(0x44) - let selector := and(calldataload(add(0x4, add(0x20, exchangeTxPtr1))), 0xffffffff00000000000000000000000000000000000000000000000000000000) + let selector := and( + exchangeCalldataload(0), + 0xffffffff00000000000000000000000000000000000000000000000000000000 + ) switch selector case 0x297bb70b00000000000000000000000000000000000000000000000000000000 /* batchFillOrders */ { -- cgit From 5863a29a913e6418c5ae9cbec146ad30c3eee6cc Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 3 Dec 2018 17:35:45 -0800 Subject: cleaning --- .../CompliantForwarder/CompliantForwarder.sol | 58 +++++++++++----------- .../test/extensions/compliant_forwarder.ts | 6 +-- 2 files changed, 30 insertions(+), 34 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol index b5290dc10..55100cae1 100644 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -32,8 +32,6 @@ contract CompliantForwarder is ExchangeSelectors{ IERC721Token internal COMPLIANCE_TOKEN; event ValidatedAddresses ( - bytes32 selector, - bytes32 one, address[] addresses ); @@ -52,11 +50,29 @@ contract CompliantForwarder is ExchangeSelectors{ ) external { - // Validate `signedFillOrderTransaction` + // Addresses that are validated below. address[] memory validatedAddresses; - bytes32 selectorS; - bytes32 one; + + /** + * Do not add variables after this point. + * The assembly block may overwrite their values. + */ + + // Validate addresses assembly { + function exchangeCalldataload(offset) -> value { + // exchangeTxPtr at global level + // 0x20 for length offset into exchange TX + // 0x4 for function selector in exhcange TX + let exchangeTxPtr := calldataload(0x44) + let exchangeOffset := add(exchangeTxPtr, add(0x24, offset)) + value := calldataload(exchangeOffset) + } + + function loadExchangeData(offset) -> value { + value := exchangeCalldataload(add(offset, 0x4)) + } + // Adds address to validate function addAddressToValidate(addressToValidate) { // Compute `addressesToValidate` memory location @@ -72,30 +88,13 @@ contract CompliantForwarder is ExchangeSelectors{ mstore(add(addressesToValidate_, offset), addressToValidate) } - function validateAddress(addressToValidate) { - - } - - function exchangeCalldataload(offset) -> value { - // exchangeTxPtr at global level - // 0x20 for length offset into exchange TX - // 0x4 for function selector in exhcange TX - let exchangeTxPtr := calldataload(0x44) - let exchangeOffset := add(exchangeTxPtr, add(0x24, offset)) - value := calldataload(exchangeOffset) - } - - function loadExchangeData(offset) -> value { - value := exchangeCalldataload(add(offset, 0x4)) - } - function appendMakerAddressFromOrder(orderParamIndex) { let orderPtr := loadExchangeData(0) let makerAddress := loadExchangeData(orderPtr) addAddressToValidate(makerAddress) } - function appendMakerAddressesFromOrderSet(orderSetParamIndex) -> one { + function appendMakerAddressesFromOrderSet(orderSetParamIndex) { let orderSetPtr := loadExchangeData(0) let orderSetLength := loadExchangeData(orderSetPtr) let orderSetElementPtr := add(orderSetPtr, 0x20) @@ -115,7 +114,7 @@ contract CompliantForwarder is ExchangeSelectors{ switch selector case 0x297bb70b00000000000000000000000000000000000000000000000000000000 /* batchFillOrders */ { - one := appendMakerAddressesFromOrderSet(0) + appendMakerAddressesFromOrderSet(0) } case 0x3c28d86100000000000000000000000000000000000000000000000000000000 /* matchOrders */ { @@ -133,12 +132,12 @@ contract CompliantForwarder is ExchangeSelectors{ revert(0, 100) } + // let addressesToValidate := mload(0x40) let nAddressesToValidate := mload(addressesToValidate) let newMemFreePtr := add(addressesToValidate, add(0x20, mul(mload(addressesToValidate), 0x20))) mstore(0x40, newMemFreePtr) - /* // Validate addresses let complianceTokenAddress := sload(COMPLIANCE_TOKEN_slot) for {let i := add(0x20, addressesToValidate)} lt(i, add(addressesToValidate, add(32, mul(nAddressesToValidate, 32)))) {i := add(i, 32)} { @@ -175,21 +174,20 @@ contract CompliantForwarder is ExchangeSelectors{ mstore(96, 0x4f5f42414c414e43450000000000000000000000000000000000000000000000) revert(0, 109) } - }*/ + } + // Record validated addresses validatedAddresses := addressesToValidate - selectorS := selector } - emit ValidatedAddresses(selectorS, one, validatedAddresses); + emit ValidatedAddresses(validatedAddresses); // All entities are verified. Execute fillOrder. - /* EXCHANGE.executeTransaction( salt, signerAddress, signedExchangeTransaction, signature - );*/ + ); } } \ No newline at end of file diff --git a/packages/contracts/test/extensions/compliant_forwarder.ts b/packages/contracts/test/extensions/compliant_forwarder.ts index e995e5435..cc05f9981 100644 --- a/packages/contracts/test/extensions/compliant_forwarder.ts +++ b/packages/contracts/test/extensions/compliant_forwarder.ts @@ -221,9 +221,7 @@ describe.only(ContractName.CompliantForwarder, () => { const tx = await decoder.getTxWithDecodedLogsAsync(txHash); console.log(JSON.stringify(tx, null, 4)); console.log('****** MAKER ADDRESS = ', compliantSignedOrder.makerAddress); - - - /*const newBalances = await erc20Wrapper.getBalancesAsync(); + const newBalances = await erc20Wrapper.getBalancesAsync(); const makerAssetFillAmount = takerAssetFillAmount .times(compliantSignedOrder.makerAssetAmount) .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); @@ -253,7 +251,7 @@ describe.only(ContractName.CompliantForwarder, () => { ); expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)), - );*/ + ); }); it('should revert if the signed transaction is not intended for fillOrder', async () => { // Create signed order without the fillOrder function selector -- cgit From 4217d0cd7d7993b72415cd34a8fc3fab6c17976e Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 3 Dec 2018 17:43:00 -0800 Subject: cleanup --- .../CompliantForwarder/CompliantForwarder.sol | 33 ++++++++++++---------- 1 file changed, 18 insertions(+), 15 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol index 55100cae1..f81b975be 100644 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -115,6 +115,7 @@ contract CompliantForwarder is ExchangeSelectors{ case 0x297bb70b00000000000000000000000000000000000000000000000000000000 /* batchFillOrders */ { appendMakerAddressesFromOrderSet(0) + addAddressToValidate(signerAddress) } case 0x3c28d86100000000000000000000000000000000000000000000000000000000 /* matchOrders */ { @@ -132,40 +133,42 @@ contract CompliantForwarder is ExchangeSelectors{ revert(0, 100) } - // + // Load addresses to validate from memory let addressesToValidate := mload(0x40) - let nAddressesToValidate := mload(addressesToValidate) - let newMemFreePtr := add(addressesToValidate, add(0x20, mul(mload(addressesToValidate), 0x20))) - mstore(0x40, newMemFreePtr) + let addressesToValidateLength := mload(addressesToValidate) + let addressesToValidateElementPtr := add(addressesToValidate, 0x20) + let addressesToValidateElementEndPtr := add(addressesToValidateElementPtr, mul(addressesToValidateLength, 0x20)) + + // Record new free memory pointer to after `addressesToValidate` array + // This is to avoid corruption when making calls in the loop below. + let freeMemPtr := addressesToValidateElementEndPtr + mstore(0x40, freeMemPtr) // Validate addresses let complianceTokenAddress := sload(COMPLIANCE_TOKEN_slot) - for {let i := add(0x20, addressesToValidate)} lt(i, add(addressesToValidate, add(32, mul(nAddressesToValidate, 32)))) {i := add(i, 32)} { + + for {let addressToValidate := addressesToValidateElementPtr} lt(addressToValidate, addressesToValidateElementEndPtr) {addressToValidate := add(addressToValidate, 0x20)} { // Construct calldata for `COMPLIANCE_TOKEN.balanceOf` - mstore(newMemFreePtr, 0x70a0823100000000000000000000000000000000000000000000000000000000) - mstore(add(4, newMemFreePtr), mload(i)) + mstore(freeMemPtr, 0x70a0823100000000000000000000000000000000000000000000000000000000) + mstore(add(4, freeMemPtr), mload(addressToValidate)) // call `COMPLIANCE_TOKEN.balanceOf` let success := call( gas, // forward all gas complianceTokenAddress, // call address of asset proxy 0, // don't send any ETH - newMemFreePtr, // pointer to start of input + freeMemPtr, // pointer to start of input 0x24, // length of input (one padded address) - newMemFreePtr, // write output to next free memory offset + freeMemPtr, // write output to next free memory offset 0x20 // reserve space for return balance (0x20 bytes) ) if eq(success, 0) { - // Revert with `Error("BALANCE_CHECK_FAILED")` @TODO - mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) - mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000) - mstore(64, 0x0000001453454e4445525f4e4f545f415554484f52495a454400000000000000) - mstore(96, 0) + // @TODO Revert with `Error("BALANCE_CHECK_FAILED")` revert(0, 100) } // Revert if balance not held - let addressBalance := mload(newMemFreePtr) + let addressBalance := mload(freeMemPtr) if eq(addressBalance, 0) { // Revert with `Error("AT_LEAST_ONE_ADDRESS_HAS_ZERO_BALANCE")` mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) -- cgit From 0556defa58ba0a74779a0dc828c112631fb19eb6 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 3 Dec 2018 17:46:25 -0800 Subject: working on batch fills. Compliance part is finished. --- .../extensions/CompliantForwarder/CompliantForwarder.sol | 8 ++++---- packages/contracts/test/extensions/compliant_forwarder.ts | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol index f81b975be..706c2091d 100644 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -171,11 +171,11 @@ contract CompliantForwarder is ExchangeSelectors{ let addressBalance := mload(freeMemPtr) if eq(addressBalance, 0) { // Revert with `Error("AT_LEAST_ONE_ADDRESS_HAS_ZERO_BALANCE")` - mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) + /*mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000) mstore(64, 0x0000002541545f4c454153545f4f4e455f414444524553535f4841535f5a4552) mstore(96, 0x4f5f42414c414e43450000000000000000000000000000000000000000000000) - revert(0, 109) + revert(0, 109)*/ } } @@ -186,11 +186,11 @@ contract CompliantForwarder is ExchangeSelectors{ emit ValidatedAddresses(validatedAddresses); // All entities are verified. Execute fillOrder. - EXCHANGE.executeTransaction( + /* EXCHANGE.executeTransaction( salt, signerAddress, signedExchangeTransaction, signature - ); + );*/ } } \ No newline at end of file diff --git a/packages/contracts/test/extensions/compliant_forwarder.ts b/packages/contracts/test/extensions/compliant_forwarder.ts index cc05f9981..b066f5d08 100644 --- a/packages/contracts/test/extensions/compliant_forwarder.ts +++ b/packages/contracts/test/extensions/compliant_forwarder.ts @@ -206,11 +206,11 @@ describe.only(ContractName.CompliantForwarder, () => { afterEach(async () => { await blockchainLifecycle.revertAsync(); }); - describe.only('fillOrder', () => { + describe('fillOrder', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); }); - it.only('should transfer the correct amounts when maker and taker are compliant', async () => { + it('should transfer the correct amounts when maker and taker are compliant', async () => { const txHash = await compliantForwarderInstance.executeTransaction.sendTransactionAsync( compliantSignedFillOrderTx.salt, compliantSignedFillOrderTx.signerAddress, @@ -336,7 +336,7 @@ describe.only(ContractName.CompliantForwarder, () => { }); }); - describe('batchFillOrders', () => { + describe.only('batchFillOrders', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); }); -- cgit From 3bb147b0f1554366733098dac1af924225effc26 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 4 Dec 2018 13:37:48 -0800 Subject: Documented inline assembly functions --- .../CompliantForwarder/CompliantForwarder.sol | 200 ++++++++++++++++----- .../test/extensions/compliant_forwarder.ts | 4 +- 2 files changed, 153 insertions(+), 51 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol index 706c2091d..2dce95716 100644 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -60,93 +60,193 @@ contract CompliantForwarder is ExchangeSelectors{ // Validate addresses assembly { + /** + * Emulates the `calldataload` opcode on the embedded Exchange calldata, + * which is accessed through `signedExchangeTransaction`. + * @param offset - Offset into the Exchange calldata. + * @return value - Corresponding 32 byte value stored at `offset`. + */ function exchangeCalldataload(offset) -> value { - // exchangeTxPtr at global level - // 0x20 for length offset into exchange TX - // 0x4 for function selector in exhcange TX + // Pointer to exchange transaction + // 0x04 for calldata selector + // 0x40 to access `signedExchangeTransaction`, which is the third parameter let exchangeTxPtr := calldataload(0x44) - let exchangeOffset := add(exchangeTxPtr, add(0x24, offset)) - value := calldataload(exchangeOffset) + + // Offset into Exchange calldata + // We compute this by adding 0x24 to the `exchangeTxPtr` computed above. + // 0x04 for calldata selector + // 0x20 for length field of `signedExchangeTransaction` + let exchangeCalldataOffset := add(exchangeTxPtr, add(0x24, offset)) + value := calldataload(exchangeCalldataOffset) } + /** + * Convenience function that skips the 4 byte selector when loading + * from the embedded Exchange calldata. + * @param offset - Offset into the Exchange calldata (minus the 4 byte selector) + * @return value - Corresponding 32 byte value stored at `offset` + 4. + */ function loadExchangeData(offset) -> value { value := exchangeCalldataload(add(offset, 0x4)) } - // Adds address to validate - function addAddressToValidate(addressToValidate) { - // Compute `addressesToValidate` memory location + /** + * A running list is maintained of addresses to validate. + * This function records an address in this array. + * @param addressToValidate - Address to record for validation. + * @note - Variables are scoped but names are not, so we append + * underscores to names that share the global namespace. + */ + function recordAddressToValidate(addressToValidate) { + // Compute `addressesToValidate` memory offset let addressesToValidate_ := mload(0x40) let nAddressesToValidate_ := mload(addressesToValidate_) // Increment length - nAddressesToValidate_ := add(mload(addressesToValidate_), 1) + nAddressesToValidate_ := add(mload(addressesToValidate_), 0x01) mstore(addressesToValidate_, nAddressesToValidate_) // Append address to validate - let offset := mul(32, nAddressesToValidate_) + let offset := mul(nAddressesToValidate_, 0x20) mstore(add(addressesToValidate_, offset), addressToValidate) } - function appendMakerAddressFromOrder(orderParamIndex) { - let orderPtr := loadExchangeData(0) + /** + * Extracts the maker address from an order stored in the Exchange calldata + * (which is embedded in `signedExchangeTransaction`), and records it in + * the running list of addresses to validate. + * @param orderParamIndex - Index of the order in the Exchange function's signature + */ + function recordMakerAddressFromOrder(orderParamIndex) { + let orderPtr := loadExchangeData(orderParamIndex) let makerAddress := loadExchangeData(orderPtr) - addAddressToValidate(makerAddress) + recordAddressToValidate(makerAddress) } - function appendMakerAddressesFromOrderSet(orderSetParamIndex) { - let orderSetPtr := loadExchangeData(0) - let orderSetLength := loadExchangeData(orderSetPtr) - let orderSetElementPtr := add(orderSetPtr, 0x20) - let orderSetElementEndPtr := add(orderSetElementPtr, mul(orderSetLength, 0x20)) - for {let orderPtrOffset := orderSetElementPtr} lt(orderPtrOffset, orderSetElementEndPtr) {orderPtrOffset := add(orderPtrOffset, 0x20)} { + /** + * Extracts the maker addresses from an array of orders stored in the Exchange calldata + * (which is embedded in `signedExchangeTransaction`), and records them in + * the running list of addresses to validate. + * @param orderArrayParamIndex - Index of the order array in the Exchange function's signature + */ + function recordMakerAddressesFromOrderArray(orderArrayParamIndex) { + let orderArrayPtr := loadExchangeData(0x0) + let orderArrayLength := loadExchangeData(orderArrayPtr) + let orderArrayElementPtr := add(orderArrayPtr, 0x20) + let orderArrayElementEndPtr := add(orderArrayElementPtr, mul(orderArrayLength, 0x20)) + for {let orderPtrOffset := orderArrayElementPtr} lt(orderPtrOffset, orderArrayElementEndPtr) {orderPtrOffset := add(orderPtrOffset, 0x20)} { let orderPtr := loadExchangeData(orderPtrOffset) - let makerAddress := loadExchangeData(add(orderPtr, orderSetElementPtr)) - addAddressToValidate(makerAddress) + let makerAddress := loadExchangeData(add(orderPtr, orderArrayElementPtr)) + recordAddressToValidate(makerAddress) } } - // Extract addresses to validate - let selector := and( - exchangeCalldataload(0), - 0xffffffff00000000000000000000000000000000000000000000000000000000 - ) - switch selector - case 0x297bb70b00000000000000000000000000000000000000000000000000000000 /* batchFillOrders */ - { - appendMakerAddressesFromOrderSet(0) - addAddressToValidate(signerAddress) + /** + * Records address of signer in the running list of addresses to validate. + * @note: We cannot access `signerAddress` directly from within the asm function, + * so it is loaded from the calldata. + */ + function recordSignerAddress() { + // Load the signer address from calldata + // 0x04 for selector + // 0x20 to access `signerAddress`, which is the second parameter. + let signerAddress_ := calldataload(0x24) + recordAddressToValidate(signerAddress_) + } + + /** + * Records addresses to be validated when Exchange transaction is a batch fill variant. + * This is one of: batchFillOrders, batchFillOrKillOrders, batchFillNoThrow + * Reference signature: (Order[],uint256[],bytes[]) + */ + function recordAddressesForBatchFillVariant() { + // Record maker addresses from order array (parameter index 0) + // The signer is the taker for these orders and must also be validated. + recordMakerAddressesFromOrderArray(0) + recordSignerAddress() + } + + /** + * Records addresses to be validated when Exchange transaction is a fill order variant. + * This is one of: fillOrder, fillOrKillOrder, fillOrderNoThrow + * Reference signature: (Order,uint256,bytes) + */ + function recordAddressesForFillOrderVariant() { + // Record maker address from the order (param index 0) + // The signer is the taker for this order and must also be validated. + recordMakerAddressFromOrder(0) + recordSignerAddress() } - case 0x3c28d86100000000000000000000000000000000000000000000000000000000 /* matchOrders */ - { - // appendMakerAddressFromOrder(0) - //// appendMakerAddressFromOrder(1) - // addAddressToValidate(signerAddress) + + /** + * Records addresses to be validated when Exchange transaction is a market fill variant. + * This is one of: marketBuyOrders, marketBuyOrdersNoThrow, marketSellOrders, marketSellOrdersNoThrow + * Reference signature: (Order[],uint256,bytes[]) + */ + function recordAddressesForMarketFillVariant() { + // Record maker addresses from order array (parameter index 0) + // The signer is the taker for these orders and must also be validated. + recordMakerAddressesFromOrderArray(0) + recordSignerAddress() } - case 0xb4be83d500000000000000000000000000000000000000000000000000000000 /* fillOrder */ - { - appendMakerAddressFromOrder(0) - addAddressToValidate(signerAddress) + + /** + * Records addresses to be validated when Exchange transaction is matchOrders. + * Reference signature: matchOrders(Order,Order) + */ + function recordAddressesForMatchOrders() { + // Record maker address from both orders (param indices 0 & 1). + // The signer is the taker and must also be validated. + recordMakerAddressFromOrder(0) + recordMakerAddressFromOrder(1) + recordSignerAddress() } - case 0xd46b02c300000000000000000000000000000000000000000000000000000000 /* cancelOrder */ {} + + ///// Record Addresses to Validate ///// + + // Addresses needing validation depends on which Exchange function is being called. + // Step 1/2 Read the exchange function selector. + let exchangeFunctionSelector := and( + exchangeCalldataload(0x0), + 0xffffffff00000000000000000000000000000000000000000000000000000000 + ) + + // Step 2/2 Extract addresses to validate based on this selector. + // See ../../utils/ExchangeSelectors/ExchangeSelectors.sol for selectors + switch exchangeFunctionSelector + case 0x297bb70b00000000000000000000000000000000000000000000000000000000 { recordAddressesForBatchFillVariant() } // batchFillOrders + case 0x50dde19000000000000000000000000000000000000000000000000000000000 { recordAddressesForBatchFillVariant() } // batchFillOrdersNoThrow + case 0x4d0ae54600000000000000000000000000000000000000000000000000000000 { recordAddressesForBatchFillVariant() } // batchFillOrKillOrders + case 0xb4be83d500000000000000000000000000000000000000000000000000000000 { recordAddressesForFillOrderVariant() } // fillOrder + case 0x3e228bae00000000000000000000000000000000000000000000000000000000 { recordAddressesForFillOrderVariant() } // fillOrderNoThrow + case 0x64a3bc1500000000000000000000000000000000000000000000000000000000 { recordAddressesForFillOrderVariant() } // fillOrKillOrder + case 0xe5fa431b00000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketBuyOrders + case 0xa3e2038000000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketBuyOrdersNoThrow + case 0x7e1d980800000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketSellOrders + case 0xdd1c7d1800000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketSellOrdersNoThrow + case 0x3c28d86100000000000000000000000000000000000000000000000000000000 { recordAddressesForMatchOrders() } // matchOrders + case 0xd46b02c300000000000000000000000000000000000000000000000000000000 {} // cancelOrder + case 0x4ac1478200000000000000000000000000000000000000000000000000000000 {} // batchCancelOrders + case 0x4f9559b100000000000000000000000000000000000000000000000000000000 {} // cancelOrdersUpTo default { revert(0, 100) } - // Load addresses to validate from memory + ///// Validate Recorded Addresses ///// + + // Load from memory the addresses to validate let addressesToValidate := mload(0x40) let addressesToValidateLength := mload(addressesToValidate) let addressesToValidateElementPtr := add(addressesToValidate, 0x20) let addressesToValidateElementEndPtr := add(addressesToValidateElementPtr, mul(addressesToValidateLength, 0x20)) - // Record new free memory pointer to after `addressesToValidate` array + // Set free memory pointer to after `addressesToValidate` array. // This is to avoid corruption when making calls in the loop below. let freeMemPtr := addressesToValidateElementEndPtr mstore(0x40, freeMemPtr) // Validate addresses let complianceTokenAddress := sload(COMPLIANCE_TOKEN_slot) - for {let addressToValidate := addressesToValidateElementPtr} lt(addressToValidate, addressesToValidateElementEndPtr) {addressToValidate := add(addressToValidate, 0x20)} { // Construct calldata for `COMPLIANCE_TOKEN.balanceOf` mstore(freeMemPtr, 0x70a0823100000000000000000000000000000000000000000000000000000000) @@ -171,11 +271,11 @@ contract CompliantForwarder is ExchangeSelectors{ let addressBalance := mload(freeMemPtr) if eq(addressBalance, 0) { // Revert with `Error("AT_LEAST_ONE_ADDRESS_HAS_ZERO_BALANCE")` - /*mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) + mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000) mstore(64, 0x0000002541545f4c454153545f4f4e455f414444524553535f4841535f5a4552) mstore(96, 0x4f5f42414c414e43450000000000000000000000000000000000000000000000) - revert(0, 109)*/ + revert(0, 109) } } @@ -183,14 +283,16 @@ contract CompliantForwarder is ExchangeSelectors{ validatedAddresses := addressesToValidate } + + ///// If we hit this point then all addresses are valid ///// emit ValidatedAddresses(validatedAddresses); - // All entities are verified. Execute fillOrder. - /* EXCHANGE.executeTransaction( + // All addresses are valid. Execute fillOrder. + EXCHANGE.executeTransaction( salt, signerAddress, signedExchangeTransaction, signature - );*/ + ); } } \ No newline at end of file diff --git a/packages/contracts/test/extensions/compliant_forwarder.ts b/packages/contracts/test/extensions/compliant_forwarder.ts index b066f5d08..8fa811936 100644 --- a/packages/contracts/test/extensions/compliant_forwarder.ts +++ b/packages/contracts/test/extensions/compliant_forwarder.ts @@ -206,7 +206,7 @@ describe.only(ContractName.CompliantForwarder, () => { afterEach(async () => { await blockchainLifecycle.revertAsync(); }); - describe('fillOrder', () => { + describe.only('fillOrder', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); }); @@ -336,7 +336,7 @@ describe.only(ContractName.CompliantForwarder, () => { }); }); - describe.only('batchFillOrders', () => { + describe('batchFillOrders', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); }); -- cgit From ba986432eca33ffb4d3c916a78049c6633147b82 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 4 Dec 2018 14:03:48 -0800 Subject: Some comments --- .../extensions/CompliantForwarder/CompliantForwarder.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol index 2dce95716..71c124292 100644 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -229,6 +229,7 @@ contract CompliantForwarder is ExchangeSelectors{ case 0x4ac1478200000000000000000000000000000000000000000000000000000000 {} // batchCancelOrders case 0x4f9559b100000000000000000000000000000000000000000000000000000000 {} // cancelOrdersUpTo default { + // @TODO Revert with `Error("INVALID_OR_UNHANDLED_EXCHANGE_SELECTOR")` revert(0, 100) } @@ -272,9 +273,9 @@ contract CompliantForwarder is ExchangeSelectors{ if eq(addressBalance, 0) { // Revert with `Error("AT_LEAST_ONE_ADDRESS_HAS_ZERO_BALANCE")` mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) - mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000) - mstore(64, 0x0000002541545f4c454153545f4f4e455f414444524553535f4841535f5a4552) - mstore(96, 0x4f5f42414c414e43450000000000000000000000000000000000000000000000) + mstore(0x20, 0x0000002000000000000000000000000000000000000000000000000000000000) + mstore(0x40, 0x0000002541545f4c454153545f4f4e455f414444524553535f4841535f5a4552) + mstore(0x60, 0x4f5f42414c414e43450000000000000000000000000000000000000000000000) revert(0, 109) } } @@ -283,7 +284,6 @@ contract CompliantForwarder is ExchangeSelectors{ validatedAddresses := addressesToValidate } - ///// If we hit this point then all addresses are valid ///// emit ValidatedAddresses(validatedAddresses); -- cgit From dbf1de2e691743672fa4918e7ab0f1e3948401f1 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 4 Dec 2018 14:21:07 -0800 Subject: Revert reasons for balance threshold filter --- .../CompliantForwarder/CompliantForwarder.sol | 41 +++++++++++++++++----- .../test/extensions/compliant_forwarder.ts | 4 +-- packages/types/src/index.ts | 7 ++-- 3 files changed, 39 insertions(+), 13 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol index 71c124292..d33f4f398 100644 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -229,8 +229,18 @@ contract CompliantForwarder is ExchangeSelectors{ case 0x4ac1478200000000000000000000000000000000000000000000000000000000 {} // batchCancelOrders case 0x4f9559b100000000000000000000000000000000000000000000000000000000 {} // cancelOrdersUpTo default { - // @TODO Revert with `Error("INVALID_OR_UNHANDLED_EXCHANGE_SELECTOR")` - revert(0, 100) + // Revert with `Error("INVALID_OR_BLOCKED_EXCHANGE_SELECTOR")` + mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) + mstore(0x20, 0x0000002000000000000000000000000000000000000000000000000000000000) + mstore(0x40, 0x00000024494e56414c49445f4f525f424c4f434b45445f45584348414e47455f) + mstore(0x60, 0x53454c4543544f52000000000000000000000000000000000000000000000000) + mstore(0x80, 0x00000000) + // Revert length calculation: + // 4 -- error selector + // 32 -- offset to string + // 32 -- string length field + // 64 -- strlen(INVALID_OR_BLOCKED_EXCHANGE_SELECTOR) rounded up to nearest 32-byte word. + revert(0, 132) } ///// Validate Recorded Addresses ///// @@ -264,19 +274,34 @@ contract CompliantForwarder is ExchangeSelectors{ 0x20 // reserve space for return balance (0x20 bytes) ) if eq(success, 0) { - // @TODO Revert with `Error("BALANCE_CHECK_FAILED")` + // @TODO Revert with `Error("BALANCE_QUERY_FAILED")` + mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) + mstore(0x20, 0x0000002000000000000000000000000000000000000000000000000000000000) + mstore(0x40, 0x0000001442414c414e43455f51554552595f4641494c45440000000000000000) + mstore(0x60, 0x00000000) + // Revert length calculation: + // 4 -- error selector + // 32 -- offset to string + // 32 -- string length field + // 32 -- strlen(BALANCE_QUERY_FAILED) rounded up to nearest 32-byte word. revert(0, 100) } // Revert if balance not held let addressBalance := mload(freeMemPtr) if eq(addressBalance, 0) { - // Revert with `Error("AT_LEAST_ONE_ADDRESS_HAS_ZERO_BALANCE")` - mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) + // Revert with `Error("AT_LEAST_ONE_ADDRESS_DOES_NOT_MEET_BALANCE_THRESHOLD")` + mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) mstore(0x20, 0x0000002000000000000000000000000000000000000000000000000000000000) - mstore(0x40, 0x0000002541545f4c454153545f4f4e455f414444524553535f4841535f5a4552) - mstore(0x60, 0x4f5f42414c414e43450000000000000000000000000000000000000000000000) - revert(0, 109) + mstore(0x40, 0x0000003441545f4c454153545f4f4e455f414444524553535f444f45535f4e4f) + mstore(0x60, 0x545f4d4545545f42414c414e43455f5448524553484f4c440000000000000000) + mstore(0x80, 0x00000000) + // Revert length calculation: + // 4 -- error selector + // 32 -- offset to string + // 32 -- string length field + // 64 -- strlen(AT_LEAST_ONE_ADDRESS_DOES_NOT_MEET_BALANCE_THRESHOLD) rounded up to nearest 32-byte word. + revert(0, 132) } } diff --git a/packages/contracts/test/extensions/compliant_forwarder.ts b/packages/contracts/test/extensions/compliant_forwarder.ts index 8fa811936..196167264 100644 --- a/packages/contracts/test/extensions/compliant_forwarder.ts +++ b/packages/contracts/test/extensions/compliant_forwarder.ts @@ -303,7 +303,7 @@ describe.only(ContractName.CompliantForwarder, () => { compliantSignedFillOrderTx.data, compliantSignedFillOrderTx.signature, ), - RevertReason.AtLeastOneAddressHasZeroBalance + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold ); }); it('should revert if maker address is not compliant (does not hold a Yes Token)', async () => { @@ -331,7 +331,7 @@ describe.only(ContractName.CompliantForwarder, () => { signedFillOrderTx.data, signedFillOrderTx.signature, ), - RevertReason.AtLeastOneAddressHasZeroBalance + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold ); }); }); diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 022b24e70..4d5b6e1f2 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -243,9 +243,10 @@ export enum RevertReason { AuctionNotStarted = 'AUCTION_NOT_STARTED', AuctionInvalidBeginTime = 'INVALID_BEGIN_TIME', InvalidAssetData = 'INVALID_ASSET_DATA', - MakerUnverified = 'MAKER_UNVERIFED', - TakerUnverified = 'TAKER_UNVERIFIED', - AtLeastOneAddressHasZeroBalance = 'AT_LEAST_ONE_ADDRESS_HAS_ZERO_BALANCE', + // Balance Threshold Filter + InvalidOrBlockedExchangeSelector = 'INVALID_OR_BLOCKED_EXCHANGE_SELECTOR', + BalanceQueryFailed = 'BALANCE_QUERY_FAILED', + AtLeastOneAddressDoesNotMeetBalanceThreshold= 'AT_LEAST_ONE_ADDRESS_DOES_NOT_MEET_BALANCE_THRESHOLD', } export enum StatusCodes { -- cgit From 1cdd82178ff630827095e778a222fafa4161969e Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 4 Dec 2018 15:23:25 -0800 Subject: ComplianceForwarder renamed to BalanceThresholdFilter --- .../BalanceThresholdFilter.sol | 323 ++++++++++++++++++ .../interfaces/IThresholdAsset.sol | 30 ++ .../CompliantForwarder/CompliantForwarder.sol | 323 ------------------ .../test/extensions/balance_threshold_filter.ts | 366 +++++++++++++++++++++ .../test/extensions/compliant_forwarder.ts | 366 --------------------- 5 files changed, 719 insertions(+), 689 deletions(-) create mode 100644 packages/contracts/contracts/extensions/BalanceThresholdFilter/BalanceThresholdFilter.sol create mode 100644 packages/contracts/contracts/extensions/BalanceThresholdFilter/interfaces/IThresholdAsset.sol delete mode 100644 packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol create mode 100644 packages/contracts/test/extensions/balance_threshold_filter.ts delete mode 100644 packages/contracts/test/extensions/compliant_forwarder.ts (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/BalanceThresholdFilter/BalanceThresholdFilter.sol b/packages/contracts/contracts/extensions/BalanceThresholdFilter/BalanceThresholdFilter.sol new file mode 100644 index 000000000..93b63eb52 --- /dev/null +++ b/packages/contracts/contracts/extensions/BalanceThresholdFilter/BalanceThresholdFilter.sol @@ -0,0 +1,323 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; +pragma experimental ABIEncoderV2; + +import "../../protocol/Exchange/interfaces/IExchange.sol"; +import "../../utils/LibBytes/LibBytes.sol"; +import "../../utils/ExchangeSelectors/ExchangeSelectors.sol"; +import "./interfaces/IThresholdAsset.sol"; + +contract BalanceThresholdFilter is ExchangeSelectors { + + using LibBytes for bytes; + + IExchange internal EXCHANGE; + IThresholdAsset internal THRESHOLD_ASSET; + + event ValidatedAddresses ( + address[] addresses + ); + + constructor(address exchange, address thresholdAsset) + public + { + EXCHANGE = IExchange(exchange); + THRESHOLD_ASSET = IThresholdAsset(thresholdAsset); + } + + function executeTransaction( + uint256 salt, + address signerAddress, + bytes signedExchangeTransaction, + bytes signature + ) + external + { + // Addresses that are validated below. + address[] memory validatedAddresses; + + /** + * Do not add variables after this point. + * The assembly block may overwrite their values. + */ + + // Validate addresses + assembly { + /** + * Emulates the `calldataload` opcode on the embedded Exchange calldata, + * which is accessed through `signedExchangeTransaction`. + * @param offset - Offset into the Exchange calldata. + * @return value - Corresponding 32 byte value stored at `offset`. + */ + function exchangeCalldataload(offset) -> value { + // Pointer to exchange transaction + // 0x04 for calldata selector + // 0x40 to access `signedExchangeTransaction`, which is the third parameter + let exchangeTxPtr := calldataload(0x44) + + // Offset into Exchange calldata + // We compute this by adding 0x24 to the `exchangeTxPtr` computed above. + // 0x04 for calldata selector + // 0x20 for length field of `signedExchangeTransaction` + let exchangeCalldataOffset := add(exchangeTxPtr, add(0x24, offset)) + value := calldataload(exchangeCalldataOffset) + } + + /** + * Convenience function that skips the 4 byte selector when loading + * from the embedded Exchange calldata. + * @param offset - Offset into the Exchange calldata (minus the 4 byte selector) + * @return value - Corresponding 32 byte value stored at `offset` + 4. + */ + function loadExchangeData(offset) -> value { + value := exchangeCalldataload(add(offset, 0x4)) + } + + /** + * A running list is maintained of addresses to validate. + * This function records an address in this array. + * @param addressToValidate - Address to record for validation. + * @note - Variables are scoped but names are not, so we append + * underscores to names that share the global namespace. + */ + function recordAddressToValidate(addressToValidate) { + // Compute `addressesToValidate` memory offset + let addressesToValidate_ := mload(0x40) + let nAddressesToValidate_ := mload(addressesToValidate_) + + // Increment length + nAddressesToValidate_ := add(mload(addressesToValidate_), 0x01) + mstore(addressesToValidate_, nAddressesToValidate_) + + // Append address to validate + let offset := mul(nAddressesToValidate_, 0x20) + mstore(add(addressesToValidate_, offset), addressToValidate) + } + + /** + * Extracts the maker address from an order stored in the Exchange calldata + * (which is embedded in `signedExchangeTransaction`), and records it in + * the running list of addresses to validate. + * @param orderParamIndex - Index of the order in the Exchange function's signature + */ + function recordMakerAddressFromOrder(orderParamIndex) { + let orderPtr := loadExchangeData(orderParamIndex) + let makerAddress := loadExchangeData(orderPtr) + recordAddressToValidate(makerAddress) + } + + /** + * Extracts the maker addresses from an array of orders stored in the Exchange calldata + * (which is embedded in `signedExchangeTransaction`), and records them in + * the running list of addresses to validate. + * @param orderArrayParamIndex - Index of the order array in the Exchange function's signature + */ + function recordMakerAddressesFromOrderArray(orderArrayParamIndex) { + let orderArrayPtr := loadExchangeData(0x0) + let orderArrayLength := loadExchangeData(orderArrayPtr) + let orderArrayElementPtr := add(orderArrayPtr, 0x20) + let orderArrayElementEndPtr := add(orderArrayElementPtr, mul(orderArrayLength, 0x20)) + for {let orderPtrOffset := orderArrayElementPtr} lt(orderPtrOffset, orderArrayElementEndPtr) {orderPtrOffset := add(orderPtrOffset, 0x20)} { + let orderPtr := loadExchangeData(orderPtrOffset) + let makerAddress := loadExchangeData(add(orderPtr, orderArrayElementPtr)) + recordAddressToValidate(makerAddress) + } + } + + /** + * Records address of signer in the running list of addresses to validate. + * @note: We cannot access `signerAddress` directly from within the asm function, + * so it is loaded from the calldata. + */ + function recordSignerAddress() { + // Load the signer address from calldata + // 0x04 for selector + // 0x20 to access `signerAddress`, which is the second parameter. + let signerAddress_ := calldataload(0x24) + recordAddressToValidate(signerAddress_) + } + + /** + * Records addresses to be validated when Exchange transaction is a batch fill variant. + * This is one of: batchFillOrders, batchFillOrKillOrders, batchFillNoThrow + * Reference signature: (Order[],uint256[],bytes[]) + */ + function recordAddressesForBatchFillVariant() { + // Record maker addresses from order array (parameter index 0) + // The signer is the taker for these orders and must also be validated. + recordMakerAddressesFromOrderArray(0) + recordSignerAddress() + } + + /** + * Records addresses to be validated when Exchange transaction is a fill order variant. + * This is one of: fillOrder, fillOrKillOrder, fillOrderNoThrow + * Reference signature: (Order,uint256,bytes) + */ + function recordAddressesForFillOrderVariant() { + // Record maker address from the order (param index 0) + // The signer is the taker for this order and must also be validated. + recordMakerAddressFromOrder(0) + recordSignerAddress() + } + + /** + * Records addresses to be validated when Exchange transaction is a market fill variant. + * This is one of: marketBuyOrders, marketBuyOrdersNoThrow, marketSellOrders, marketSellOrdersNoThrow + * Reference signature: (Order[],uint256,bytes[]) + */ + function recordAddressesForMarketFillVariant() { + // Record maker addresses from order array (parameter index 0) + // The signer is the taker for these orders and must also be validated. + recordMakerAddressesFromOrderArray(0) + recordSignerAddress() + } + + /** + * Records addresses to be validated when Exchange transaction is matchOrders. + * Reference signature: matchOrders(Order,Order) + */ + function recordAddressesForMatchOrders() { + // Record maker address from both orders (param indices 0 & 1). + // The signer is the taker and must also be validated. + recordMakerAddressFromOrder(0) + recordMakerAddressFromOrder(1) + recordSignerAddress() + } + + ///// Record Addresses to Validate ///// + + // Addresses needing validation depends on which Exchange function is being called. + // Step 1/2 Read the exchange function selector. + let exchangeFunctionSelector := and( + exchangeCalldataload(0x0), + 0xffffffff00000000000000000000000000000000000000000000000000000000 + ) + + // Step 2/2 Extract addresses to validate based on this selector. + // See ../../utils/ExchangeSelectors/ExchangeSelectors.sol for selectors + switch exchangeFunctionSelector + case 0x297bb70b00000000000000000000000000000000000000000000000000000000 { recordAddressesForBatchFillVariant() } // batchFillOrders + case 0x50dde19000000000000000000000000000000000000000000000000000000000 { recordAddressesForBatchFillVariant() } // batchFillOrdersNoThrow + case 0x4d0ae54600000000000000000000000000000000000000000000000000000000 { recordAddressesForBatchFillVariant() } // batchFillOrKillOrders + case 0xb4be83d500000000000000000000000000000000000000000000000000000000 { recordAddressesForFillOrderVariant() } // fillOrder + case 0x3e228bae00000000000000000000000000000000000000000000000000000000 { recordAddressesForFillOrderVariant() } // fillOrderNoThrow + case 0x64a3bc1500000000000000000000000000000000000000000000000000000000 { recordAddressesForFillOrderVariant() } // fillOrKillOrder + case 0xe5fa431b00000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketBuyOrders + case 0xa3e2038000000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketBuyOrdersNoThrow + case 0x7e1d980800000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketSellOrders + case 0xdd1c7d1800000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketSellOrdersNoThrow + case 0x3c28d86100000000000000000000000000000000000000000000000000000000 { recordAddressesForMatchOrders() } // matchOrders + case 0xd46b02c300000000000000000000000000000000000000000000000000000000 {} // cancelOrder + case 0x4ac1478200000000000000000000000000000000000000000000000000000000 {} // batchCancelOrders + case 0x4f9559b100000000000000000000000000000000000000000000000000000000 {} // cancelOrdersUpTo + default { + // Revert with `Error("INVALID_OR_BLOCKED_EXCHANGE_SELECTOR")` + mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) + mstore(0x20, 0x0000002000000000000000000000000000000000000000000000000000000000) + mstore(0x40, 0x00000024494e56414c49445f4f525f424c4f434b45445f45584348414e47455f) + mstore(0x60, 0x53454c4543544f52000000000000000000000000000000000000000000000000) + mstore(0x80, 0x00000000) + // Revert length calculation: + // 4 -- error selector + // 32 -- offset to string + // 32 -- string length field + // 64 -- strlen(INVALID_OR_BLOCKED_EXCHANGE_SELECTOR) rounded up to nearest 32-byte word. + revert(0, 132) + } + + ///// Validate Recorded Addresses ///// + + // Load from memory the addresses to validate + let addressesToValidate := mload(0x40) + let addressesToValidateLength := mload(addressesToValidate) + let addressesToValidateElementPtr := add(addressesToValidate, 0x20) + let addressesToValidateElementEndPtr := add(addressesToValidateElementPtr, mul(addressesToValidateLength, 0x20)) + + // Set free memory pointer to after `addressesToValidate` array. + // This is to avoid corruption when making calls in the loop below. + let freeMemPtr := addressesToValidateElementEndPtr + mstore(0x40, freeMemPtr) + + // Validate addresses + let thresholdAssetAddress := sload(THRESHOLD_ASSET_slot) + for {let addressToValidate := addressesToValidateElementPtr} lt(addressToValidate, addressesToValidateElementEndPtr) {addressToValidate := add(addressToValidate, 0x20)} { + // Construct calldata for `THRESHOLD_ASSET.balanceOf` + mstore(freeMemPtr, 0x70a0823100000000000000000000000000000000000000000000000000000000) + mstore(add(4, freeMemPtr), mload(addressToValidate)) + + // call `THRESHOLD_ASSET.balanceOf` + let success := call( + gas, // forward all gas + thresholdAssetAddress, // call address of asset proxy + 0, // don't send any ETH + freeMemPtr, // pointer to start of input + 0x24, // length of input (one padded address) + freeMemPtr, // write output to next free memory offset + 0x20 // reserve space for return balance (0x20 bytes) + ) + if eq(success, 0) { + // @TODO Revert with `Error("BALANCE_QUERY_FAILED")` + mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) + mstore(0x20, 0x0000002000000000000000000000000000000000000000000000000000000000) + mstore(0x40, 0x0000001442414c414e43455f51554552595f4641494c45440000000000000000) + mstore(0x60, 0x00000000) + // Revert length calculation: + // 4 -- error selector + // 32 -- offset to string + // 32 -- string length field + // 32 -- strlen(BALANCE_QUERY_FAILED) rounded up to nearest 32-byte word. + revert(0, 100) + } + + // Revert if balance not held + let addressBalance := mload(freeMemPtr) + if eq(addressBalance, 0) { + // Revert with `Error("AT_LEAST_ONE_ADDRESS_DOES_NOT_MEET_BALANCE_THRESHOLD")` + mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) + mstore(0x20, 0x0000002000000000000000000000000000000000000000000000000000000000) + mstore(0x40, 0x0000003441545f4c454153545f4f4e455f414444524553535f444f45535f4e4f) + mstore(0x60, 0x545f4d4545545f42414c414e43455f5448524553484f4c440000000000000000) + mstore(0x80, 0x00000000) + // Revert length calculation: + // 4 -- error selector + // 32 -- offset to string + // 32 -- string length field + // 64 -- strlen(AT_LEAST_ONE_ADDRESS_DOES_NOT_MEET_BALANCE_THRESHOLD) rounded up to nearest 32-byte word. + revert(0, 132) + } + } + + // Record validated addresses + validatedAddresses := addressesToValidate + } + + ///// If we hit this point then all addresses are valid ///// + emit ValidatedAddresses(validatedAddresses); + + // All addresses are valid. Execute fillOrder. + EXCHANGE.executeTransaction( + salt, + signerAddress, + signedExchangeTransaction, + signature + ); + } +} \ No newline at end of file diff --git a/packages/contracts/contracts/extensions/BalanceThresholdFilter/interfaces/IThresholdAsset.sol b/packages/contracts/contracts/extensions/BalanceThresholdFilter/interfaces/IThresholdAsset.sol new file mode 100644 index 000000000..61acaba0a --- /dev/null +++ b/packages/contracts/contracts/extensions/BalanceThresholdFilter/interfaces/IThresholdAsset.sol @@ -0,0 +1,30 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; + +contract IThresholdAsset { + + /// @param _owner The address from which the balance will be retrieved + /// @return Balance of owner + function balanceOf(address _owner) + external + view + returns (uint256); + +} \ No newline at end of file diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol deleted file mode 100644 index d33f4f398..000000000 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ /dev/null @@ -1,323 +0,0 @@ -/* - - Copyright 2018 ZeroEx Intl. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -*/ - -pragma solidity 0.4.24; -pragma experimental ABIEncoderV2; - -import "../../protocol/Exchange/interfaces/IExchange.sol"; -import "../../tokens/ERC721Token/IERC721Token.sol"; -import "../../utils/LibBytes/LibBytes.sol"; -import "../../utils/ExchangeSelectors/ExchangeSelectors.sol"; - -contract CompliantForwarder is ExchangeSelectors{ - - using LibBytes for bytes; - - IExchange internal EXCHANGE; - IERC721Token internal COMPLIANCE_TOKEN; - - event ValidatedAddresses ( - address[] addresses - ); - - constructor(address exchange, address complianceToken) - public - { - EXCHANGE = IExchange(exchange); - COMPLIANCE_TOKEN = IERC721Token(complianceToken); - } - - function executeTransaction( - uint256 salt, - address signerAddress, - bytes signedExchangeTransaction, - bytes signature - ) - external - { - // Addresses that are validated below. - address[] memory validatedAddresses; - - /** - * Do not add variables after this point. - * The assembly block may overwrite their values. - */ - - // Validate addresses - assembly { - /** - * Emulates the `calldataload` opcode on the embedded Exchange calldata, - * which is accessed through `signedExchangeTransaction`. - * @param offset - Offset into the Exchange calldata. - * @return value - Corresponding 32 byte value stored at `offset`. - */ - function exchangeCalldataload(offset) -> value { - // Pointer to exchange transaction - // 0x04 for calldata selector - // 0x40 to access `signedExchangeTransaction`, which is the third parameter - let exchangeTxPtr := calldataload(0x44) - - // Offset into Exchange calldata - // We compute this by adding 0x24 to the `exchangeTxPtr` computed above. - // 0x04 for calldata selector - // 0x20 for length field of `signedExchangeTransaction` - let exchangeCalldataOffset := add(exchangeTxPtr, add(0x24, offset)) - value := calldataload(exchangeCalldataOffset) - } - - /** - * Convenience function that skips the 4 byte selector when loading - * from the embedded Exchange calldata. - * @param offset - Offset into the Exchange calldata (minus the 4 byte selector) - * @return value - Corresponding 32 byte value stored at `offset` + 4. - */ - function loadExchangeData(offset) -> value { - value := exchangeCalldataload(add(offset, 0x4)) - } - - /** - * A running list is maintained of addresses to validate. - * This function records an address in this array. - * @param addressToValidate - Address to record for validation. - * @note - Variables are scoped but names are not, so we append - * underscores to names that share the global namespace. - */ - function recordAddressToValidate(addressToValidate) { - // Compute `addressesToValidate` memory offset - let addressesToValidate_ := mload(0x40) - let nAddressesToValidate_ := mload(addressesToValidate_) - - // Increment length - nAddressesToValidate_ := add(mload(addressesToValidate_), 0x01) - mstore(addressesToValidate_, nAddressesToValidate_) - - // Append address to validate - let offset := mul(nAddressesToValidate_, 0x20) - mstore(add(addressesToValidate_, offset), addressToValidate) - } - - /** - * Extracts the maker address from an order stored in the Exchange calldata - * (which is embedded in `signedExchangeTransaction`), and records it in - * the running list of addresses to validate. - * @param orderParamIndex - Index of the order in the Exchange function's signature - */ - function recordMakerAddressFromOrder(orderParamIndex) { - let orderPtr := loadExchangeData(orderParamIndex) - let makerAddress := loadExchangeData(orderPtr) - recordAddressToValidate(makerAddress) - } - - /** - * Extracts the maker addresses from an array of orders stored in the Exchange calldata - * (which is embedded in `signedExchangeTransaction`), and records them in - * the running list of addresses to validate. - * @param orderArrayParamIndex - Index of the order array in the Exchange function's signature - */ - function recordMakerAddressesFromOrderArray(orderArrayParamIndex) { - let orderArrayPtr := loadExchangeData(0x0) - let orderArrayLength := loadExchangeData(orderArrayPtr) - let orderArrayElementPtr := add(orderArrayPtr, 0x20) - let orderArrayElementEndPtr := add(orderArrayElementPtr, mul(orderArrayLength, 0x20)) - for {let orderPtrOffset := orderArrayElementPtr} lt(orderPtrOffset, orderArrayElementEndPtr) {orderPtrOffset := add(orderPtrOffset, 0x20)} { - let orderPtr := loadExchangeData(orderPtrOffset) - let makerAddress := loadExchangeData(add(orderPtr, orderArrayElementPtr)) - recordAddressToValidate(makerAddress) - } - } - - /** - * Records address of signer in the running list of addresses to validate. - * @note: We cannot access `signerAddress` directly from within the asm function, - * so it is loaded from the calldata. - */ - function recordSignerAddress() { - // Load the signer address from calldata - // 0x04 for selector - // 0x20 to access `signerAddress`, which is the second parameter. - let signerAddress_ := calldataload(0x24) - recordAddressToValidate(signerAddress_) - } - - /** - * Records addresses to be validated when Exchange transaction is a batch fill variant. - * This is one of: batchFillOrders, batchFillOrKillOrders, batchFillNoThrow - * Reference signature: (Order[],uint256[],bytes[]) - */ - function recordAddressesForBatchFillVariant() { - // Record maker addresses from order array (parameter index 0) - // The signer is the taker for these orders and must also be validated. - recordMakerAddressesFromOrderArray(0) - recordSignerAddress() - } - - /** - * Records addresses to be validated when Exchange transaction is a fill order variant. - * This is one of: fillOrder, fillOrKillOrder, fillOrderNoThrow - * Reference signature: (Order,uint256,bytes) - */ - function recordAddressesForFillOrderVariant() { - // Record maker address from the order (param index 0) - // The signer is the taker for this order and must also be validated. - recordMakerAddressFromOrder(0) - recordSignerAddress() - } - - /** - * Records addresses to be validated when Exchange transaction is a market fill variant. - * This is one of: marketBuyOrders, marketBuyOrdersNoThrow, marketSellOrders, marketSellOrdersNoThrow - * Reference signature: (Order[],uint256,bytes[]) - */ - function recordAddressesForMarketFillVariant() { - // Record maker addresses from order array (parameter index 0) - // The signer is the taker for these orders and must also be validated. - recordMakerAddressesFromOrderArray(0) - recordSignerAddress() - } - - /** - * Records addresses to be validated when Exchange transaction is matchOrders. - * Reference signature: matchOrders(Order,Order) - */ - function recordAddressesForMatchOrders() { - // Record maker address from both orders (param indices 0 & 1). - // The signer is the taker and must also be validated. - recordMakerAddressFromOrder(0) - recordMakerAddressFromOrder(1) - recordSignerAddress() - } - - ///// Record Addresses to Validate ///// - - // Addresses needing validation depends on which Exchange function is being called. - // Step 1/2 Read the exchange function selector. - let exchangeFunctionSelector := and( - exchangeCalldataload(0x0), - 0xffffffff00000000000000000000000000000000000000000000000000000000 - ) - - // Step 2/2 Extract addresses to validate based on this selector. - // See ../../utils/ExchangeSelectors/ExchangeSelectors.sol for selectors - switch exchangeFunctionSelector - case 0x297bb70b00000000000000000000000000000000000000000000000000000000 { recordAddressesForBatchFillVariant() } // batchFillOrders - case 0x50dde19000000000000000000000000000000000000000000000000000000000 { recordAddressesForBatchFillVariant() } // batchFillOrdersNoThrow - case 0x4d0ae54600000000000000000000000000000000000000000000000000000000 { recordAddressesForBatchFillVariant() } // batchFillOrKillOrders - case 0xb4be83d500000000000000000000000000000000000000000000000000000000 { recordAddressesForFillOrderVariant() } // fillOrder - case 0x3e228bae00000000000000000000000000000000000000000000000000000000 { recordAddressesForFillOrderVariant() } // fillOrderNoThrow - case 0x64a3bc1500000000000000000000000000000000000000000000000000000000 { recordAddressesForFillOrderVariant() } // fillOrKillOrder - case 0xe5fa431b00000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketBuyOrders - case 0xa3e2038000000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketBuyOrdersNoThrow - case 0x7e1d980800000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketSellOrders - case 0xdd1c7d1800000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketSellOrdersNoThrow - case 0x3c28d86100000000000000000000000000000000000000000000000000000000 { recordAddressesForMatchOrders() } // matchOrders - case 0xd46b02c300000000000000000000000000000000000000000000000000000000 {} // cancelOrder - case 0x4ac1478200000000000000000000000000000000000000000000000000000000 {} // batchCancelOrders - case 0x4f9559b100000000000000000000000000000000000000000000000000000000 {} // cancelOrdersUpTo - default { - // Revert with `Error("INVALID_OR_BLOCKED_EXCHANGE_SELECTOR")` - mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) - mstore(0x20, 0x0000002000000000000000000000000000000000000000000000000000000000) - mstore(0x40, 0x00000024494e56414c49445f4f525f424c4f434b45445f45584348414e47455f) - mstore(0x60, 0x53454c4543544f52000000000000000000000000000000000000000000000000) - mstore(0x80, 0x00000000) - // Revert length calculation: - // 4 -- error selector - // 32 -- offset to string - // 32 -- string length field - // 64 -- strlen(INVALID_OR_BLOCKED_EXCHANGE_SELECTOR) rounded up to nearest 32-byte word. - revert(0, 132) - } - - ///// Validate Recorded Addresses ///// - - // Load from memory the addresses to validate - let addressesToValidate := mload(0x40) - let addressesToValidateLength := mload(addressesToValidate) - let addressesToValidateElementPtr := add(addressesToValidate, 0x20) - let addressesToValidateElementEndPtr := add(addressesToValidateElementPtr, mul(addressesToValidateLength, 0x20)) - - // Set free memory pointer to after `addressesToValidate` array. - // This is to avoid corruption when making calls in the loop below. - let freeMemPtr := addressesToValidateElementEndPtr - mstore(0x40, freeMemPtr) - - // Validate addresses - let complianceTokenAddress := sload(COMPLIANCE_TOKEN_slot) - for {let addressToValidate := addressesToValidateElementPtr} lt(addressToValidate, addressesToValidateElementEndPtr) {addressToValidate := add(addressToValidate, 0x20)} { - // Construct calldata for `COMPLIANCE_TOKEN.balanceOf` - mstore(freeMemPtr, 0x70a0823100000000000000000000000000000000000000000000000000000000) - mstore(add(4, freeMemPtr), mload(addressToValidate)) - - // call `COMPLIANCE_TOKEN.balanceOf` - let success := call( - gas, // forward all gas - complianceTokenAddress, // call address of asset proxy - 0, // don't send any ETH - freeMemPtr, // pointer to start of input - 0x24, // length of input (one padded address) - freeMemPtr, // write output to next free memory offset - 0x20 // reserve space for return balance (0x20 bytes) - ) - if eq(success, 0) { - // @TODO Revert with `Error("BALANCE_QUERY_FAILED")` - mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) - mstore(0x20, 0x0000002000000000000000000000000000000000000000000000000000000000) - mstore(0x40, 0x0000001442414c414e43455f51554552595f4641494c45440000000000000000) - mstore(0x60, 0x00000000) - // Revert length calculation: - // 4 -- error selector - // 32 -- offset to string - // 32 -- string length field - // 32 -- strlen(BALANCE_QUERY_FAILED) rounded up to nearest 32-byte word. - revert(0, 100) - } - - // Revert if balance not held - let addressBalance := mload(freeMemPtr) - if eq(addressBalance, 0) { - // Revert with `Error("AT_LEAST_ONE_ADDRESS_DOES_NOT_MEET_BALANCE_THRESHOLD")` - mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) - mstore(0x20, 0x0000002000000000000000000000000000000000000000000000000000000000) - mstore(0x40, 0x0000003441545f4c454153545f4f4e455f414444524553535f444f45535f4e4f) - mstore(0x60, 0x545f4d4545545f42414c414e43455f5448524553484f4c440000000000000000) - mstore(0x80, 0x00000000) - // Revert length calculation: - // 4 -- error selector - // 32 -- offset to string - // 32 -- string length field - // 64 -- strlen(AT_LEAST_ONE_ADDRESS_DOES_NOT_MEET_BALANCE_THRESHOLD) rounded up to nearest 32-byte word. - revert(0, 132) - } - } - - // Record validated addresses - validatedAddresses := addressesToValidate - } - - ///// If we hit this point then all addresses are valid ///// - emit ValidatedAddresses(validatedAddresses); - - // All addresses are valid. Execute fillOrder. - EXCHANGE.executeTransaction( - salt, - signerAddress, - signedExchangeTransaction, - signature - ); - } -} \ No newline at end of file diff --git a/packages/contracts/test/extensions/balance_threshold_filter.ts b/packages/contracts/test/extensions/balance_threshold_filter.ts new file mode 100644 index 000000000..50fd79439 --- /dev/null +++ b/packages/contracts/test/extensions/balance_threshold_filter.ts @@ -0,0 +1,366 @@ +import { BlockchainLifecycle } from '@0x/dev-utils'; +import { assetDataUtils } from '@0x/order-utils'; +import { RevertReason, SignedOrder } from '@0x/types'; +import { BigNumber } from '@0x/utils'; +import { Web3Wrapper } from '@0x/web3-wrapper'; +import * as chai from 'chai'; +import * as ethUtil from 'ethereumjs-util'; +import * as _ from 'lodash'; + +import { DummyERC20TokenContract } from '../../generated-wrappers/dummy_erc20_token'; +import { ExchangeContract } from '../../generated-wrappers/exchange'; +import { BalanceThresholdFilterContract } from '../../generated-wrappers/balance_threshold_filter'; +import { YesComplianceTokenContract } from '../../generated-wrappers/yes_compliance_token'; + +import { artifacts } from '../../src/artifacts'; +import { + expectTransactionFailedAsync, + expectTransactionFailedWithoutReasonAsync, +} from '../utils/assertions'; +import { chaiSetup } from '../utils/chai_setup'; +import { constants } from '../utils/constants'; +import { ERC20Wrapper } from '../utils/erc20_wrapper'; +import { ExchangeWrapper } from '../utils/exchange_wrapper'; +import { OrderFactory } from '../utils/order_factory'; +import { orderUtils } from '../utils/order_utils'; +import { TransactionFactory } from '../utils/transaction_factory'; +import { ContractName, ERC20BalancesByOwner, SignedTransaction } from '../utils/types'; +import { provider, txDefaults, web3Wrapper } from '../utils/web3_wrapper'; + +import { MethodAbi, AbiDefinition } from 'ethereum-types'; +import { AbiEncoder } from '@0x/utils'; +import { Method } from '@0x/utils/lib/src/abi_encoder'; +import { LogDecoder } from '../utils/log_decoder'; + +chaiSetup.configure(); +const expect = chai.expect; +const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); +const DECIMALS_DEFAULT = 18; + +describe.only(ContractName.BalanceThresholdFilter, () => { + let compliantMakerAddress: string; + let owner: string; + let compliantTakerAddress: string; + let feeRecipientAddress: string; + let nonCompliantAddress: string; + let defaultMakerAssetAddress: string; + let defaultTakerAssetAddress: string; + let zrxAssetData: string; + let zrxToken: DummyERC20TokenContract; + let exchangeInstance: ExchangeContract; + let exchangeWrapper: ExchangeWrapper; + + let orderFactory: OrderFactory; + let erc20Wrapper: ERC20Wrapper; + let erc20Balances: ERC20BalancesByOwner; + + let takerTransactionFactory: TransactionFactory; + let compliantSignedOrder: SignedOrder; + let compliantSignedFillOrderTx: SignedTransaction; + let noncompliantSignedFillOrderTx: SignedTransaction; + + const takerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(500), DECIMALS_DEFAULT); + const makerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(1000), DECIMALS_DEFAULT); + const takerAssetFillAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(250), DECIMALS_DEFAULT); + + let compliantForwarderInstance: BalanceThresholdFilterContract; + + before(async () => { + // Create accounts + await blockchainLifecycle.startAsync(); + const accounts = await web3Wrapper.getAvailableAddressesAsync(); + const usedAddresses = ([ + owner, + compliantMakerAddress, + compliantTakerAddress, + feeRecipientAddress, + nonCompliantAddress, + ] = accounts); + // Create wrappers + erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner); + // Deploy ERC20 tokens + const numDummyErc20ToDeploy = 3; + let erc20TokenA: DummyERC20TokenContract; + let erc20TokenB: DummyERC20TokenContract; + [erc20TokenA, erc20TokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync( + numDummyErc20ToDeploy, + constants.DUMMY_TOKEN_DECIMALS, + ); + defaultMakerAssetAddress = erc20TokenA.address; + defaultTakerAssetAddress = erc20TokenB.address; + zrxAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address); + // Deploy Yes Token + const yesTokenInstance = await YesComplianceTokenContract.deployFrom0xArtifactAsync( + artifacts.YesComplianceToken, + provider, + txDefaults, + ); + // Create proxies + const erc20Proxy = await erc20Wrapper.deployProxyAsync(); + await erc20Wrapper.setBalancesAndAllowancesAsync(); + // Deploy Exchange congtract + exchangeInstance = await ExchangeContract.deployFrom0xArtifactAsync( + artifacts.Exchange, + provider, + txDefaults, + zrxAssetData, + ); + exchangeWrapper = new ExchangeWrapper(exchangeInstance, provider); + // Register proxies + await exchangeWrapper.registerAssetProxyAsync(erc20Proxy.address, owner); + await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(exchangeInstance.address, { + from: owner, + }); + // Default order parameters + const defaultOrderParams = { + exchangeAddress: exchangeInstance.address, + makerAddress: compliantMakerAddress, + feeRecipientAddress, + makerAssetData: assetDataUtils.encodeERC20AssetData(defaultMakerAssetAddress), + takerAssetData: assetDataUtils.encodeERC20AssetData(defaultTakerAssetAddress), + makerAssetAmount, + takerAssetAmount, + makerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), DECIMALS_DEFAULT), + takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(150), DECIMALS_DEFAULT), + }; + const privateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantMakerAddress)]; + orderFactory = new OrderFactory(privateKey, defaultOrderParams); + // Deploy Compliant Forwarder + compliantForwarderInstance = await BalanceThresholdFilterContract.deployFrom0xArtifactAsync( + artifacts.BalanceThresholdFilter, + provider, + txDefaults, + exchangeInstance.address, + yesTokenInstance.address, + ); + /* + const compliantForwarderContract = new BalanceThresholdFilterContract( + compliantForwarderInstance.abi, + compliantForwarderInstance.address, + provider, + ); + forwarderWrapper = new ForwarderWrapper(compliantForwarderContract, provider); + */ + // Initialize Yes Token + await yesTokenInstance._upgradeable_initialize.sendTransactionAsync({ from: owner }); + const yesTokenName = 'YesToken'; + const yesTokenTicker = 'YEET'; + await yesTokenInstance.initialize.sendTransactionAsync(yesTokenName, yesTokenTicker, { from: owner }); + // Verify Maker / Taker + const addressesCanControlTheirToken = true; + const compliantMakerCountryCode = new BigNumber(519); + const compliantMakerYesMark = new BigNumber(1); + const compliantMakerEntityId = new BigNumber(2); + await yesTokenInstance.mint2.sendTransactionAsync( + compliantMakerAddress, + compliantMakerEntityId, + addressesCanControlTheirToken, + compliantMakerCountryCode, + [compliantMakerYesMark], + { from: owner }, + ); + const compliantTakerCountryCode = new BigNumber(519); + const compliantTakerYesMark = new BigNumber(1); + const compliantTakerEntityId = new BigNumber(2); + await yesTokenInstance.mint2.sendTransactionAsync( + compliantTakerAddress, + compliantTakerEntityId, + addressesCanControlTheirToken, + compliantTakerCountryCode, + [compliantTakerYesMark], + { from: owner }, + ); + // Create Valid/Invalid orders + const takerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantTakerAddress)]; + takerTransactionFactory = new TransactionFactory(takerPrivateKey, exchangeInstance.address); + compliantSignedOrder = await orderFactory.newSignedOrderAsync({ + senderAddress: compliantForwarderInstance.address, + }); + const compliantSignedOrderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress( + compliantSignedOrder, + ); + const compliantSignedOrderWithoutExchangeAddressData = exchangeInstance.fillOrder.getABIEncodedTransactionData( + compliantSignedOrderWithoutExchangeAddress, + takerAssetFillAmount, + compliantSignedOrder.signature, + ); + compliantSignedFillOrderTx = takerTransactionFactory.newSignedTransaction( + compliantSignedOrderWithoutExchangeAddressData, + ); + + /* generate selectors for every exchange method + _.each(exchangeInstance.abi, (abiDefinition: AbiDefinition) => { + try { + const method = new Method(abiDefinition as MethodAbi); + console.log('\n', `// ${method.getDataItem().name}`); + console.log(`bytes4 constant ${method.getDataItem().name}Selector = ${method.getSelector()};`); + console.log(`bytes4 constant ${method.getDataItem().name}SelectorGenerator = byes4(keccak256('${method.getSignature()}'));`); + } catch(e) { + _.noop(); + } + });*/ + }); + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + }); + describe.only('fillOrder', () => { + beforeEach(async () => { + erc20Balances = await erc20Wrapper.getBalancesAsync(); + }); + it('should transfer the correct amounts when maker and taker are compliant', async () => { + const txHash = await compliantForwarderInstance.executeTransaction.sendTransactionAsync( + compliantSignedFillOrderTx.salt, + compliantSignedFillOrderTx.signerAddress, + compliantSignedFillOrderTx.data, + compliantSignedFillOrderTx.signature, + ); + const decoder = new LogDecoder(web3Wrapper); + const tx = await decoder.getTxWithDecodedLogsAsync(txHash); + console.log(JSON.stringify(tx, null, 4)); + console.log('****** MAKER ADDRESS = ', compliantSignedOrder.makerAddress); + const newBalances = await erc20Wrapper.getBalancesAsync(); + const makerAssetFillAmount = takerAssetFillAmount + .times(compliantSignedOrder.makerAssetAmount) + .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); + const makerFeePaid = compliantSignedOrder.makerFee + .times(makerAssetFillAmount) + .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); + const takerFeePaid = compliantSignedOrder.takerFee + .times(makerAssetFillAmount) + .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); + expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), + ); + expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount), + ); + expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount), + ); + expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), + ); + expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)), + ); + }); + it('should revert if the signed transaction is not intended for fillOrder', async () => { + // Create signed order without the fillOrder function selector + const txDataBuf = ethUtil.toBuffer(compliantSignedFillOrderTx.data); + const selectorLengthInBytes = 4; + const txDataBufMinusSelector = txDataBuf.slice(selectorLengthInBytes); + const badSelector = '0x00000000'; + const badSelectorBuf = ethUtil.toBuffer(badSelector); + const txDataBufWithBadSelector = Buffer.concat([badSelectorBuf, txDataBufMinusSelector]); + const txDataBufWithBadSelectorHex = ethUtil.bufferToHex(txDataBufWithBadSelector); + // Call compliant forwarder + return expectTransactionFailedWithoutReasonAsync(compliantForwarderInstance.executeTransaction.sendTransactionAsync( + compliantSignedFillOrderTx.salt, + compliantSignedFillOrderTx.signerAddress, + txDataBufWithBadSelectorHex, + compliantSignedFillOrderTx.signature, + )); + }); + it('should revert if senderAddress is not set to the compliant forwarding contract', async () => { + // Create signed order with incorrect senderAddress + const notBalanceThresholdFilterAddress = zrxToken.address; + const signedOrderWithBadSenderAddress = await orderFactory.newSignedOrderAsync({ + senderAddress: notBalanceThresholdFilterAddress, + }); + const signedOrderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress( + signedOrderWithBadSenderAddress, + ); + const signedOrderWithoutExchangeAddressData = exchangeInstance.fillOrder.getABIEncodedTransactionData( + signedOrderWithoutExchangeAddress, + takerAssetFillAmount, + compliantSignedOrder.signature, + ); + const signedFillOrderTx = takerTransactionFactory.newSignedTransaction( + signedOrderWithoutExchangeAddressData, + ); + // Call compliant forwarder + return expectTransactionFailedWithoutReasonAsync(compliantForwarderInstance.executeTransaction.sendTransactionAsync( + signedFillOrderTx.salt, + signedFillOrderTx.signerAddress, + signedFillOrderTx.data, + signedFillOrderTx.signature, + )); + }); + it('should revert if taker address is not compliant (does not hold a Yes Token)', async () => { + return expectTransactionFailedAsync( + compliantForwarderInstance.executeTransaction.sendTransactionAsync( + compliantSignedFillOrderTx.salt, + nonCompliantAddress, + compliantSignedFillOrderTx.data, + compliantSignedFillOrderTx.signature, + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); + }); + it('should revert if maker address is not compliant (does not hold a Yes Token)', async () => { + // Create signed order with non-compliant maker address + const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ + senderAddress: compliantForwarderInstance.address, + makerAddress: nonCompliantAddress + }); + const signedOrderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress( + signedOrderWithBadMakerAddress, + ); + const signedOrderWithoutExchangeAddressData = exchangeInstance.fillOrder.getABIEncodedTransactionData( + signedOrderWithoutExchangeAddress, + takerAssetFillAmount, + compliantSignedOrder.signature, + ); + const signedFillOrderTx = takerTransactionFactory.newSignedTransaction( + signedOrderWithoutExchangeAddressData, + ); + // Call compliant forwarder + return expectTransactionFailedAsync( + compliantForwarderInstance.executeTransaction.sendTransactionAsync( + signedFillOrderTx.salt, + signedFillOrderTx.signerAddress, + signedFillOrderTx.data, + signedFillOrderTx.signature, + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); + }); + }); + + describe('batchFillOrders', () => { + beforeEach(async () => { + erc20Balances = await erc20Wrapper.getBalancesAsync(); + }); + it('should transfer the correct amounts when maker and taker are compliant', async () => { + let order2 = _.cloneDeep(compliantSignedOrder); + order2.makerAddress = `0x${_.reverse(compliantSignedOrder.makerAddress.slice(2).split('')).join('')}`; + const orders = [compliantSignedOrder, order2]; + const fillAmounts = [new BigNumber(4), new BigNumber(4)]; + const signatures = ["0xabcd", "0xabcd"]; + const exchangeCalldata = exchangeInstance.batchFillOrders.getABIEncodedTransactionData(orders, fillAmounts, signatures); + console.log('*'.repeat(40), exchangeCalldata, '*'.repeat(40)); + console.log('****** MAKER ADDRESS = ', compliantSignedOrder.makerAddress); + + const txHash = await compliantForwarderInstance.executeTransaction.sendTransactionAsync( + compliantSignedFillOrderTx.salt, + compliantSignedFillOrderTx.signerAddress, + exchangeCalldata, + compliantSignedFillOrderTx.signature, + ); + const decoder = new LogDecoder(web3Wrapper); + const tx = await decoder.getTxWithDecodedLogsAsync(txHash); + console.log(JSON.stringify(tx, null, 4)); + }); + }); +}); +// tslint:disable:max-file-line-count +// tslint:enable:no-unnecessary-type-assertion diff --git a/packages/contracts/test/extensions/compliant_forwarder.ts b/packages/contracts/test/extensions/compliant_forwarder.ts deleted file mode 100644 index 196167264..000000000 --- a/packages/contracts/test/extensions/compliant_forwarder.ts +++ /dev/null @@ -1,366 +0,0 @@ -import { BlockchainLifecycle } from '@0x/dev-utils'; -import { assetDataUtils } from '@0x/order-utils'; -import { RevertReason, SignedOrder } from '@0x/types'; -import { BigNumber } from '@0x/utils'; -import { Web3Wrapper } from '@0x/web3-wrapper'; -import * as chai from 'chai'; -import * as ethUtil from 'ethereumjs-util'; -import * as _ from 'lodash'; - -import { DummyERC20TokenContract } from '../../generated-wrappers/dummy_erc20_token'; -import { ExchangeContract } from '../../generated-wrappers/exchange'; -import { CompliantForwarderContract } from '../../generated-wrappers/compliant_forwarder'; -import { YesComplianceTokenContract } from '../../generated-wrappers/yes_compliance_token'; - -import { artifacts } from '../../src/artifacts'; -import { - expectTransactionFailedAsync, - expectTransactionFailedWithoutReasonAsync, -} from '../utils/assertions'; -import { chaiSetup } from '../utils/chai_setup'; -import { constants } from '../utils/constants'; -import { ERC20Wrapper } from '../utils/erc20_wrapper'; -import { ExchangeWrapper } from '../utils/exchange_wrapper'; -import { OrderFactory } from '../utils/order_factory'; -import { orderUtils } from '../utils/order_utils'; -import { TransactionFactory } from '../utils/transaction_factory'; -import { ContractName, ERC20BalancesByOwner, SignedTransaction } from '../utils/types'; -import { provider, txDefaults, web3Wrapper } from '../utils/web3_wrapper'; - -import { MethodAbi, AbiDefinition } from 'ethereum-types'; -import { AbiEncoder } from '@0x/utils'; -import { Method } from '@0x/utils/lib/src/abi_encoder'; -import { LogDecoder } from '../utils/log_decoder'; - -chaiSetup.configure(); -const expect = chai.expect; -const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); -const DECIMALS_DEFAULT = 18; - -describe.only(ContractName.CompliantForwarder, () => { - let compliantMakerAddress: string; - let owner: string; - let compliantTakerAddress: string; - let feeRecipientAddress: string; - let nonCompliantAddress: string; - let defaultMakerAssetAddress: string; - let defaultTakerAssetAddress: string; - let zrxAssetData: string; - let zrxToken: DummyERC20TokenContract; - let exchangeInstance: ExchangeContract; - let exchangeWrapper: ExchangeWrapper; - - let orderFactory: OrderFactory; - let erc20Wrapper: ERC20Wrapper; - let erc20Balances: ERC20BalancesByOwner; - - let takerTransactionFactory: TransactionFactory; - let compliantSignedOrder: SignedOrder; - let compliantSignedFillOrderTx: SignedTransaction; - let noncompliantSignedFillOrderTx: SignedTransaction; - - const takerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(500), DECIMALS_DEFAULT); - const makerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(1000), DECIMALS_DEFAULT); - const takerAssetFillAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(250), DECIMALS_DEFAULT); - - let compliantForwarderInstance: CompliantForwarderContract; - - before(async () => { - // Create accounts - await blockchainLifecycle.startAsync(); - const accounts = await web3Wrapper.getAvailableAddressesAsync(); - const usedAddresses = ([ - owner, - compliantMakerAddress, - compliantTakerAddress, - feeRecipientAddress, - nonCompliantAddress, - ] = accounts); - // Create wrappers - erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner); - // Deploy ERC20 tokens - const numDummyErc20ToDeploy = 3; - let erc20TokenA: DummyERC20TokenContract; - let erc20TokenB: DummyERC20TokenContract; - [erc20TokenA, erc20TokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync( - numDummyErc20ToDeploy, - constants.DUMMY_TOKEN_DECIMALS, - ); - defaultMakerAssetAddress = erc20TokenA.address; - defaultTakerAssetAddress = erc20TokenB.address; - zrxAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address); - // Deploy Yes Token - const yesTokenInstance = await YesComplianceTokenContract.deployFrom0xArtifactAsync( - artifacts.YesComplianceToken, - provider, - txDefaults, - ); - // Create proxies - const erc20Proxy = await erc20Wrapper.deployProxyAsync(); - await erc20Wrapper.setBalancesAndAllowancesAsync(); - // Deploy Exchange congtract - exchangeInstance = await ExchangeContract.deployFrom0xArtifactAsync( - artifacts.Exchange, - provider, - txDefaults, - zrxAssetData, - ); - exchangeWrapper = new ExchangeWrapper(exchangeInstance, provider); - // Register proxies - await exchangeWrapper.registerAssetProxyAsync(erc20Proxy.address, owner); - await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(exchangeInstance.address, { - from: owner, - }); - // Default order parameters - const defaultOrderParams = { - exchangeAddress: exchangeInstance.address, - makerAddress: compliantMakerAddress, - feeRecipientAddress, - makerAssetData: assetDataUtils.encodeERC20AssetData(defaultMakerAssetAddress), - takerAssetData: assetDataUtils.encodeERC20AssetData(defaultTakerAssetAddress), - makerAssetAmount, - takerAssetAmount, - makerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), DECIMALS_DEFAULT), - takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(150), DECIMALS_DEFAULT), - }; - const privateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantMakerAddress)]; - orderFactory = new OrderFactory(privateKey, defaultOrderParams); - // Deploy Compliant Forwarder - compliantForwarderInstance = await CompliantForwarderContract.deployFrom0xArtifactAsync( - artifacts.CompliantForwarder, - provider, - txDefaults, - exchangeInstance.address, - yesTokenInstance.address, - ); - /* - const compliantForwarderContract = new CompliantForwarderContract( - compliantForwarderInstance.abi, - compliantForwarderInstance.address, - provider, - ); - forwarderWrapper = new ForwarderWrapper(compliantForwarderContract, provider); - */ - // Initialize Yes Token - await yesTokenInstance._upgradeable_initialize.sendTransactionAsync({ from: owner }); - const yesTokenName = 'YesToken'; - const yesTokenTicker = 'YEET'; - await yesTokenInstance.initialize.sendTransactionAsync(yesTokenName, yesTokenTicker, { from: owner }); - // Verify Maker / Taker - const addressesCanControlTheirToken = true; - const compliantMakerCountryCode = new BigNumber(519); - const compliantMakerYesMark = new BigNumber(1); - const compliantMakerEntityId = new BigNumber(2); - await yesTokenInstance.mint2.sendTransactionAsync( - compliantMakerAddress, - compliantMakerEntityId, - addressesCanControlTheirToken, - compliantMakerCountryCode, - [compliantMakerYesMark], - { from: owner }, - ); - const compliantTakerCountryCode = new BigNumber(519); - const compliantTakerYesMark = new BigNumber(1); - const compliantTakerEntityId = new BigNumber(2); - await yesTokenInstance.mint2.sendTransactionAsync( - compliantTakerAddress, - compliantTakerEntityId, - addressesCanControlTheirToken, - compliantTakerCountryCode, - [compliantTakerYesMark], - { from: owner }, - ); - // Create Valid/Invalid orders - const takerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantTakerAddress)]; - takerTransactionFactory = new TransactionFactory(takerPrivateKey, exchangeInstance.address); - compliantSignedOrder = await orderFactory.newSignedOrderAsync({ - senderAddress: compliantForwarderInstance.address, - }); - const compliantSignedOrderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress( - compliantSignedOrder, - ); - const compliantSignedOrderWithoutExchangeAddressData = exchangeInstance.fillOrder.getABIEncodedTransactionData( - compliantSignedOrderWithoutExchangeAddress, - takerAssetFillAmount, - compliantSignedOrder.signature, - ); - compliantSignedFillOrderTx = takerTransactionFactory.newSignedTransaction( - compliantSignedOrderWithoutExchangeAddressData, - ); - - /* generate selectors for every exchange method - _.each(exchangeInstance.abi, (abiDefinition: AbiDefinition) => { - try { - const method = new Method(abiDefinition as MethodAbi); - console.log('\n', `// ${method.getDataItem().name}`); - console.log(`bytes4 constant ${method.getDataItem().name}Selector = ${method.getSelector()};`); - console.log(`bytes4 constant ${method.getDataItem().name}SelectorGenerator = byes4(keccak256('${method.getSignature()}'));`); - } catch(e) { - _.noop(); - } - });*/ - }); - beforeEach(async () => { - await blockchainLifecycle.startAsync(); - }); - afterEach(async () => { - await blockchainLifecycle.revertAsync(); - }); - describe.only('fillOrder', () => { - beforeEach(async () => { - erc20Balances = await erc20Wrapper.getBalancesAsync(); - }); - it('should transfer the correct amounts when maker and taker are compliant', async () => { - const txHash = await compliantForwarderInstance.executeTransaction.sendTransactionAsync( - compliantSignedFillOrderTx.salt, - compliantSignedFillOrderTx.signerAddress, - compliantSignedFillOrderTx.data, - compliantSignedFillOrderTx.signature, - ); - const decoder = new LogDecoder(web3Wrapper); - const tx = await decoder.getTxWithDecodedLogsAsync(txHash); - console.log(JSON.stringify(tx, null, 4)); - console.log('****** MAKER ADDRESS = ', compliantSignedOrder.makerAddress); - const newBalances = await erc20Wrapper.getBalancesAsync(); - const makerAssetFillAmount = takerAssetFillAmount - .times(compliantSignedOrder.makerAssetAmount) - .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); - const makerFeePaid = compliantSignedOrder.makerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); - const takerFeePaid = compliantSignedOrder.takerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); - expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), - ); - expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount), - ); - expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount), - ); - expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), - ); - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)), - ); - }); - it('should revert if the signed transaction is not intended for fillOrder', async () => { - // Create signed order without the fillOrder function selector - const txDataBuf = ethUtil.toBuffer(compliantSignedFillOrderTx.data); - const selectorLengthInBytes = 4; - const txDataBufMinusSelector = txDataBuf.slice(selectorLengthInBytes); - const badSelector = '0x00000000'; - const badSelectorBuf = ethUtil.toBuffer(badSelector); - const txDataBufWithBadSelector = Buffer.concat([badSelectorBuf, txDataBufMinusSelector]); - const txDataBufWithBadSelectorHex = ethUtil.bufferToHex(txDataBufWithBadSelector); - // Call compliant forwarder - return expectTransactionFailedWithoutReasonAsync(compliantForwarderInstance.executeTransaction.sendTransactionAsync( - compliantSignedFillOrderTx.salt, - compliantSignedFillOrderTx.signerAddress, - txDataBufWithBadSelectorHex, - compliantSignedFillOrderTx.signature, - )); - }); - it('should revert if senderAddress is not set to the compliant forwarding contract', async () => { - // Create signed order with incorrect senderAddress - const notCompliantForwarderAddress = zrxToken.address; - const signedOrderWithBadSenderAddress = await orderFactory.newSignedOrderAsync({ - senderAddress: notCompliantForwarderAddress, - }); - const signedOrderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress( - signedOrderWithBadSenderAddress, - ); - const signedOrderWithoutExchangeAddressData = exchangeInstance.fillOrder.getABIEncodedTransactionData( - signedOrderWithoutExchangeAddress, - takerAssetFillAmount, - compliantSignedOrder.signature, - ); - const signedFillOrderTx = takerTransactionFactory.newSignedTransaction( - signedOrderWithoutExchangeAddressData, - ); - // Call compliant forwarder - return expectTransactionFailedWithoutReasonAsync(compliantForwarderInstance.executeTransaction.sendTransactionAsync( - signedFillOrderTx.salt, - signedFillOrderTx.signerAddress, - signedFillOrderTx.data, - signedFillOrderTx.signature, - )); - }); - it('should revert if taker address is not compliant (does not hold a Yes Token)', async () => { - return expectTransactionFailedAsync( - compliantForwarderInstance.executeTransaction.sendTransactionAsync( - compliantSignedFillOrderTx.salt, - nonCompliantAddress, - compliantSignedFillOrderTx.data, - compliantSignedFillOrderTx.signature, - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - it('should revert if maker address is not compliant (does not hold a Yes Token)', async () => { - // Create signed order with non-compliant maker address - const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - senderAddress: compliantForwarderInstance.address, - makerAddress: nonCompliantAddress - }); - const signedOrderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress( - signedOrderWithBadMakerAddress, - ); - const signedOrderWithoutExchangeAddressData = exchangeInstance.fillOrder.getABIEncodedTransactionData( - signedOrderWithoutExchangeAddress, - takerAssetFillAmount, - compliantSignedOrder.signature, - ); - const signedFillOrderTx = takerTransactionFactory.newSignedTransaction( - signedOrderWithoutExchangeAddressData, - ); - // Call compliant forwarder - return expectTransactionFailedAsync( - compliantForwarderInstance.executeTransaction.sendTransactionAsync( - signedFillOrderTx.salt, - signedFillOrderTx.signerAddress, - signedFillOrderTx.data, - signedFillOrderTx.signature, - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - }); - - describe('batchFillOrders', () => { - beforeEach(async () => { - erc20Balances = await erc20Wrapper.getBalancesAsync(); - }); - it('should transfer the correct amounts when maker and taker are compliant', async () => { - let order2 = _.cloneDeep(compliantSignedOrder); - order2.makerAddress = `0x${_.reverse(compliantSignedOrder.makerAddress.slice(2).split('')).join('')}`; - const orders = [compliantSignedOrder, order2]; - const fillAmounts = [new BigNumber(4), new BigNumber(4)]; - const signatures = ["0xabcd", "0xabcd"]; - const exchangeCalldata = exchangeInstance.batchFillOrders.getABIEncodedTransactionData(orders, fillAmounts, signatures); - console.log('*'.repeat(40), exchangeCalldata, '*'.repeat(40)); - console.log('****** MAKER ADDRESS = ', compliantSignedOrder.makerAddress); - - const txHash = await compliantForwarderInstance.executeTransaction.sendTransactionAsync( - compliantSignedFillOrderTx.salt, - compliantSignedFillOrderTx.signerAddress, - exchangeCalldata, - compliantSignedFillOrderTx.signature, - ); - const decoder = new LogDecoder(web3Wrapper); - const tx = await decoder.getTxWithDecodedLogsAsync(txHash); - console.log(JSON.stringify(tx, null, 4)); - }); - }); -}); -// tslint:disable:max-file-line-count -// tslint:enable:no-unnecessary-type-assertion -- cgit From 2be9b1ff082ff2753798cc0c218ab1829949661f Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 4 Dec 2018 15:47:45 -0800 Subject: Updated Balance Threshold Filter to use mixin pattern --- .../BalanceThresholdFilter.sol | 292 +------------------ .../MixinBalanceThresholdFilterCore.sol | 312 +++++++++++++++++++++ .../mixins/MBalanceThresholdFilterCore.sol | 44 +++ 3 files changed, 358 insertions(+), 290 deletions(-) create mode 100644 packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol create mode 100644 packages/contracts/contracts/extensions/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/BalanceThresholdFilter/BalanceThresholdFilter.sol b/packages/contracts/contracts/extensions/BalanceThresholdFilter/BalanceThresholdFilter.sol index 93b63eb52..ce3e925fe 100644 --- a/packages/contracts/contracts/extensions/BalanceThresholdFilter/BalanceThresholdFilter.sol +++ b/packages/contracts/contracts/extensions/BalanceThresholdFilter/BalanceThresholdFilter.sol @@ -20,20 +20,11 @@ pragma solidity 0.4.24; pragma experimental ABIEncoderV2; import "../../protocol/Exchange/interfaces/IExchange.sol"; -import "../../utils/LibBytes/LibBytes.sol"; -import "../../utils/ExchangeSelectors/ExchangeSelectors.sol"; import "./interfaces/IThresholdAsset.sol"; +import "./MixinBalanceThresholdFilterCore.sol"; -contract BalanceThresholdFilter is ExchangeSelectors { - using LibBytes for bytes; - - IExchange internal EXCHANGE; - IThresholdAsset internal THRESHOLD_ASSET; - - event ValidatedAddresses ( - address[] addresses - ); +contract BalanceThresholdFilter is MixinBalanceThresholdFilterCore { constructor(address exchange, address thresholdAsset) public @@ -41,283 +32,4 @@ contract BalanceThresholdFilter is ExchangeSelectors { EXCHANGE = IExchange(exchange); THRESHOLD_ASSET = IThresholdAsset(thresholdAsset); } - - function executeTransaction( - uint256 salt, - address signerAddress, - bytes signedExchangeTransaction, - bytes signature - ) - external - { - // Addresses that are validated below. - address[] memory validatedAddresses; - - /** - * Do not add variables after this point. - * The assembly block may overwrite their values. - */ - - // Validate addresses - assembly { - /** - * Emulates the `calldataload` opcode on the embedded Exchange calldata, - * which is accessed through `signedExchangeTransaction`. - * @param offset - Offset into the Exchange calldata. - * @return value - Corresponding 32 byte value stored at `offset`. - */ - function exchangeCalldataload(offset) -> value { - // Pointer to exchange transaction - // 0x04 for calldata selector - // 0x40 to access `signedExchangeTransaction`, which is the third parameter - let exchangeTxPtr := calldataload(0x44) - - // Offset into Exchange calldata - // We compute this by adding 0x24 to the `exchangeTxPtr` computed above. - // 0x04 for calldata selector - // 0x20 for length field of `signedExchangeTransaction` - let exchangeCalldataOffset := add(exchangeTxPtr, add(0x24, offset)) - value := calldataload(exchangeCalldataOffset) - } - - /** - * Convenience function that skips the 4 byte selector when loading - * from the embedded Exchange calldata. - * @param offset - Offset into the Exchange calldata (minus the 4 byte selector) - * @return value - Corresponding 32 byte value stored at `offset` + 4. - */ - function loadExchangeData(offset) -> value { - value := exchangeCalldataload(add(offset, 0x4)) - } - - /** - * A running list is maintained of addresses to validate. - * This function records an address in this array. - * @param addressToValidate - Address to record for validation. - * @note - Variables are scoped but names are not, so we append - * underscores to names that share the global namespace. - */ - function recordAddressToValidate(addressToValidate) { - // Compute `addressesToValidate` memory offset - let addressesToValidate_ := mload(0x40) - let nAddressesToValidate_ := mload(addressesToValidate_) - - // Increment length - nAddressesToValidate_ := add(mload(addressesToValidate_), 0x01) - mstore(addressesToValidate_, nAddressesToValidate_) - - // Append address to validate - let offset := mul(nAddressesToValidate_, 0x20) - mstore(add(addressesToValidate_, offset), addressToValidate) - } - - /** - * Extracts the maker address from an order stored in the Exchange calldata - * (which is embedded in `signedExchangeTransaction`), and records it in - * the running list of addresses to validate. - * @param orderParamIndex - Index of the order in the Exchange function's signature - */ - function recordMakerAddressFromOrder(orderParamIndex) { - let orderPtr := loadExchangeData(orderParamIndex) - let makerAddress := loadExchangeData(orderPtr) - recordAddressToValidate(makerAddress) - } - - /** - * Extracts the maker addresses from an array of orders stored in the Exchange calldata - * (which is embedded in `signedExchangeTransaction`), and records them in - * the running list of addresses to validate. - * @param orderArrayParamIndex - Index of the order array in the Exchange function's signature - */ - function recordMakerAddressesFromOrderArray(orderArrayParamIndex) { - let orderArrayPtr := loadExchangeData(0x0) - let orderArrayLength := loadExchangeData(orderArrayPtr) - let orderArrayElementPtr := add(orderArrayPtr, 0x20) - let orderArrayElementEndPtr := add(orderArrayElementPtr, mul(orderArrayLength, 0x20)) - for {let orderPtrOffset := orderArrayElementPtr} lt(orderPtrOffset, orderArrayElementEndPtr) {orderPtrOffset := add(orderPtrOffset, 0x20)} { - let orderPtr := loadExchangeData(orderPtrOffset) - let makerAddress := loadExchangeData(add(orderPtr, orderArrayElementPtr)) - recordAddressToValidate(makerAddress) - } - } - - /** - * Records address of signer in the running list of addresses to validate. - * @note: We cannot access `signerAddress` directly from within the asm function, - * so it is loaded from the calldata. - */ - function recordSignerAddress() { - // Load the signer address from calldata - // 0x04 for selector - // 0x20 to access `signerAddress`, which is the second parameter. - let signerAddress_ := calldataload(0x24) - recordAddressToValidate(signerAddress_) - } - - /** - * Records addresses to be validated when Exchange transaction is a batch fill variant. - * This is one of: batchFillOrders, batchFillOrKillOrders, batchFillNoThrow - * Reference signature: (Order[],uint256[],bytes[]) - */ - function recordAddressesForBatchFillVariant() { - // Record maker addresses from order array (parameter index 0) - // The signer is the taker for these orders and must also be validated. - recordMakerAddressesFromOrderArray(0) - recordSignerAddress() - } - - /** - * Records addresses to be validated when Exchange transaction is a fill order variant. - * This is one of: fillOrder, fillOrKillOrder, fillOrderNoThrow - * Reference signature: (Order,uint256,bytes) - */ - function recordAddressesForFillOrderVariant() { - // Record maker address from the order (param index 0) - // The signer is the taker for this order and must also be validated. - recordMakerAddressFromOrder(0) - recordSignerAddress() - } - - /** - * Records addresses to be validated when Exchange transaction is a market fill variant. - * This is one of: marketBuyOrders, marketBuyOrdersNoThrow, marketSellOrders, marketSellOrdersNoThrow - * Reference signature: (Order[],uint256,bytes[]) - */ - function recordAddressesForMarketFillVariant() { - // Record maker addresses from order array (parameter index 0) - // The signer is the taker for these orders and must also be validated. - recordMakerAddressesFromOrderArray(0) - recordSignerAddress() - } - - /** - * Records addresses to be validated when Exchange transaction is matchOrders. - * Reference signature: matchOrders(Order,Order) - */ - function recordAddressesForMatchOrders() { - // Record maker address from both orders (param indices 0 & 1). - // The signer is the taker and must also be validated. - recordMakerAddressFromOrder(0) - recordMakerAddressFromOrder(1) - recordSignerAddress() - } - - ///// Record Addresses to Validate ///// - - // Addresses needing validation depends on which Exchange function is being called. - // Step 1/2 Read the exchange function selector. - let exchangeFunctionSelector := and( - exchangeCalldataload(0x0), - 0xffffffff00000000000000000000000000000000000000000000000000000000 - ) - - // Step 2/2 Extract addresses to validate based on this selector. - // See ../../utils/ExchangeSelectors/ExchangeSelectors.sol for selectors - switch exchangeFunctionSelector - case 0x297bb70b00000000000000000000000000000000000000000000000000000000 { recordAddressesForBatchFillVariant() } // batchFillOrders - case 0x50dde19000000000000000000000000000000000000000000000000000000000 { recordAddressesForBatchFillVariant() } // batchFillOrdersNoThrow - case 0x4d0ae54600000000000000000000000000000000000000000000000000000000 { recordAddressesForBatchFillVariant() } // batchFillOrKillOrders - case 0xb4be83d500000000000000000000000000000000000000000000000000000000 { recordAddressesForFillOrderVariant() } // fillOrder - case 0x3e228bae00000000000000000000000000000000000000000000000000000000 { recordAddressesForFillOrderVariant() } // fillOrderNoThrow - case 0x64a3bc1500000000000000000000000000000000000000000000000000000000 { recordAddressesForFillOrderVariant() } // fillOrKillOrder - case 0xe5fa431b00000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketBuyOrders - case 0xa3e2038000000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketBuyOrdersNoThrow - case 0x7e1d980800000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketSellOrders - case 0xdd1c7d1800000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketSellOrdersNoThrow - case 0x3c28d86100000000000000000000000000000000000000000000000000000000 { recordAddressesForMatchOrders() } // matchOrders - case 0xd46b02c300000000000000000000000000000000000000000000000000000000 {} // cancelOrder - case 0x4ac1478200000000000000000000000000000000000000000000000000000000 {} // batchCancelOrders - case 0x4f9559b100000000000000000000000000000000000000000000000000000000 {} // cancelOrdersUpTo - default { - // Revert with `Error("INVALID_OR_BLOCKED_EXCHANGE_SELECTOR")` - mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) - mstore(0x20, 0x0000002000000000000000000000000000000000000000000000000000000000) - mstore(0x40, 0x00000024494e56414c49445f4f525f424c4f434b45445f45584348414e47455f) - mstore(0x60, 0x53454c4543544f52000000000000000000000000000000000000000000000000) - mstore(0x80, 0x00000000) - // Revert length calculation: - // 4 -- error selector - // 32 -- offset to string - // 32 -- string length field - // 64 -- strlen(INVALID_OR_BLOCKED_EXCHANGE_SELECTOR) rounded up to nearest 32-byte word. - revert(0, 132) - } - - ///// Validate Recorded Addresses ///// - - // Load from memory the addresses to validate - let addressesToValidate := mload(0x40) - let addressesToValidateLength := mload(addressesToValidate) - let addressesToValidateElementPtr := add(addressesToValidate, 0x20) - let addressesToValidateElementEndPtr := add(addressesToValidateElementPtr, mul(addressesToValidateLength, 0x20)) - - // Set free memory pointer to after `addressesToValidate` array. - // This is to avoid corruption when making calls in the loop below. - let freeMemPtr := addressesToValidateElementEndPtr - mstore(0x40, freeMemPtr) - - // Validate addresses - let thresholdAssetAddress := sload(THRESHOLD_ASSET_slot) - for {let addressToValidate := addressesToValidateElementPtr} lt(addressToValidate, addressesToValidateElementEndPtr) {addressToValidate := add(addressToValidate, 0x20)} { - // Construct calldata for `THRESHOLD_ASSET.balanceOf` - mstore(freeMemPtr, 0x70a0823100000000000000000000000000000000000000000000000000000000) - mstore(add(4, freeMemPtr), mload(addressToValidate)) - - // call `THRESHOLD_ASSET.balanceOf` - let success := call( - gas, // forward all gas - thresholdAssetAddress, // call address of asset proxy - 0, // don't send any ETH - freeMemPtr, // pointer to start of input - 0x24, // length of input (one padded address) - freeMemPtr, // write output to next free memory offset - 0x20 // reserve space for return balance (0x20 bytes) - ) - if eq(success, 0) { - // @TODO Revert with `Error("BALANCE_QUERY_FAILED")` - mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) - mstore(0x20, 0x0000002000000000000000000000000000000000000000000000000000000000) - mstore(0x40, 0x0000001442414c414e43455f51554552595f4641494c45440000000000000000) - mstore(0x60, 0x00000000) - // Revert length calculation: - // 4 -- error selector - // 32 -- offset to string - // 32 -- string length field - // 32 -- strlen(BALANCE_QUERY_FAILED) rounded up to nearest 32-byte word. - revert(0, 100) - } - - // Revert if balance not held - let addressBalance := mload(freeMemPtr) - if eq(addressBalance, 0) { - // Revert with `Error("AT_LEAST_ONE_ADDRESS_DOES_NOT_MEET_BALANCE_THRESHOLD")` - mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) - mstore(0x20, 0x0000002000000000000000000000000000000000000000000000000000000000) - mstore(0x40, 0x0000003441545f4c454153545f4f4e455f414444524553535f444f45535f4e4f) - mstore(0x60, 0x545f4d4545545f42414c414e43455f5448524553484f4c440000000000000000) - mstore(0x80, 0x00000000) - // Revert length calculation: - // 4 -- error selector - // 32 -- offset to string - // 32 -- string length field - // 64 -- strlen(AT_LEAST_ONE_ADDRESS_DOES_NOT_MEET_BALANCE_THRESHOLD) rounded up to nearest 32-byte word. - revert(0, 132) - } - } - - // Record validated addresses - validatedAddresses := addressesToValidate - } - - ///// If we hit this point then all addresses are valid ///// - emit ValidatedAddresses(validatedAddresses); - - // All addresses are valid. Execute fillOrder. - EXCHANGE.executeTransaction( - salt, - signerAddress, - signedExchangeTransaction, - signature - ); - } } \ No newline at end of file diff --git a/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol b/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol new file mode 100644 index 000000000..dbb352f90 --- /dev/null +++ b/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol @@ -0,0 +1,312 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; +pragma experimental ABIEncoderV2; + +import "./mixins/MBalanceThresholdFilterCore.sol"; + + +contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { + + function executeTransaction( + uint256 salt, + address signerAddress, + bytes signedExchangeTransaction, + bytes signature + ) + external + { + // Validate addresses. + validateBalanceThresholdsOrRevert(); + + // All addresses are valid. Execute fillOrder. + EXCHANGE.executeTransaction( + salt, + signerAddress, + signedExchangeTransaction, + signature + ); + } + + function validateBalanceThresholdsOrRevert() + internal + { + // Addresses that are validated below. + address[] memory validatedAddresses; + + /** + * Do not add variables after this point. + * The assembly block may overwrite their values. + */ + + // Validate addresses + assembly { + /** + * Emulates the `calldataload` opcode on the embedded Exchange calldata, + * which is accessed through `signedExchangeTransaction`. + * @param offset - Offset into the Exchange calldata. + * @return value - Corresponding 32 byte value stored at `offset`. + */ + function exchangeCalldataload(offset) -> value { + // Pointer to exchange transaction + // 0x04 for calldata selector + // 0x40 to access `signedExchangeTransaction`, which is the third parameter + let exchangeTxPtr := calldataload(0x44) + + // Offset into Exchange calldata + // We compute this by adding 0x24 to the `exchangeTxPtr` computed above. + // 0x04 for calldata selector + // 0x20 for length field of `signedExchangeTransaction` + let exchangeCalldataOffset := add(exchangeTxPtr, add(0x24, offset)) + value := calldataload(exchangeCalldataOffset) + } + + /** + * Convenience function that skips the 4 byte selector when loading + * from the embedded Exchange calldata. + * @param offset - Offset into the Exchange calldata (minus the 4 byte selector) + * @return value - Corresponding 32 byte value stored at `offset` + 4. + */ + function loadExchangeData(offset) -> value { + value := exchangeCalldataload(add(offset, 0x4)) + } + + /** + * A running list is maintained of addresses to validate. + * This function records an address in this array. + * @param addressToValidate - Address to record for validation. + * @note - Variables are scoped but names are not, so we append + * underscores to names that share the global namespace. + */ + function recordAddressToValidate(addressToValidate) { + // Compute `addressesToValidate` memory offset + let addressesToValidate_ := mload(0x40) + let nAddressesToValidate_ := mload(addressesToValidate_) + + // Increment length + nAddressesToValidate_ := add(mload(addressesToValidate_), 0x01) + mstore(addressesToValidate_, nAddressesToValidate_) + + // Append address to validate + let offset := mul(nAddressesToValidate_, 0x20) + mstore(add(addressesToValidate_, offset), addressToValidate) + } + + /** + * Extracts the maker address from an order stored in the Exchange calldata + * (which is embedded in `signedExchangeTransaction`), and records it in + * the running list of addresses to validate. + * @param orderParamIndex - Index of the order in the Exchange function's signature + */ + function recordMakerAddressFromOrder(orderParamIndex) { + let orderPtr := loadExchangeData(orderParamIndex) + let makerAddress := loadExchangeData(orderPtr) + recordAddressToValidate(makerAddress) + } + + /** + * Extracts the maker addresses from an array of orders stored in the Exchange calldata + * (which is embedded in `signedExchangeTransaction`), and records them in + * the running list of addresses to validate. + * @param orderArrayParamIndex - Index of the order array in the Exchange function's signature + */ + function recordMakerAddressesFromOrderArray(orderArrayParamIndex) { + let orderArrayPtr := loadExchangeData(orderArrayParamIndex) + let orderArrayLength := loadExchangeData(orderArrayPtr) + let orderArrayElementPtr := add(orderArrayPtr, 0x20) + let orderArrayElementEndPtr := add(orderArrayElementPtr, mul(orderArrayLength, 0x20)) + for {let orderPtrOffset := orderArrayElementPtr} lt(orderPtrOffset, orderArrayElementEndPtr) {orderPtrOffset := add(orderPtrOffset, 0x20)} { + let orderPtr := loadExchangeData(orderPtrOffset) + let makerAddress := loadExchangeData(add(orderPtr, orderArrayElementPtr)) + recordAddressToValidate(makerAddress) + } + } + + /** + * Records address of signer in the running list of addresses to validate. + * @note: We cannot access `signerAddress` directly from within the asm function, + * so it is loaded from the calldata. + */ + function recordSignerAddress() { + // Load the signer address from calldata + // 0x04 for selector + // 0x20 to access `signerAddress`, which is the second parameter. + let signerAddress_ := calldataload(0x24) + recordAddressToValidate(signerAddress_) + } + + /** + * Records addresses to be validated when Exchange transaction is a batch fill variant. + * This is one of: batchFillOrders, batchFillOrKillOrders, batchFillNoThrow + * Reference signature: (Order[],uint256[],bytes[]) + */ + function recordAddressesForBatchFillVariant() { + // Record maker addresses from order array (parameter index 0) + // The signer is the taker for these orders and must also be validated. + recordMakerAddressesFromOrderArray(0) + recordSignerAddress() + } + + /** + * Records addresses to be validated when Exchange transaction is a fill order variant. + * This is one of: fillOrder, fillOrKillOrder, fillOrderNoThrow + * Reference signature: (Order,uint256,bytes) + */ + function recordAddressesForFillOrderVariant() { + // Record maker address from the order (param index 0) + // The signer is the taker for this order and must also be validated. + recordMakerAddressFromOrder(0) + recordSignerAddress() + } + + /** + * Records addresses to be validated when Exchange transaction is a market fill variant. + * This is one of: marketBuyOrders, marketBuyOrdersNoThrow, marketSellOrders, marketSellOrdersNoThrow + * Reference signature: (Order[],uint256,bytes[]) + */ + function recordAddressesForMarketFillVariant() { + // Record maker addresses from order array (parameter index 0) + // The signer is the taker for these orders and must also be validated. + recordMakerAddressesFromOrderArray(0) + recordSignerAddress() + } + + /** + * Records addresses to be validated when Exchange transaction is matchOrders. + * Reference signature: matchOrders(Order,Order) + */ + function recordAddressesForMatchOrders() { + // Record maker address from both orders (param indices 0 & 1). + // The signer is the taker and must also be validated. + recordMakerAddressFromOrder(0) + recordMakerAddressFromOrder(1) + recordSignerAddress() + } + + ///// Record Addresses to Validate ///// + + // Addresses needing validation depends on which Exchange function is being called. + // Step 1/2 Read the exchange function selector. + let exchangeFunctionSelector := and( + exchangeCalldataload(0x0), + 0xffffffff00000000000000000000000000000000000000000000000000000000 + ) + + // Step 2/2 Extract addresses to validate based on this selector. + // See ../../utils/ExchangeSelectors/ExchangeSelectors.sol for selectors + switch exchangeFunctionSelector + case 0x297bb70b00000000000000000000000000000000000000000000000000000000 { recordAddressesForBatchFillVariant() } // batchFillOrders + case 0x50dde19000000000000000000000000000000000000000000000000000000000 { recordAddressesForBatchFillVariant() } // batchFillOrdersNoThrow + case 0x4d0ae54600000000000000000000000000000000000000000000000000000000 { recordAddressesForBatchFillVariant() } // batchFillOrKillOrders + case 0xb4be83d500000000000000000000000000000000000000000000000000000000 { recordAddressesForFillOrderVariant() } // fillOrder + case 0x3e228bae00000000000000000000000000000000000000000000000000000000 { recordAddressesForFillOrderVariant() } // fillOrderNoThrow + case 0x64a3bc1500000000000000000000000000000000000000000000000000000000 { recordAddressesForFillOrderVariant() } // fillOrKillOrder + case 0xe5fa431b00000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketBuyOrders + case 0xa3e2038000000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketBuyOrdersNoThrow + case 0x7e1d980800000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketSellOrders + case 0xdd1c7d1800000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketSellOrdersNoThrow + case 0x3c28d86100000000000000000000000000000000000000000000000000000000 { recordAddressesForMatchOrders() } // matchOrders + case 0xd46b02c300000000000000000000000000000000000000000000000000000000 {} // cancelOrder + case 0x4ac1478200000000000000000000000000000000000000000000000000000000 {} // batchCancelOrders + case 0x4f9559b100000000000000000000000000000000000000000000000000000000 {} // cancelOrdersUpTo + default { + // Revert with `Error("INVALID_OR_BLOCKED_EXCHANGE_SELECTOR")` + mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) + mstore(0x20, 0x0000002000000000000000000000000000000000000000000000000000000000) + mstore(0x40, 0x00000024494e56414c49445f4f525f424c4f434b45445f45584348414e47455f) + mstore(0x60, 0x53454c4543544f52000000000000000000000000000000000000000000000000) + mstore(0x80, 0x00000000) + // Revert length calculation: + // 4 -- error selector + // 32 -- offset to string + // 32 -- string length field + // 64 -- strlen(INVALID_OR_BLOCKED_EXCHANGE_SELECTOR) rounded up to nearest 32-byte word. + revert(0, 132) + } + + ///// Validate Recorded Addresses ///// + + // Load from memory the addresses to validate + let addressesToValidate := mload(0x40) + let addressesToValidateLength := mload(addressesToValidate) + let addressesToValidateElementPtr := add(addressesToValidate, 0x20) + let addressesToValidateElementEndPtr := add(addressesToValidateElementPtr, mul(addressesToValidateLength, 0x20)) + + // Set free memory pointer to after `addressesToValidate` array. + // This is to avoid corruption when making calls in the loop below. + let freeMemPtr := addressesToValidateElementEndPtr + mstore(0x40, freeMemPtr) + + // Validate addresses + let thresholdAssetAddress := sload(THRESHOLD_ASSET_slot) + for {let addressToValidate := addressesToValidateElementPtr} lt(addressToValidate, addressesToValidateElementEndPtr) {addressToValidate := add(addressToValidate, 0x20)} { + // Construct calldata for `THRESHOLD_ASSET.balanceOf` + mstore(freeMemPtr, 0x70a0823100000000000000000000000000000000000000000000000000000000) + mstore(add(4, freeMemPtr), mload(addressToValidate)) + + // call `THRESHOLD_ASSET.balanceOf` + let success := call( + gas, // forward all gas + thresholdAssetAddress, // call address of asset proxy + 0, // don't send any ETH + freeMemPtr, // pointer to start of input + 0x24, // length of input (one padded address) + freeMemPtr, // write output to next free memory offset + 0x20 // reserve space for return balance (0x20 bytes) + ) + if eq(success, 0) { + // @TODO Revert with `Error("BALANCE_QUERY_FAILED")` + mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) + mstore(0x20, 0x0000002000000000000000000000000000000000000000000000000000000000) + mstore(0x40, 0x0000001442414c414e43455f51554552595f4641494c45440000000000000000) + mstore(0x60, 0x00000000) + // Revert length calculation: + // 4 -- error selector + // 32 -- offset to string + // 32 -- string length field + // 32 -- strlen(BALANCE_QUERY_FAILED) rounded up to nearest 32-byte word. + revert(0, 100) + } + + // Revert if balance not held + let addressBalance := mload(freeMemPtr) + if eq(addressBalance, 0) { + // Revert with `Error("AT_LEAST_ONE_ADDRESS_DOES_NOT_MEET_BALANCE_THRESHOLD")` + mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) + mstore(0x20, 0x0000002000000000000000000000000000000000000000000000000000000000) + mstore(0x40, 0x0000003441545f4c454153545f4f4e455f414444524553535f444f45535f4e4f) + mstore(0x60, 0x545f4d4545545f42414c414e43455f5448524553484f4c440000000000000000) + mstore(0x80, 0x00000000) + // Revert length calculation: + // 4 -- error selector + // 32 -- offset to string + // 32 -- string length field + // 64 -- strlen(AT_LEAST_ONE_ADDRESS_DOES_NOT_MEET_BALANCE_THRESHOLD) rounded up to nearest 32-byte word. + revert(0, 132) + } + } + + // Record validated addresses + validatedAddresses := addressesToValidate + } + + ///// If we hit this point then all addresses are valid ///// + emit ValidatedAddresses(validatedAddresses); + } +} \ No newline at end of file diff --git a/packages/contracts/contracts/extensions/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol b/packages/contracts/contracts/extensions/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol new file mode 100644 index 000000000..1de415f27 --- /dev/null +++ b/packages/contracts/contracts/extensions/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol @@ -0,0 +1,44 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; +pragma experimental ABIEncoderV2; + +import "../../../protocol/Exchange/interfaces/IExchange.sol"; +import "../interfaces/IThresholdAsset.sol"; + + +contract MBalanceThresholdFilterCore { + + IExchange internal EXCHANGE; + IThresholdAsset internal THRESHOLD_ASSET; + + event ValidatedAddresses ( + address[] addresses + ); + + function executeTransaction( + uint256 salt, + address signerAddress, + bytes signedExchangeTransaction, + bytes signature + ) + external; + + function validateBalanceThresholdsOrRevert() internal; +} \ No newline at end of file -- cgit From 58a382d9b69eedc5f618c8d29d130151ba0ed740 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 4 Dec 2018 16:07:45 -0800 Subject: Function Documentation --- .../MixinBalanceThresholdFilterCore.sol | 126 +++++++++++---------- .../mixins/MBalanceThresholdFilterCore.sol | 29 +++++ 2 files changed, 96 insertions(+), 59 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol b/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol index dbb352f90..fe4a50a29 100644 --- a/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol +++ b/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol @@ -24,6 +24,28 @@ import "./mixins/MBalanceThresholdFilterCore.sol"; contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { + /// @dev Executes an Exchange transaction iff the maker and taker meet + /// the hold at least `BALANCE_THRESHOLD` of the asset `THRESHOLD_ASSET` OR + /// the exchange function is a cancellation. + /// Supported Exchange functions: + /// - batchFillOrdersNoThrow + /// - batchFillOrKillOrders + /// - fillOrder + /// - fillOrderNoThrow + /// - fillOrKillOrder + /// - marketBuyOrders + /// - marketBuyOrdersNoThrow + /// - marketSellOrders + /// - marketSellOrdersNoThrow + /// - matchOrders + /// - cancelOrder + /// - batchCancelOrders + /// - cancelOrdersUpTo + /// Trying to call any other exchange function will throw. + /// @param salt Arbitrary number to ensure uniqueness of transaction hash. + /// @param signerAddress Address of transaction signer. + /// @param signedExchangeTransaction AbiV2 encoded calldata. + /// @param signature Proof of signer transaction by signer. function executeTransaction( uint256 salt, address signerAddress, @@ -44,25 +66,29 @@ contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { ); } + /// @dev Validates addresses meet the balance threshold specified by `BALANCE_THRESHOLD` + /// for the asset `THRESHOLD_ASSET`. If one address does not meet the thresold + /// then this function will revert. Which addresses are validated depends on + /// which Exchange function is to be called (defined by `signedExchangeTransaction` above). + /// No parameters are taken as this function reads arguments directly from calldata, to save gas. + /// If all addresses are valid then this function emits a ValidatedAddresses event, listing all + /// of the addresses whose balance thresholds it checked. function validateBalanceThresholdsOrRevert() internal { // Addresses that are validated below. address[] memory validatedAddresses; - /** - * Do not add variables after this point. - * The assembly block may overwrite their values. - */ + + ///// Do not add variables after this point. ///// + ///// The assembly block may overwrite their values. ///// // Validate addresses assembly { - /** - * Emulates the `calldataload` opcode on the embedded Exchange calldata, - * which is accessed through `signedExchangeTransaction`. - * @param offset - Offset into the Exchange calldata. - * @return value - Corresponding 32 byte value stored at `offset`. - */ + /// @dev Emulates the `calldataload` opcode on the embedded Exchange calldata, + /// which is accessed through `signedExchangeTransaction`. + /// @param offset - Offset into the Exchange calldata. + /// @return value - Corresponding 32 byte value stored at `offset`. function exchangeCalldataload(offset) -> value { // Pointer to exchange transaction // 0x04 for calldata selector @@ -77,23 +103,19 @@ contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { value := calldataload(exchangeCalldataOffset) } - /** - * Convenience function that skips the 4 byte selector when loading - * from the embedded Exchange calldata. - * @param offset - Offset into the Exchange calldata (minus the 4 byte selector) - * @return value - Corresponding 32 byte value stored at `offset` + 4. - */ + /// @dev Convenience function that skips the 4 byte selector when loading + /// from the embedded Exchange calldata. + /// @param offset - Offset into the Exchange calldata (minus the 4 byte selector) + /// @return value - Corresponding 32 byte value stored at `offset` + 4. function loadExchangeData(offset) -> value { value := exchangeCalldataload(add(offset, 0x4)) } - /** - * A running list is maintained of addresses to validate. - * This function records an address in this array. - * @param addressToValidate - Address to record for validation. - * @note - Variables are scoped but names are not, so we append - * underscores to names that share the global namespace. - */ + /// @dev A running list is maintained of addresses to validate. + /// This function records an address in this array. + /// @param addressToValidate - Address to record for validation. + /// @note - Variables are scoped but names are not, so we append + /// underscores to names that share the global namespace. function recordAddressToValidate(addressToValidate) { // Compute `addressesToValidate` memory offset let addressesToValidate_ := mload(0x40) @@ -108,24 +130,20 @@ contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { mstore(add(addressesToValidate_, offset), addressToValidate) } - /** - * Extracts the maker address from an order stored in the Exchange calldata - * (which is embedded in `signedExchangeTransaction`), and records it in - * the running list of addresses to validate. - * @param orderParamIndex - Index of the order in the Exchange function's signature - */ + /// @dev Extracts the maker address from an order stored in the Exchange calldata + /// (which is embedded in `signedExchangeTransaction`), and records it in + /// the running list of addresses to validate. + /// @param orderParamIndex - Index of the order in the Exchange function's signature function recordMakerAddressFromOrder(orderParamIndex) { let orderPtr := loadExchangeData(orderParamIndex) let makerAddress := loadExchangeData(orderPtr) recordAddressToValidate(makerAddress) } - /** - * Extracts the maker addresses from an array of orders stored in the Exchange calldata - * (which is embedded in `signedExchangeTransaction`), and records them in - * the running list of addresses to validate. - * @param orderArrayParamIndex - Index of the order array in the Exchange function's signature - */ + /// @dev Extracts the maker addresses from an array of orders stored in the Exchange calldata + /// (which is embedded in `signedExchangeTransaction`), and records them in + /// the running list of addresses to validate. + /// @param orderArrayParamIndex - Index of the order array in the Exchange function's signature function recordMakerAddressesFromOrderArray(orderArrayParamIndex) { let orderArrayPtr := loadExchangeData(orderArrayParamIndex) let orderArrayLength := loadExchangeData(orderArrayPtr) @@ -138,11 +156,9 @@ contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { } } - /** - * Records address of signer in the running list of addresses to validate. - * @note: We cannot access `signerAddress` directly from within the asm function, - * so it is loaded from the calldata. - */ + /// @dev Records address of signer in the running list of addresses to validate. + /// @note: We cannot access `signerAddress` directly from within the asm function, + /// so it is loaded from the calldata. function recordSignerAddress() { // Load the signer address from calldata // 0x04 for selector @@ -151,11 +167,9 @@ contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { recordAddressToValidate(signerAddress_) } - /** - * Records addresses to be validated when Exchange transaction is a batch fill variant. - * This is one of: batchFillOrders, batchFillOrKillOrders, batchFillNoThrow - * Reference signature: (Order[],uint256[],bytes[]) - */ + /// @dev Records addresses to be validated when Exchange transaction is a batch fill variant. + /// This is one of: batchFillOrders, batchFillOrKillOrders, batchFillNoThrow + /// Reference signature: (Order[],uint256[],bytes[]) function recordAddressesForBatchFillVariant() { // Record maker addresses from order array (parameter index 0) // The signer is the taker for these orders and must also be validated. @@ -163,11 +177,9 @@ contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { recordSignerAddress() } - /** - * Records addresses to be validated when Exchange transaction is a fill order variant. - * This is one of: fillOrder, fillOrKillOrder, fillOrderNoThrow - * Reference signature: (Order,uint256,bytes) - */ + /// @dev Records addresses to be validated when Exchange transaction is a fill order variant. + /// This is one of: fillOrder, fillOrKillOrder, fillOrderNoThrow + /// Reference signature: (Order,uint256,bytes) function recordAddressesForFillOrderVariant() { // Record maker address from the order (param index 0) // The signer is the taker for this order and must also be validated. @@ -175,11 +187,9 @@ contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { recordSignerAddress() } - /** - * Records addresses to be validated when Exchange transaction is a market fill variant. - * This is one of: marketBuyOrders, marketBuyOrdersNoThrow, marketSellOrders, marketSellOrdersNoThrow - * Reference signature: (Order[],uint256,bytes[]) - */ + /// @dev Records addresses to be validated when Exchange transaction is a market fill variant. + /// This is one of: marketBuyOrders, marketBuyOrdersNoThrow, marketSellOrders, marketSellOrdersNoThrow + /// Reference signature: (Order[],uint256,bytes[]) function recordAddressesForMarketFillVariant() { // Record maker addresses from order array (parameter index 0) // The signer is the taker for these orders and must also be validated. @@ -187,10 +197,8 @@ contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { recordSignerAddress() } - /** - * Records addresses to be validated when Exchange transaction is matchOrders. - * Reference signature: matchOrders(Order,Order) - */ + /// @dev Records addresses to be validated when Exchange transaction is matchOrders. + /// Reference signature: matchOrders(Order,Order) function recordAddressesForMatchOrders() { // Record maker address from both orders (param indices 0 & 1). // The signer is the taker and must also be validated. diff --git a/packages/contracts/contracts/extensions/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol b/packages/contracts/contracts/extensions/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol index 1de415f27..ecebaa31b 100644 --- a/packages/contracts/contracts/extensions/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol +++ b/packages/contracts/contracts/extensions/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol @@ -32,6 +32,28 @@ contract MBalanceThresholdFilterCore { address[] addresses ); + /// @dev Executes an Exchange transaction iff the maker and taker meet + /// the hold at least `BALANCE_THRESHOLD` of the asset `THRESHOLD_ASSET` OR + /// the exchange function is a cancellation. + /// Supported Exchange functions: + /// - batchFillOrdersNoThrow + /// - batchFillOrKillOrders + /// - fillOrder + /// - fillOrderNoThrow + /// - fillOrKillOrder + /// - marketBuyOrders + /// - marketBuyOrdersNoThrow + /// - marketSellOrders + /// - marketSellOrdersNoThrow + /// - matchOrders + /// - cancelOrder + /// - batchCancelOrders + /// - cancelOrdersUpTo + /// Trying to call any other exchange function will throw. + /// @param salt Arbitrary number to ensure uniqueness of transaction hash. + /// @param signerAddress Address of transaction signer. + /// @param signedExchangeTransaction AbiV2 encoded calldata. + /// @param signature Proof of signer transaction by signer. function executeTransaction( uint256 salt, address signerAddress, @@ -40,5 +62,12 @@ contract MBalanceThresholdFilterCore { ) external; + /// @dev Validates addresses meet the balance threshold specified by `BALANCE_THRESHOLD` + /// for the asset `THRESHOLD_ASSET`. If one address does not meet the thresold + /// then this function will revert. Which addresses are validated depends on + /// which Exchange function is to be called (defined by `signedExchangeTransaction` above). + /// No parameters are taken as this function reads arguments directly from calldata, to save gas. + /// If all addresses are valid then this function emits a ValidatedAddresses event, listing all + /// of the addresses whose balance thresholds it checked. function validateBalanceThresholdsOrRevert() internal; } \ No newline at end of file -- cgit From 14c97b3ec368904d2e072f4603ab41ea8458aebb Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 4 Dec 2018 16:15:35 -0800 Subject: Include threshold balance in constructor of BalanceThresholdFilter contract --- .../BalanceThresholdFilter/BalanceThresholdFilter.sol | 11 ++++++++++- .../MixinBalanceThresholdFilterCore.sol | 3 ++- .../mixins/MBalanceThresholdFilterCore.sol | 7 +++++++ .../contracts/test/extensions/balance_threshold_filter.ts | 2 ++ 4 files changed, 21 insertions(+), 2 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/BalanceThresholdFilter/BalanceThresholdFilter.sol b/packages/contracts/contracts/extensions/BalanceThresholdFilter/BalanceThresholdFilter.sol index ce3e925fe..bf4a94509 100644 --- a/packages/contracts/contracts/extensions/BalanceThresholdFilter/BalanceThresholdFilter.sol +++ b/packages/contracts/contracts/extensions/BalanceThresholdFilter/BalanceThresholdFilter.sol @@ -26,10 +26,19 @@ import "./MixinBalanceThresholdFilterCore.sol"; contract BalanceThresholdFilter is MixinBalanceThresholdFilterCore { - constructor(address exchange, address thresholdAsset) + /// @dev Constructs BalanceThresholdFilter. + /// @param exchange Address of 0x exchange. + /// @param thresholdAsset The asset that must be held by makers/takers. + /// @param thresholdBalance The minimum balance of `thresholdAsset` that must be held by makers/takers. + constructor( + address exchange, + address thresholdAsset, + uint256 thresholdBalance + ) public { EXCHANGE = IExchange(exchange); THRESHOLD_ASSET = IThresholdAsset(thresholdAsset); + THRESHOLD_BALANCE = thresholdBalance; } } \ No newline at end of file diff --git a/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol b/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol index fe4a50a29..51b3b9736 100644 --- a/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol +++ b/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol @@ -263,6 +263,7 @@ contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { // Validate addresses let thresholdAssetAddress := sload(THRESHOLD_ASSET_slot) + let thresholdBalance := sload(THRESHOLD_BALANCE_slot) for {let addressToValidate := addressesToValidateElementPtr} lt(addressToValidate, addressesToValidateElementEndPtr) {addressToValidate := add(addressToValidate, 0x20)} { // Construct calldata for `THRESHOLD_ASSET.balanceOf` mstore(freeMemPtr, 0x70a0823100000000000000000000000000000000000000000000000000000000) @@ -294,7 +295,7 @@ contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { // Revert if balance not held let addressBalance := mload(freeMemPtr) - if eq(addressBalance, 0) { + if lt(addressBalance, thresholdBalance) { // Revert with `Error("AT_LEAST_ONE_ADDRESS_DOES_NOT_MEET_BALANCE_THRESHOLD")` mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) mstore(0x20, 0x0000002000000000000000000000000000000000000000000000000000000000) diff --git a/packages/contracts/contracts/extensions/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol b/packages/contracts/contracts/extensions/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol index ecebaa31b..046caecdd 100644 --- a/packages/contracts/contracts/extensions/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol +++ b/packages/contracts/contracts/extensions/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol @@ -25,9 +25,16 @@ import "../interfaces/IThresholdAsset.sol"; contract MBalanceThresholdFilterCore { + // Points to 0x exchange contract IExchange internal EXCHANGE; + + // The asset that must be held by makers/takers IThresholdAsset internal THRESHOLD_ASSET; + // The minimum balance of `THRESHOLD_ASSET` that must be held by makers/takers + uint256 internal THRESHOLD_BALANCE; + + // Addresses that hold at least `THRESHOLD_BALANCE` of `THRESHOLD_ASSET` event ValidatedAddresses ( address[] addresses ); diff --git a/packages/contracts/test/extensions/balance_threshold_filter.ts b/packages/contracts/test/extensions/balance_threshold_filter.ts index 50fd79439..db4fea77a 100644 --- a/packages/contracts/test/extensions/balance_threshold_filter.ts +++ b/packages/contracts/test/extensions/balance_threshold_filter.ts @@ -126,12 +126,14 @@ describe.only(ContractName.BalanceThresholdFilter, () => { const privateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantMakerAddress)]; orderFactory = new OrderFactory(privateKey, defaultOrderParams); // Deploy Compliant Forwarder + const erc721BalanceThreshold = new BigNumber(1); compliantForwarderInstance = await BalanceThresholdFilterContract.deployFrom0xArtifactAsync( artifacts.BalanceThresholdFilter, provider, txDefaults, exchangeInstance.address, yesTokenInstance.address, + erc721BalanceThreshold ); /* const compliantForwarderContract = new BalanceThresholdFilterContract( -- cgit From 18f028fb0833b5a5b983a213343caa7460262002 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 4 Dec 2018 16:47:45 -0800 Subject: Removed unnecessary note --- .../BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol | 2 -- 1 file changed, 2 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol b/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol index 51b3b9736..f730c5a11 100644 --- a/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol +++ b/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol @@ -114,8 +114,6 @@ contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { /// @dev A running list is maintained of addresses to validate. /// This function records an address in this array. /// @param addressToValidate - Address to record for validation. - /// @note - Variables are scoped but names are not, so we append - /// underscores to names that share the global namespace. function recordAddressToValidate(addressToValidate) { // Compute `addressesToValidate` memory offset let addressesToValidate_ := mload(0x40) -- cgit From 7e7880aea05b70e6d0515586a005fafe34eba850 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 4 Dec 2018 17:28:05 -0800 Subject: Conformed to hex format for constructing offsets --- .../BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol b/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol index f730c5a11..9bed7ba47 100644 --- a/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol +++ b/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol @@ -79,7 +79,6 @@ contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { // Addresses that are validated below. address[] memory validatedAddresses; - ///// Do not add variables after this point. ///// ///// The assembly block may overwrite their values. ///// @@ -265,7 +264,7 @@ contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { for {let addressToValidate := addressesToValidateElementPtr} lt(addressToValidate, addressesToValidateElementEndPtr) {addressToValidate := add(addressToValidate, 0x20)} { // Construct calldata for `THRESHOLD_ASSET.balanceOf` mstore(freeMemPtr, 0x70a0823100000000000000000000000000000000000000000000000000000000) - mstore(add(4, freeMemPtr), mload(addressToValidate)) + mstore(add(freeMemPtr, 0x04), mload(addressToValidate)) // call `THRESHOLD_ASSET.balanceOf` let success := call( -- cgit From d882133e444d034c62b5f88c988d4cdd3fb2dbf4 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 6 Dec 2018 18:00:53 -0800 Subject: Wrappers for balance threshold filter + updated some tests to use the wrapper --- .../test/extensions/balance_threshold_filter.ts | 249 +++++++++++++++------ .../test/utils/balance_threshold_wrapper.ts | 243 ++++++++++++++++++++ .../test/utils/compliant_forwarder_wrapper.ts | 0 3 files changed, 429 insertions(+), 63 deletions(-) create mode 100644 packages/contracts/test/utils/balance_threshold_wrapper.ts delete mode 100644 packages/contracts/test/utils/compliant_forwarder_wrapper.ts (limited to 'packages') diff --git a/packages/contracts/test/extensions/balance_threshold_filter.ts b/packages/contracts/test/extensions/balance_threshold_filter.ts index db4fea77a..9b48cdf93 100644 --- a/packages/contracts/test/extensions/balance_threshold_filter.ts +++ b/packages/contracts/test/extensions/balance_threshold_filter.ts @@ -6,6 +6,7 @@ import { Web3Wrapper } from '@0x/web3-wrapper'; import * as chai from 'chai'; import * as ethUtil from 'ethereumjs-util'; import * as _ from 'lodash'; +import { TransactionReceiptWithDecodedLogs } from 'ethereum-types'; import { DummyERC20TokenContract } from '../../generated-wrappers/dummy_erc20_token'; import { ExchangeContract } from '../../generated-wrappers/exchange'; @@ -24,6 +25,7 @@ import { ExchangeWrapper } from '../utils/exchange_wrapper'; import { OrderFactory } from '../utils/order_factory'; import { orderUtils } from '../utils/order_utils'; import { TransactionFactory } from '../utils/transaction_factory'; +import { BalanceThresholdWrapper } from '../utils/balance_threshold_wrapper'; import { ContractName, ERC20BalancesByOwner, SignedTransaction } from '../utils/types'; import { provider, txDefaults, web3Wrapper } from '../utils/web3_wrapper'; @@ -37,6 +39,10 @@ const expect = chai.expect; const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); const DECIMALS_DEFAULT = 18; +interface ValidatedAddressesLog { + args: {addresses: string[]} +} + describe.only(ContractName.BalanceThresholdFilter, () => { let compliantMakerAddress: string; let owner: string; @@ -53,18 +59,35 @@ describe.only(ContractName.BalanceThresholdFilter, () => { let orderFactory: OrderFactory; let erc20Wrapper: ERC20Wrapper; let erc20Balances: ERC20BalancesByOwner; + let balanceThresholdWrapper: BalanceThresholdWrapper; let takerTransactionFactory: TransactionFactory; let compliantSignedOrder: SignedOrder; let compliantSignedFillOrderTx: SignedTransaction; let noncompliantSignedFillOrderTx: SignedTransaction; + let logDecoder: LogDecoder; + const takerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(500), DECIMALS_DEFAULT); const makerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(1000), DECIMALS_DEFAULT); const takerAssetFillAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(250), DECIMALS_DEFAULT); let compliantForwarderInstance: BalanceThresholdFilterContract; + const assertValidatedAddressesLog = async (txReceipt: TransactionReceiptWithDecodedLogs, expectedValidatedAddresses: string[]) => { + expect(txReceipt.logs.length).to.be.gte(1); + const validatedAddressesLog = (txReceipt.logs[0] as any) as ValidatedAddressesLog; + const validatedAddresses = validatedAddressesLog.args.addresses; + // @HACK-hysz: Nested addresses are not translated to lower-case but this will change once + // the new ABI Encoder/Decoder is used by the contract templates. + let validatedAddressesNormalized: string[] = []; + _.each(validatedAddresses, (address) => { + const normalizedAddress = _.toLower(address); + validatedAddressesNormalized.push(normalizedAddress); + }); + expect(validatedAddressesNormalized).to.be.deep.equal(expectedValidatedAddresses); + }; + before(async () => { // Create accounts await blockchainLifecycle.startAsync(); @@ -190,17 +213,30 @@ describe.only(ContractName.BalanceThresholdFilter, () => { compliantSignedOrderWithoutExchangeAddressData, ); - /* generate selectors for every exchange method + /* _.each(exchangeInstance.abi, (abiDefinition: AbiDefinition) => { try { const method = new Method(abiDefinition as MethodAbi); - console.log('\n', `// ${method.getDataItem().name}`); - console.log(`bytes4 constant ${method.getDataItem().name}Selector = ${method.getSelector()};`); - console.log(`bytes4 constant ${method.getDataItem().name}SelectorGenerator = byes4(keccak256('${method.getSignature()}'));`); + console.log(method.getSignature()); + if (!method.getSignature().startsWith('matchOrders')) { + return; + } + console.log(`FOUND IT`); + const signedOrderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress( + compliantSignedOrder, + ); + const args = [signedOrderWithoutExchangeAddress, signedOrderWithoutExchangeAddress, compliantSignedOrder.signature, compliantSignedOrder.signature]; + console.log(method.encode(args, {annotate: true})); + //console.log('\n', `// ${method.getDataItem().name}`); + //console.log(`bytes4 constant ${method.getDataItem().name}Selector = ${method.getSelector()};`); + //console.log(`bytes4 constant ${method.getDataItem().name}SelectorGenerator = byes4(keccak256('${method.getSignature()}'));`); } catch(e) { - _.noop(); + console.log(`encoding failed: ${e}`); } - });*/ + }); + throw new Error(`w`);*/ + logDecoder = new LogDecoder(web3Wrapper); + balanceThresholdWrapper = new BalanceThresholdWrapper(compliantForwarderInstance, exchangeInstance, new TransactionFactory(takerPrivateKey, exchangeInstance.address), provider); }); beforeEach(async () => { await blockchainLifecycle.startAsync(); @@ -208,21 +244,101 @@ describe.only(ContractName.BalanceThresholdFilter, () => { afterEach(async () => { await blockchainLifecycle.revertAsync(); }); - describe.only('fillOrder', () => { + + describe('General Sanity Checks', () => { + it('should revert if the signed transaction is not intended for supported', async () => { + // Create signed order without the fillOrder function selector + const txDataBuf = ethUtil.toBuffer(compliantSignedFillOrderTx.data); + const selectorLengthInBytes = 4; + const txDataBufMinusSelector = txDataBuf.slice(selectorLengthInBytes); + const badSelector = '0x00000000'; + const badSelectorBuf = ethUtil.toBuffer(badSelector); + const txDataBufWithBadSelector = Buffer.concat([badSelectorBuf, txDataBufMinusSelector]); + const txDataBufWithBadSelectorHex = ethUtil.bufferToHex(txDataBufWithBadSelector); + // Call compliant forwarder + return expectTransactionFailedWithoutReasonAsync(compliantForwarderInstance.executeTransaction.sendTransactionAsync( + compliantSignedFillOrderTx.salt, + compliantSignedFillOrderTx.signerAddress, + txDataBufWithBadSelectorHex, + compliantSignedFillOrderTx.signature, + )); + }); + it('should revert if senderAddress is not set to the compliant forwarding contract', async () => { + // Create signed order with incorrect senderAddress + const notBalanceThresholdFilterAddress = zrxToken.address; + const signedOrderWithBadSenderAddress = await orderFactory.newSignedOrderAsync({ + senderAddress: notBalanceThresholdFilterAddress, + }); + const signedOrderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress( + signedOrderWithBadSenderAddress, + ); + const signedOrderWithoutExchangeAddressData = exchangeInstance.fillOrder.getABIEncodedTransactionData( + signedOrderWithoutExchangeAddress, + takerAssetFillAmount, + compliantSignedOrder.signature, + ); + const signedFillOrderTx = takerTransactionFactory.newSignedTransaction( + signedOrderWithoutExchangeAddressData, + ); + // Call compliant forwarder + return expectTransactionFailedWithoutReasonAsync(compliantForwarderInstance.executeTransaction.sendTransactionAsync( + signedFillOrderTx.salt, + signedFillOrderTx.signerAddress, + signedFillOrderTx.data, + signedFillOrderTx.signature, + )); + }); + // @TODO - greater than 1 balance + }); + + + + describe('batchFillOrders', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); }); it('should transfer the correct amounts when maker and taker are compliant', async () => { + let order2 = _.cloneDeep(compliantSignedOrder); + order2.makerAddress = `0x${_.reverse(compliantSignedOrder.makerAddress.slice(2).split('')).join('')}`; + const orders = [compliantSignedOrder, order2]; + const fillAmounts = [new BigNumber(4), new BigNumber(4)]; + const signatures = ["0xabcd", "0xabcd"]; + const exchangeCalldata = exchangeInstance.batchFillOrders.getABIEncodedTransactionData(orders, fillAmounts, signatures); + console.log('*'.repeat(40), exchangeCalldata, '*'.repeat(40)); + console.log('****** MAKER ADDRESS = ', compliantSignedOrder.makerAddress); + const txHash = await compliantForwarderInstance.executeTransaction.sendTransactionAsync( compliantSignedFillOrderTx.salt, compliantSignedFillOrderTx.signerAddress, - compliantSignedFillOrderTx.data, + exchangeCalldata, compliantSignedFillOrderTx.signature, ); const decoder = new LogDecoder(web3Wrapper); const tx = await decoder.getTxWithDecodedLogsAsync(txHash); console.log(JSON.stringify(tx, null, 4)); - console.log('****** MAKER ADDRESS = ', compliantSignedOrder.makerAddress); + }); + }); + + describe('batchFillOrdersNoThrow', () => { + }); + + describe('batchFillOrKillOrders', () => { + }); + + describe('batchFillOrKillOrders', () => { + }); + + describe.only('fillOrder', () => { + beforeEach(async () => { + erc20Balances = await erc20Wrapper.getBalancesAsync(); + }); + it('should transfer the correct amounts and validate both maker/taker when both maker and taker meet the balance threshold', async () => { + // Execute a valid fill + const txReceipt = await balanceThresholdWrapper.fillOrderAsync(compliantSignedOrder, compliantSignedFillOrderTx.signerAddress, {takerAssetFillAmount}); + // Assert validated addresses + const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedFillOrderTx.signerAddress]; + assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); + // Check balances const newBalances = await erc20Wrapper.getBalancesAsync(); const makerAssetFillAmount = takerAssetFillAmount .times(compliantSignedOrder.makerAssetAmount) @@ -255,31 +371,14 @@ describe.only(ContractName.BalanceThresholdFilter, () => { erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)), ); }); - it('should revert if the signed transaction is not intended for fillOrder', async () => { - // Create signed order without the fillOrder function selector - const txDataBuf = ethUtil.toBuffer(compliantSignedFillOrderTx.data); - const selectorLengthInBytes = 4; - const txDataBufMinusSelector = txDataBuf.slice(selectorLengthInBytes); - const badSelector = '0x00000000'; - const badSelectorBuf = ethUtil.toBuffer(badSelector); - const txDataBufWithBadSelector = Buffer.concat([badSelectorBuf, txDataBufMinusSelector]); - const txDataBufWithBadSelectorHex = ethUtil.bufferToHex(txDataBufWithBadSelector); - // Call compliant forwarder - return expectTransactionFailedWithoutReasonAsync(compliantForwarderInstance.executeTransaction.sendTransactionAsync( - compliantSignedFillOrderTx.salt, - compliantSignedFillOrderTx.signerAddress, - txDataBufWithBadSelectorHex, - compliantSignedFillOrderTx.signature, - )); - }); - it('should revert if senderAddress is not set to the compliant forwarding contract', async () => { - // Create signed order with incorrect senderAddress - const notBalanceThresholdFilterAddress = zrxToken.address; - const signedOrderWithBadSenderAddress = await orderFactory.newSignedOrderAsync({ - senderAddress: notBalanceThresholdFilterAddress, + it('should revert if maker does not meet the balance threshold', async () => { + // Create signed order with non-compliant maker address + const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ + senderAddress: compliantForwarderInstance.address, + makerAddress: nonCompliantAddress }); const signedOrderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress( - signedOrderWithBadSenderAddress, + signedOrderWithBadMakerAddress, ); const signedOrderWithoutExchangeAddressData = exchangeInstance.fillOrder.getABIEncodedTransactionData( signedOrderWithoutExchangeAddress, @@ -290,14 +389,17 @@ describe.only(ContractName.BalanceThresholdFilter, () => { signedOrderWithoutExchangeAddressData, ); // Call compliant forwarder - return expectTransactionFailedWithoutReasonAsync(compliantForwarderInstance.executeTransaction.sendTransactionAsync( - signedFillOrderTx.salt, - signedFillOrderTx.signerAddress, - signedFillOrderTx.data, - signedFillOrderTx.signature, - )); + return expectTransactionFailedAsync( + compliantForwarderInstance.executeTransaction.sendTransactionAsync( + signedFillOrderTx.salt, + signedFillOrderTx.signerAddress, + signedFillOrderTx.data, + signedFillOrderTx.signature, + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); }); - it('should revert if taker address is not compliant (does not hold a Yes Token)', async () => { + it('should revert if taker does not meet the balance threshold', async () => { return expectTransactionFailedAsync( compliantForwarderInstance.executeTransaction.sendTransactionAsync( compliantSignedFillOrderTx.salt, @@ -308,7 +410,16 @@ describe.only(ContractName.BalanceThresholdFilter, () => { RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold ); }); - it('should revert if maker address is not compliant (does not hold a Yes Token)', async () => { + }); + + describe('fillOrderNoThrow', () => { + beforeEach(async () => { + erc20Balances = await erc20Wrapper.getBalancesAsync(); + }); + it('should transfer the correct amounts and validate both maker/taker when both maker and taker meet the balance threshold', async () => { + + }); + it('should revert if maker does not meet the balance threshold', async () => { // Create signed order with non-compliant maker address const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ senderAddress: compliantForwarderInstance.address, @@ -336,32 +447,44 @@ describe.only(ContractName.BalanceThresholdFilter, () => { RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold ); }); + it('should revert if taker does not meet the balance threshold', async () => { + return expectTransactionFailedAsync( + compliantForwarderInstance.executeTransaction.sendTransactionAsync( + compliantSignedFillOrderTx.salt, + nonCompliantAddress, + compliantSignedFillOrderTx.data, + compliantSignedFillOrderTx.signature, + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); + }); }); - describe('batchFillOrders', () => { - beforeEach(async () => { - erc20Balances = await erc20Wrapper.getBalancesAsync(); - }); - it('should transfer the correct amounts when maker and taker are compliant', async () => { - let order2 = _.cloneDeep(compliantSignedOrder); - order2.makerAddress = `0x${_.reverse(compliantSignedOrder.makerAddress.slice(2).split('')).join('')}`; - const orders = [compliantSignedOrder, order2]; - const fillAmounts = [new BigNumber(4), new BigNumber(4)]; - const signatures = ["0xabcd", "0xabcd"]; - const exchangeCalldata = exchangeInstance.batchFillOrders.getABIEncodedTransactionData(orders, fillAmounts, signatures); - console.log('*'.repeat(40), exchangeCalldata, '*'.repeat(40)); - console.log('****** MAKER ADDRESS = ', compliantSignedOrder.makerAddress); + describe('fillOrKillOrder', () => { + }); - const txHash = await compliantForwarderInstance.executeTransaction.sendTransactionAsync( - compliantSignedFillOrderTx.salt, - compliantSignedFillOrderTx.signerAddress, - exchangeCalldata, - compliantSignedFillOrderTx.signature, - ); - const decoder = new LogDecoder(web3Wrapper); - const tx = await decoder.getTxWithDecodedLogsAsync(txHash); - console.log(JSON.stringify(tx, null, 4)); - }); + describe('marketBuyOrders', () => { + }); + + describe('marketBuyOrdersNoThrow', () => { + }); + + describe('marketSellOrders', () => { + }); + + describe('marketSellOrdersNoThrow', () => { + }); + + describe('matchOrders', () => { + }); + + describe('cancelOrder', () => { + }); + + describe('batchCancelOrders', () => { + }); + + describe('cancelOrdersUpTo', () => { }); }); // tslint:disable:max-file-line-count diff --git a/packages/contracts/test/utils/balance_threshold_wrapper.ts b/packages/contracts/test/utils/balance_threshold_wrapper.ts new file mode 100644 index 000000000..ac7bdd593 --- /dev/null +++ b/packages/contracts/test/utils/balance_threshold_wrapper.ts @@ -0,0 +1,243 @@ +import { SignedOrder } from '@0x/types'; +import { BigNumber } from '@0x/utils'; +import { Web3Wrapper } from '@0x/web3-wrapper'; +import { Provider, TransactionReceiptWithDecodedLogs } from 'ethereum-types'; +import * as _ from 'lodash'; + +import { ExchangeContract } from '../../generated-wrappers/exchange'; +import { BalanceThresholdFilterContract } from '../../generated-wrappers/balance_threshold_filter'; + +import { formatters } from './formatters'; +import { LogDecoder } from './log_decoder'; +import { orderUtils } from './order_utils'; +import { TransactionFactory } from '../utils/transaction_factory'; +import { OrderInfo } from './types'; + +export class BalanceThresholdWrapper { + private readonly _balanceThresholdFilter: BalanceThresholdFilterContract; + private readonly _signerTransactionFactory: TransactionFactory; + private readonly _exchange: ExchangeContract; + private readonly _web3Wrapper: Web3Wrapper; + private readonly _logDecoder: LogDecoder; + constructor(balanceThresholdFilter: BalanceThresholdFilterContract, exchangeContract: ExchangeContract, signerTransactionFactory: TransactionFactory, provider: Provider) { + this._balanceThresholdFilter = balanceThresholdFilter; + this._exchange = exchangeContract; + this._signerTransactionFactory = signerTransactionFactory; + this._web3Wrapper = new Web3Wrapper(provider); + this._logDecoder = new LogDecoder(this._web3Wrapper); + } + public async fillOrderAsync( + signedOrder: SignedOrder, + from: string, + opts: { takerAssetFillAmount?: BigNumber } = {}, + ): Promise { + const params = orderUtils.createFill(signedOrder, opts.takerAssetFillAmount); + const data = this._exchange.fillOrder.getABIEncodedTransactionData( + params.order, + params.takerAssetFillAmount, + params.signature, + ); + const txReceipt = this._executeTransaction(data, from); + return txReceipt; + } + public async fillOrKillOrderAsync( + signedOrder: SignedOrder, + from: string, + opts: { takerAssetFillAmount?: BigNumber } = {}, + ): Promise { + const params = orderUtils.createFill(signedOrder, opts.takerAssetFillAmount); + const data = this._exchange.fillOrKillOrder.getABIEncodedTransactionData( + params.order, + params.takerAssetFillAmount, + params.signature, + ); + const txReceipt = this._executeTransaction(data, from); + return txReceipt; + } + public async fillOrderNoThrowAsync( + signedOrder: SignedOrder, + from: string, + opts: { takerAssetFillAmount?: BigNumber; gas?: number } = {}, + ): Promise { + const params = orderUtils.createFill(signedOrder, opts.takerAssetFillAmount); + const data = this._exchange.fillOrderNoThrow.getABIEncodedTransactionData( + params.order, + params.takerAssetFillAmount, + params.signature, + ); + const txReceipt = this._executeTransaction(data, from, opts.gas); + return txReceipt; + } + public async batchFillOrdersAsync( + orders: SignedOrder[], + from: string, + opts: { takerAssetFillAmounts?: BigNumber[] } = {}, + ): Promise { + const params = formatters.createBatchFill(orders, opts.takerAssetFillAmounts); + const data = this._exchange.batchFillOrders.getABIEncodedTransactionData( + params.orders, + params.takerAssetFillAmounts, + params.signatures, + ); + const txReceipt = this._executeTransaction(data, from); + return txReceipt; + } + public async batchFillOrKillOrdersAsync( + orders: SignedOrder[], + from: string, + opts: { takerAssetFillAmounts?: BigNumber[] } = {}, + ): Promise { + const params = formatters.createBatchFill(orders, opts.takerAssetFillAmounts); + const data = this._exchange.batchFillOrKillOrders.getABIEncodedTransactionData( + params.orders, + params.takerAssetFillAmounts, + params.signatures, + ); + const txReceipt = this._executeTransaction(data, from); + return txReceipt; + } + public async batchFillOrdersNoThrowAsync( + orders: SignedOrder[], + from: string, + opts: { takerAssetFillAmounts?: BigNumber[]; gas?: number } = {}, + ): Promise { + const params = formatters.createBatchFill(orders, opts.takerAssetFillAmounts); + const data = this._exchange.batchFillOrKillOrders.getABIEncodedTransactionData( + params.orders, + params.takerAssetFillAmounts, + params.signatures, + ); + const txReceipt = this._executeTransaction(data, from, opts.gas); + return txReceipt; + } + public async marketSellOrdersAsync( + orders: SignedOrder[], + from: string, + opts: { takerAssetFillAmount: BigNumber }, + ): Promise { + const params = formatters.createMarketSellOrders(orders, opts.takerAssetFillAmount); + const data = this._exchange.marketSellOrders.getABIEncodedTransactionData( + params.orders, + params.takerAssetFillAmount, + params.signatures, + ); + const txReceipt = this._executeTransaction(data, from); + return txReceipt; + } + public async marketSellOrdersNoThrowAsync( + orders: SignedOrder[], + from: string, + opts: { takerAssetFillAmount: BigNumber; gas?: number }, + ): Promise { + const params = formatters.createMarketSellOrders(orders, opts.takerAssetFillAmount); + const data = this._exchange.marketSellOrdersNoThrow.getABIEncodedTransactionData( + params.orders, + params.takerAssetFillAmount, + params.signatures, + ); + const txReceipt = this._executeTransaction(data, from, opts.gas); + return txReceipt; + } + public async marketBuyOrdersAsync( + orders: SignedOrder[], + from: string, + opts: { makerAssetFillAmount: BigNumber }, + ): Promise { + const params = formatters.createMarketBuyOrders(orders, opts.makerAssetFillAmount); + const data = this._exchange.marketBuyOrders.getABIEncodedTransactionData( + params.orders, + params.makerAssetFillAmount, + params.signatures, + ); + const txReceipt = this._executeTransaction(data, from); + return txReceipt; + } + public async marketBuyOrdersNoThrowAsync( + orders: SignedOrder[], + from: string, + opts: { makerAssetFillAmount: BigNumber; gas?: number }, + ): Promise { + const params = formatters.createMarketBuyOrders(orders, opts.makerAssetFillAmount); + const data = this._exchange.marketBuyOrdersNoThrow.getABIEncodedTransactionData( + params.orders, + params.makerAssetFillAmount, + params.signatures, + ); + const txReceipt = this._executeTransaction(data, from, opts.gas); + return txReceipt; + } + public async cancelOrderAsync(signedOrder: SignedOrder, from: string): Promise { + const params = orderUtils.createCancel(signedOrder); + const data = this._exchange.cancelOrder.getABIEncodedTransactionData(params.order); + const txReceipt = this._executeTransaction(data, from); + return txReceipt; + } + public async batchCancelOrdersAsync( + orders: SignedOrder[], + from: string, + ): Promise { + const params = formatters.createBatchCancel(orders); + const data = this._exchange.batchCancelOrders.getABIEncodedTransactionData(params.orders); + const txReceipt = this._executeTransaction(data, from); + return txReceipt; + } + public async cancelOrdersUpToAsync(salt: BigNumber, from: string): Promise { + const data = this._exchange.cancelOrdersUpTo.getABIEncodedTransactionData(salt); + const txReceipt = this._executeTransaction(data, from); + return txReceipt; + } + public async getTakerAssetFilledAmountAsync(orderHashHex: string): Promise { + const filledAmount = await this._exchange.filled.callAsync(orderHashHex); + return filledAmount; + } + public async isCancelledAsync(orderHashHex: string): Promise { + const isCancelled = await this._exchange.cancelled.callAsync(orderHashHex); + return isCancelled; + } + public async getOrderEpochAsync(makerAddress: string, senderAddress: string): Promise { + const orderEpoch = await this._exchange.orderEpoch.callAsync(makerAddress, senderAddress); + return orderEpoch; + } + public async getOrderInfoAsync(signedOrder: SignedOrder): Promise { + const orderInfo = (await this._exchange.getOrderInfo.callAsync(signedOrder)) as OrderInfo; + return orderInfo; + } + public async getOrdersInfoAsync(signedOrders: SignedOrder[]): Promise { + const ordersInfo = (await this._exchange.getOrdersInfo.callAsync(signedOrders)) as OrderInfo[]; + return ordersInfo; + } + public async matchOrdersAsync( + signedOrderLeft: SignedOrder, + signedOrderRight: SignedOrder, + from: string, + ): Promise { + const params = orderUtils.createMatchOrders(signedOrderLeft, signedOrderRight); + const data = await this._exchange.matchOrders.getABIEncodedTransactionData( + params.left, + params.right, + params.leftSignature, + params.rightSignature + ); + const txReceipt = this._executeTransaction(data, from); + return txReceipt; + } + public getBalanceThresholdAddress(): string { + return this._balanceThresholdFilter.address; + } + public getExchangeAddress(): string { + return this._exchange.address; + } + private async _executeTransaction(abiEncodedExchangeTxData: string, from: string, gas?: number): Promise { + const signedExchangeTx = this._signerTransactionFactory.newSignedTransaction(abiEncodedExchangeTxData); + const txOpts = _.isUndefined(gas) ? {from} : {from, gas}; + const txHash = await this._balanceThresholdFilter.executeTransaction.sendTransactionAsync( + signedExchangeTx.salt, + signedExchangeTx.signerAddress, + signedExchangeTx.data, + signedExchangeTx.signature, + txOpts, + ); + const txReceipt = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); + return txReceipt; + } +} diff --git a/packages/contracts/test/utils/compliant_forwarder_wrapper.ts b/packages/contracts/test/utils/compliant_forwarder_wrapper.ts deleted file mode 100644 index e69de29bb..000000000 -- cgit From 7af2c751dceb52adc84ded417b260666e6a96c29 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Fri, 7 Dec 2018 14:21:29 -0800 Subject: fillOrder tests with new wrapper --- .../test/extensions/balance_threshold_filter.ts | 37 ++++++++-------------- 1 file changed, 13 insertions(+), 24 deletions(-) (limited to 'packages') diff --git a/packages/contracts/test/extensions/balance_threshold_filter.ts b/packages/contracts/test/extensions/balance_threshold_filter.ts index 9b48cdf93..cfa1dcbdf 100644 --- a/packages/contracts/test/extensions/balance_threshold_filter.ts +++ b/packages/contracts/test/extensions/balance_threshold_filter.ts @@ -60,11 +60,11 @@ describe.only(ContractName.BalanceThresholdFilter, () => { let erc20Wrapper: ERC20Wrapper; let erc20Balances: ERC20BalancesByOwner; let balanceThresholdWrapper: BalanceThresholdWrapper; + let nonCompliantBalanceThresholdWrapper: BalanceThresholdWrapper; let takerTransactionFactory: TransactionFactory; let compliantSignedOrder: SignedOrder; let compliantSignedFillOrderTx: SignedTransaction; - let noncompliantSignedFillOrderTx: SignedTransaction; let logDecoder: LogDecoder; @@ -237,6 +237,8 @@ describe.only(ContractName.BalanceThresholdFilter, () => { throw new Error(`w`);*/ logDecoder = new LogDecoder(web3Wrapper); balanceThresholdWrapper = new BalanceThresholdWrapper(compliantForwarderInstance, exchangeInstance, new TransactionFactory(takerPrivateKey, exchangeInstance.address), provider); + const nonCompliantPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(nonCompliantAddress)]; + nonCompliantBalanceThresholdWrapper = new BalanceThresholdWrapper(compliantForwarderInstance, exchangeInstance, new TransactionFactory(nonCompliantPrivateKey, exchangeInstance.address), provider); }); beforeEach(async () => { await blockchainLifecycle.startAsync(); @@ -334,7 +336,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); it('should transfer the correct amounts and validate both maker/taker when both maker and taker meet the balance threshold', async () => { // Execute a valid fill - const txReceipt = await balanceThresholdWrapper.fillOrderAsync(compliantSignedOrder, compliantSignedFillOrderTx.signerAddress, {takerAssetFillAmount}); + const txReceipt = await balanceThresholdWrapper.fillOrderAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount}); // Assert validated addresses const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedFillOrderTx.signerAddress]; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); @@ -377,35 +379,22 @@ describe.only(ContractName.BalanceThresholdFilter, () => { senderAddress: compliantForwarderInstance.address, makerAddress: nonCompliantAddress }); - const signedOrderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress( - signedOrderWithBadMakerAddress, - ); - const signedOrderWithoutExchangeAddressData = exchangeInstance.fillOrder.getABIEncodedTransactionData( - signedOrderWithoutExchangeAddress, - takerAssetFillAmount, - compliantSignedOrder.signature, - ); - const signedFillOrderTx = takerTransactionFactory.newSignedTransaction( - signedOrderWithoutExchangeAddressData, - ); - // Call compliant forwarder + // Execute transaction return expectTransactionFailedAsync( - compliantForwarderInstance.executeTransaction.sendTransactionAsync( - signedFillOrderTx.salt, - signedFillOrderTx.signerAddress, - signedFillOrderTx.data, - signedFillOrderTx.signature, + balanceThresholdWrapper.fillOrderAsync( + signedOrderWithBadMakerAddress, + compliantTakerAddress, + {takerAssetFillAmount} ), RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold ); }); it('should revert if taker does not meet the balance threshold', async () => { return expectTransactionFailedAsync( - compliantForwarderInstance.executeTransaction.sendTransactionAsync( - compliantSignedFillOrderTx.salt, - nonCompliantAddress, - compliantSignedFillOrderTx.data, - compliantSignedFillOrderTx.signature, + nonCompliantBalanceThresholdWrapper.fillOrderAsync( + compliantSignedOrder, + nonCompliantAddress, + {takerAssetFillAmount} ), RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold ); -- cgit From a655f4b193d032cea7654f982b2f012220a0e77f Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Fri, 7 Dec 2018 14:54:24 -0800 Subject: tests for fillOrder variants --- .../test/extensions/balance_threshold_filter.ts | 155 +++++++++++++++++---- 1 file changed, 131 insertions(+), 24 deletions(-) (limited to 'packages') diff --git a/packages/contracts/test/extensions/balance_threshold_filter.ts b/packages/contracts/test/extensions/balance_threshold_filter.ts index cfa1dcbdf..778aee1a1 100644 --- a/packages/contracts/test/extensions/balance_threshold_filter.ts +++ b/packages/contracts/test/extensions/balance_threshold_filter.ts @@ -298,6 +298,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { describe('batchFillOrders', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); + compliantSignedOrder = await orderFactory.newSignedOrderAsync(); }); it('should transfer the correct amounts when maker and taker are compliant', async () => { let order2 = _.cloneDeep(compliantSignedOrder); @@ -333,6 +334,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { describe.only('fillOrder', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); + compliantSignedOrder = await orderFactory.newSignedOrderAsync(); }); it('should transfer the correct amounts and validate both maker/taker when both maker and taker meet the balance threshold', async () => { // Execute a valid fill @@ -401,12 +403,49 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); }); - describe('fillOrderNoThrow', () => { + describe.only('fillOrderNoThrow', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); + compliantSignedOrder = await orderFactory.newSignedOrderAsync(); }); it('should transfer the correct amounts and validate both maker/taker when both maker and taker meet the balance threshold', async () => { - + // Execute a valid fill + const txReceipt = await balanceThresholdWrapper.fillOrderNoThrowAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount}); + // Assert validated addresses + const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedFillOrderTx.signerAddress]; + assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); + // Check balances + const newBalances = await erc20Wrapper.getBalancesAsync(); + const makerAssetFillAmount = takerAssetFillAmount + .times(compliantSignedOrder.makerAssetAmount) + .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); + const makerFeePaid = compliantSignedOrder.makerFee + .times(makerAssetFillAmount) + .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); + const takerFeePaid = compliantSignedOrder.takerFee + .times(makerAssetFillAmount) + .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); + expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), + ); + expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount), + ); + expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount), + ); + expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), + ); + expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)), + ); }); it('should revert if maker does not meet the balance threshold', async () => { // Create signed order with non-compliant maker address @@ -414,42 +453,110 @@ describe.only(ContractName.BalanceThresholdFilter, () => { senderAddress: compliantForwarderInstance.address, makerAddress: nonCompliantAddress }); - const signedOrderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress( - signedOrderWithBadMakerAddress, + // Execute transaction + return expectTransactionFailedAsync( + balanceThresholdWrapper.fillOrderNoThrowAsync( + signedOrderWithBadMakerAddress, + compliantTakerAddress, + {takerAssetFillAmount} + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold ); - const signedOrderWithoutExchangeAddressData = exchangeInstance.fillOrder.getABIEncodedTransactionData( - signedOrderWithoutExchangeAddress, - takerAssetFillAmount, - compliantSignedOrder.signature, + }); + it('should revert if taker does not meet the balance threshold', async () => { + return expectTransactionFailedAsync( + nonCompliantBalanceThresholdWrapper.fillOrderNoThrowAsync( + compliantSignedOrder, + nonCompliantAddress, + {takerAssetFillAmount} + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold ); - const signedFillOrderTx = takerTransactionFactory.newSignedTransaction( - signedOrderWithoutExchangeAddressData, + }); + }); + + describe.only('fillOrKillOrder', () => { + beforeEach(async () => { + erc20Balances = await erc20Wrapper.getBalancesAsync(); + compliantSignedOrder = await orderFactory.newSignedOrderAsync(); + }); + it('should transfer the correct amounts and validate both maker/taker when both maker and taker meet the balance threshold', async () => { + // Execute a valid fill + const takerAssetFillAmount_ = compliantSignedOrder.takerAssetAmount; + const txReceipt = await balanceThresholdWrapper.fillOrKillOrderAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount: takerAssetFillAmount_}); + // Assert validated addresses + const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedFillOrderTx.signerAddress]; + assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); + // Check balances + const newBalances = await erc20Wrapper.getBalancesAsync(); + const makerAssetFillAmount = takerAssetFillAmount_ + .times(compliantSignedOrder.makerAssetAmount) + .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); + const makerFeePaid = compliantSignedOrder.makerFee + .times(makerAssetFillAmount) + .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); + const takerFeePaid = compliantSignedOrder.takerFee + .times(makerAssetFillAmount) + .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); + expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), ); - // Call compliant forwarder + expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount_), + ); + expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), + ); + expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount_), + ); + expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount), + ); + expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), + ); + expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)), + ); + }); + it('should revert if maker does not meet the balance threshold', async () => { + // Create signed order with non-compliant maker address + const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ + senderAddress: compliantForwarderInstance.address, + makerAddress: nonCompliantAddress + }); + // Execute transaction return expectTransactionFailedAsync( - compliantForwarderInstance.executeTransaction.sendTransactionAsync( - signedFillOrderTx.salt, - signedFillOrderTx.signerAddress, - signedFillOrderTx.data, - signedFillOrderTx.signature, + balanceThresholdWrapper.fillOrKillOrderAsync( + signedOrderWithBadMakerAddress, + compliantTakerAddress, + {takerAssetFillAmount} ), RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold ); }); it('should revert if taker does not meet the balance threshold', async () => { return expectTransactionFailedAsync( - compliantForwarderInstance.executeTransaction.sendTransactionAsync( - compliantSignedFillOrderTx.salt, - nonCompliantAddress, - compliantSignedFillOrderTx.data, - compliantSignedFillOrderTx.signature, + nonCompliantBalanceThresholdWrapper.fillOrKillOrderAsync( + compliantSignedOrder, + nonCompliantAddress, + {takerAssetFillAmount} ), RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold ); }); - }); - - describe('fillOrKillOrder', () => { + it('should revert if order is not fully filled', async () => { + const tooBigTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.times(2); + return expectTransactionFailedAsync( + balanceThresholdWrapper.fillOrKillOrderAsync( + compliantSignedOrder, + compliantTakerAddress, + {takerAssetFillAmount: tooBigTakerAssetFillAmount} + ), + RevertReason.FailedExecution + ); + }); }); describe('marketBuyOrders', () => { -- cgit From 4b0d01ad72b93e8ccb01ca2eabe521cdf78eeea2 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Fri, 7 Dec 2018 15:35:49 -0800 Subject: tests for batchFillOrdes --- .../test/extensions/balance_threshold_filter.ts | 326 ++++++++++++++++++--- 1 file changed, 287 insertions(+), 39 deletions(-) (limited to 'packages') diff --git a/packages/contracts/test/extensions/balance_threshold_filter.ts b/packages/contracts/test/extensions/balance_threshold_filter.ts index 778aee1a1..c91d4880e 100644 --- a/packages/contracts/test/extensions/balance_threshold_filter.ts +++ b/packages/contracts/test/extensions/balance_threshold_filter.ts @@ -45,6 +45,7 @@ interface ValidatedAddressesLog { describe.only(ContractName.BalanceThresholdFilter, () => { let compliantMakerAddress: string; + let compliantMakerAddress2: string; let owner: string; let compliantTakerAddress: string; let feeRecipientAddress: string; @@ -57,6 +58,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { let exchangeWrapper: ExchangeWrapper; let orderFactory: OrderFactory; + let orderFactory2: OrderFactory; let erc20Wrapper: ERC20Wrapper; let erc20Balances: ERC20BalancesByOwner; let balanceThresholdWrapper: BalanceThresholdWrapper; @@ -64,6 +66,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { let takerTransactionFactory: TransactionFactory; let compliantSignedOrder: SignedOrder; + let compliantSignedOrder2: SignedOrder; let compliantSignedFillOrderTx: SignedTransaction; let logDecoder: LogDecoder; @@ -95,6 +98,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { const usedAddresses = ([ owner, compliantMakerAddress, + compliantMakerAddress2, compliantTakerAddress, feeRecipientAddress, nonCompliantAddress, @@ -134,10 +138,19 @@ describe.only(ContractName.BalanceThresholdFilter, () => { await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(exchangeInstance.address, { from: owner, }); + // Deploy Compliant Forwarder + const erc721BalanceThreshold = new BigNumber(1); + compliantForwarderInstance = await BalanceThresholdFilterContract.deployFrom0xArtifactAsync( + artifacts.BalanceThresholdFilter, + provider, + txDefaults, + exchangeInstance.address, + yesTokenInstance.address, + erc721BalanceThreshold + ); // Default order parameters const defaultOrderParams = { exchangeAddress: exchangeInstance.address, - makerAddress: compliantMakerAddress, feeRecipientAddress, makerAssetData: assetDataUtils.encodeERC20AssetData(defaultMakerAssetAddress), takerAssetData: assetDataUtils.encodeERC20AssetData(defaultTakerAssetAddress), @@ -145,19 +158,22 @@ describe.only(ContractName.BalanceThresholdFilter, () => { takerAssetAmount, makerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), DECIMALS_DEFAULT), takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(150), DECIMALS_DEFAULT), + senderAddress: compliantForwarderInstance.address, }; - const privateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantMakerAddress)]; - orderFactory = new OrderFactory(privateKey, defaultOrderParams); - // Deploy Compliant Forwarder - const erc721BalanceThreshold = new BigNumber(1); - compliantForwarderInstance = await BalanceThresholdFilterContract.deployFrom0xArtifactAsync( - artifacts.BalanceThresholdFilter, - provider, - txDefaults, - exchangeInstance.address, - yesTokenInstance.address, - erc721BalanceThreshold - ); + const defaultOrderParams1 = { + makerAddress: compliantMakerAddress, + ... + defaultOrderParams, + } + const makerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantMakerAddress)]; + orderFactory = new OrderFactory(makerPrivateKey, defaultOrderParams1); + const defaultOrderParams2 = { + makerAddress: compliantMakerAddress2, + ... + defaultOrderParams, + } + const secondMakerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantMakerAddress2)]; + orderFactory2 = new OrderFactory(secondMakerPrivateKey, defaultOrderParams2); /* const compliantForwarderContract = new BalanceThresholdFilterContract( compliantForwarderInstance.abi, @@ -195,6 +211,14 @@ describe.only(ContractName.BalanceThresholdFilter, () => { [compliantTakerYesMark], { from: owner }, ); + await yesTokenInstance.mint2.sendTransactionAsync( + compliantMakerAddress2, + compliantTakerEntityId, + addressesCanControlTheirToken, + compliantTakerCountryCode, + [compliantTakerYesMark], + { from: owner }, + ); // Create Valid/Invalid orders const takerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantTakerAddress)]; takerTransactionFactory = new TransactionFactory(takerPrivateKey, exchangeInstance.address); @@ -293,45 +317,269 @@ describe.only(ContractName.BalanceThresholdFilter, () => { // @TODO - greater than 1 balance }); + describe('batchFillOrdersNoThrow', () => { + }); + + describe('batchFillOrKillOrders', () => { + }); + describe('batchFillOrKillOrders', () => { + }); - describe('batchFillOrders', () => { + describe.only('batchFillOrders', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); compliantSignedOrder = await orderFactory.newSignedOrderAsync(); + compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); }); - it('should transfer the correct amounts when maker and taker are compliant', async () => { - let order2 = _.cloneDeep(compliantSignedOrder); - order2.makerAddress = `0x${_.reverse(compliantSignedOrder.makerAddress.slice(2).split('')).join('')}`; - const orders = [compliantSignedOrder, order2]; - const fillAmounts = [new BigNumber(4), new BigNumber(4)]; - const signatures = ["0xabcd", "0xabcd"]; - const exchangeCalldata = exchangeInstance.batchFillOrders.getABIEncodedTransactionData(orders, fillAmounts, signatures); - console.log('*'.repeat(40), exchangeCalldata, '*'.repeat(40)); - console.log('****** MAKER ADDRESS = ', compliantSignedOrder.makerAddress); - - const txHash = await compliantForwarderInstance.executeTransaction.sendTransactionAsync( - compliantSignedFillOrderTx.salt, - compliantSignedFillOrderTx.signerAddress, - exchangeCalldata, - compliantSignedFillOrderTx.signature, + it('should transfer the correct amounts and validate both makers/taker when both maker and taker meet the balance threshold', async () => { + // Execute a valid fill + const orders = [compliantSignedOrder, compliantSignedOrder2]; + const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; + const txReceipt = await balanceThresholdWrapper.batchFillOrdersAsync(orders, compliantTakerAddress, {takerAssetFillAmounts}); + // Assert validated addresses + const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; + assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); + // Check balances + const newBalances = await erc20Wrapper.getBalancesAsync(); + const cumulativeTakerAssetFillAmount = takerAssetFillAmount.times(2); + const makerAssetFillAmount = takerAssetFillAmount + .times(compliantSignedOrder.makerAssetAmount) + .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); + const makerFeePaid = compliantSignedOrder.makerFee + .times(makerAssetFillAmount) + .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); + const takerFeePaid = compliantSignedOrder.takerFee + .times(makerAssetFillAmount) + .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount) + .times(2); + // Maker #1 + expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), + ); + // Maker #2 + expect(newBalances[compliantMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress2][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress2][zrxToken.address].minus(makerFeePaid), + ); + // Taker + expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount), + ); + + expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount.times(2)), + ); + expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), + ); + // Fee recipient + expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.times(2).add(takerFeePaid)), + ); + }); + it('should revert if one maker does not meet the balance threshold', async () => { + // Create order set with one non-compliant maker address + const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; + const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ + makerAddress: nonCompliantAddress + }); + const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; + // Execute transaction + return expectTransactionFailedAsync( + balanceThresholdWrapper.batchFillOrdersAsync( + orders, + compliantTakerAddress, + {takerAssetFillAmounts} + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); + }); + it('should revert if taker does not meet the balance threshold', async () => { + const orders = [compliantSignedOrder, compliantSignedOrder2]; + const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; + return expectTransactionFailedAsync( + nonCompliantBalanceThresholdWrapper.batchFillOrdersAsync( + orders, + nonCompliantAddress, + {takerAssetFillAmounts} + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold ); - const decoder = new LogDecoder(web3Wrapper); - const tx = await decoder.getTxWithDecodedLogsAsync(txHash); - console.log(JSON.stringify(tx, null, 4)); }); }); - describe('batchFillOrdersNoThrow', () => { + /* + describe.only('fillOrderNoThrow', () => { + beforeEach(async () => { + erc20Balances = await erc20Wrapper.getBalancesAsync(); + compliantSignedOrder = await orderFactory.newSignedOrderAsync(); + }); + it('should transfer the correct amounts and validate both maker/taker when both maker and taker meet the balance threshold', async () => { + // Execute a valid fill + const txReceipt = await balanceThresholdWrapper.fillOrderNoThrowAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount}); + // Assert validated addresses + const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedFillOrderTx.signerAddress]; + assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); + // Check balances + const newBalances = await erc20Wrapper.getBalancesAsync(); + const makerAssetFillAmount = takerAssetFillAmount + .times(compliantSignedOrder.makerAssetAmount) + .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); + const makerFeePaid = compliantSignedOrder.makerFee + .times(makerAssetFillAmount) + .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); + const takerFeePaid = compliantSignedOrder.takerFee + .times(makerAssetFillAmount) + .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); + expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), + ); + expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount), + ); + expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount), + ); + expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), + ); + expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)), + ); + }); + it('should revert if maker does not meet the balance threshold', async () => { + // Create signed order with non-compliant maker address + const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ + senderAddress: compliantForwarderInstance.address, + makerAddress: nonCompliantAddress + }); + // Execute transaction + return expectTransactionFailedAsync( + balanceThresholdWrapper.fillOrderNoThrowAsync( + signedOrderWithBadMakerAddress, + compliantTakerAddress, + {takerAssetFillAmount} + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); + }); + it('should revert if taker does not meet the balance threshold', async () => { + return expectTransactionFailedAsync( + nonCompliantBalanceThresholdWrapper.fillOrderNoThrowAsync( + compliantSignedOrder, + nonCompliantAddress, + {takerAssetFillAmount} + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); + }); }); - describe('batchFillOrKillOrders', () => { + describe.only('fillOrKillOrder', () => { + beforeEach(async () => { + erc20Balances = await erc20Wrapper.getBalancesAsync(); + compliantSignedOrder = await orderFactory.newSignedOrderAsync(); + }); + it('should transfer the correct amounts and validate both maker/taker when both maker and taker meet the balance threshold', async () => { + // Execute a valid fill + const takerAssetFillAmount_ = compliantSignedOrder.takerAssetAmount; + const txReceipt = await balanceThresholdWrapper.fillOrKillOrderAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount: takerAssetFillAmount_}); + // Assert validated addresses + const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedFillOrderTx.signerAddress]; + assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); + // Check balances + const newBalances = await erc20Wrapper.getBalancesAsync(); + const makerAssetFillAmount = takerAssetFillAmount_ + .times(compliantSignedOrder.makerAssetAmount) + .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); + const makerFeePaid = compliantSignedOrder.makerFee + .times(makerAssetFillAmount) + .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); + const takerFeePaid = compliantSignedOrder.takerFee + .times(makerAssetFillAmount) + .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); + expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount_), + ); + expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), + ); + expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount_), + ); + expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount), + ); + expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), + ); + expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)), + ); + }); + it('should revert if maker does not meet the balance threshold', async () => { + // Create signed order with non-compliant maker address + const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ + senderAddress: compliantForwarderInstance.address, + makerAddress: nonCompliantAddress + }); + // Execute transaction + return expectTransactionFailedAsync( + balanceThresholdWrapper.fillOrKillOrderAsync( + signedOrderWithBadMakerAddress, + compliantTakerAddress, + {takerAssetFillAmount} + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); + }); + it('should revert if taker does not meet the balance threshold', async () => { + return expectTransactionFailedAsync( + nonCompliantBalanceThresholdWrapper.fillOrKillOrderAsync( + compliantSignedOrder, + nonCompliantAddress, + {takerAssetFillAmount} + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); + }); + it('should revert if order is not fully filled', async () => { + const tooBigTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.times(2); + return expectTransactionFailedAsync( + balanceThresholdWrapper.fillOrKillOrderAsync( + compliantSignedOrder, + compliantTakerAddress, + {takerAssetFillAmount: tooBigTakerAssetFillAmount} + ), + RevertReason.FailedExecution + ); + }); }); - describe('batchFillOrKillOrders', () => { - }); + */ - describe.only('fillOrder', () => { + describe('fillOrder', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); compliantSignedOrder = await orderFactory.newSignedOrderAsync(); @@ -403,7 +651,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); }); - describe.only('fillOrderNoThrow', () => { + describe('fillOrderNoThrow', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); compliantSignedOrder = await orderFactory.newSignedOrderAsync(); @@ -475,7 +723,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); }); - describe.only('fillOrKillOrder', () => { + describe('fillOrKillOrder', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); compliantSignedOrder = await orderFactory.newSignedOrderAsync(); -- cgit From 51355209a2a43a1a7b5a72c78aabefcdac5ede33 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Fri, 7 Dec 2018 15:59:15 -0800 Subject: Tests for batchFill variants --- .../test/extensions/balance_threshold_filter.ts | 214 +++++++++++++++++++-- 1 file changed, 203 insertions(+), 11 deletions(-) (limited to 'packages') diff --git a/packages/contracts/test/extensions/balance_threshold_filter.ts b/packages/contracts/test/extensions/balance_threshold_filter.ts index c91d4880e..003bc2cdc 100644 --- a/packages/contracts/test/extensions/balance_threshold_filter.ts +++ b/packages/contracts/test/extensions/balance_threshold_filter.ts @@ -317,16 +317,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { // @TODO - greater than 1 balance }); - describe('batchFillOrdersNoThrow', () => { - }); - - describe('batchFillOrKillOrders', () => { - }); - - describe('batchFillOrKillOrders', () => { - }); - - describe.only('batchFillOrders', () => { + describe('batchFillOrders', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); compliantSignedOrder = await orderFactory.newSignedOrderAsync(); @@ -420,6 +411,207 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); }); + describe('batchFillOrdersNoThrow', () => { + beforeEach(async () => { + erc20Balances = await erc20Wrapper.getBalancesAsync(); + compliantSignedOrder = await orderFactory.newSignedOrderAsync(); + compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); + }); + it('should transfer the correct amounts and validate both makers/taker when both maker and taker meet the balance threshold', async () => { + // Execute a valid fill + const orders = [compliantSignedOrder, compliantSignedOrder2]; + const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; + const txReceipt = await balanceThresholdWrapper.batchFillOrdersNoThrowAsync(orders, compliantTakerAddress, {takerAssetFillAmounts}); + // Assert validated addresses + const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; + assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); + // Check balances + const newBalances = await erc20Wrapper.getBalancesAsync(); + const cumulativeTakerAssetFillAmount = takerAssetFillAmount.times(2); + const makerAssetFillAmount = takerAssetFillAmount + .times(compliantSignedOrder.makerAssetAmount) + .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); + const makerFeePaid = compliantSignedOrder.makerFee + .times(makerAssetFillAmount) + .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); + const takerFeePaid = compliantSignedOrder.takerFee + .times(makerAssetFillAmount) + .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount) + .times(2); + // Maker #1 + expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), + ); + // Maker #2 + expect(newBalances[compliantMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress2][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress2][zrxToken.address].minus(makerFeePaid), + ); + // Taker + expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount), + ); + + expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount.times(2)), + ); + expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), + ); + // Fee recipient + expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.times(2).add(takerFeePaid)), + ); + }); + it('should revert if one maker does not meet the balance threshold', async () => { + // Create order set with one non-compliant maker address + const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; + const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ + makerAddress: nonCompliantAddress + }); + const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; + // Execute transaction + return expectTransactionFailedAsync( + balanceThresholdWrapper.batchFillOrdersNoThrowAsync( + orders, + compliantTakerAddress, + {takerAssetFillAmounts} + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); + }); + it('should revert if taker does not meet the balance threshold', async () => { + const orders = [compliantSignedOrder, compliantSignedOrder2]; + const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; + return expectTransactionFailedAsync( + nonCompliantBalanceThresholdWrapper.batchFillOrdersNoThrowAsync( + orders, + nonCompliantAddress, + {takerAssetFillAmounts} + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); + }); + }); + + describe.only('batchFillOrKillOrders', () => { + beforeEach(async () => { + erc20Balances = await erc20Wrapper.getBalancesAsync(); + compliantSignedOrder = await orderFactory.newSignedOrderAsync(); + compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); + }); + it('should transfer the correct amounts and validate both makers/taker when both makers and taker meet the balance threshold', async () => { + // Execute a valid fill + const orders = [compliantSignedOrder, compliantSignedOrder2]; + const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; + const txReceipt = await balanceThresholdWrapper.batchFillOrKillOrdersAsync(orders, compliantTakerAddress, {takerAssetFillAmounts}); + // Assert validated addresses + const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; + assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); + // Check balances + const newBalances = await erc20Wrapper.getBalancesAsync(); + const cumulativeTakerAssetFillAmount = takerAssetFillAmount.times(2); + const makerAssetFillAmount = takerAssetFillAmount + .times(compliantSignedOrder.makerAssetAmount) + .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); + const makerFeePaid = compliantSignedOrder.makerFee + .times(makerAssetFillAmount) + .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); + const takerFeePaid = compliantSignedOrder.takerFee + .times(makerAssetFillAmount) + .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount) + .times(2); + // Maker #1 + expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), + ); + // Maker #2 + expect(newBalances[compliantMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress2][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress2][zrxToken.address].minus(makerFeePaid), + ); + // Taker + expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount), + ); + + expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount.times(2)), + ); + expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), + ); + // Fee recipient + expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.times(2).add(takerFeePaid)), + ); + }); + it('should revert if one maker does not meet the balance threshold', async () => { + // Create order set with one non-compliant maker address + const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; + const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ + makerAddress: nonCompliantAddress + }); + const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; + // Execute transaction + return expectTransactionFailedAsync( + balanceThresholdWrapper.batchFillOrKillOrdersAsync( + orders, + compliantTakerAddress, + {takerAssetFillAmounts} + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); + }); + it('should revert if taker does not meet the balance threshold', async () => { + const orders = [compliantSignedOrder, compliantSignedOrder2]; + const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; + return expectTransactionFailedAsync( + nonCompliantBalanceThresholdWrapper.batchFillOrKillOrdersAsync( + orders, + nonCompliantAddress, + {takerAssetFillAmounts} + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); + }); + it('should revert if one takerAssetFillAmount is not fully filled', async () => { + const tooBigTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.times(2); + const orders = [compliantSignedOrder, compliantSignedOrder2]; + const takerAssetFillAmounts = [takerAssetFillAmount, tooBigTakerAssetFillAmount]; + return expectTransactionFailedAsync( + balanceThresholdWrapper.batchFillOrKillOrdersAsync( + orders, + compliantTakerAddress, + {takerAssetFillAmounts} + ), + RevertReason.FailedExecution + ); + }); + }); + /* describe.only('fillOrderNoThrow', () => { beforeEach(async () => { @@ -564,7 +756,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold ); }); - it('should revert if order is not fully filled', async () => { + it('should revert if takerAssetFillAmount is not fully filled', async () => { const tooBigTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.times(2); return expectTransactionFailedAsync( balanceThresholdWrapper.fillOrKillOrderAsync( -- cgit From e45a0ffdbfd5289b9e123a4b58c26d6dbbcfd8c7 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Fri, 7 Dec 2018 16:03:39 -0800 Subject: All tests running so far --- .../MixinBalanceThresholdFilterCore.sol | 1 + .../mixins/MBalanceThresholdFilterCore.sol | 1 + .../test/extensions/balance_threshold_filter.ts | 173 +-------------------- 3 files changed, 9 insertions(+), 166 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol b/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol index 9bed7ba47..2e058742b 100644 --- a/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol +++ b/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol @@ -28,6 +28,7 @@ contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { /// the hold at least `BALANCE_THRESHOLD` of the asset `THRESHOLD_ASSET` OR /// the exchange function is a cancellation. /// Supported Exchange functions: + /// - batchFillOrders /// - batchFillOrdersNoThrow /// - batchFillOrKillOrders /// - fillOrder diff --git a/packages/contracts/contracts/extensions/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol b/packages/contracts/contracts/extensions/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol index 046caecdd..612fd481c 100644 --- a/packages/contracts/contracts/extensions/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol +++ b/packages/contracts/contracts/extensions/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol @@ -43,6 +43,7 @@ contract MBalanceThresholdFilterCore { /// the hold at least `BALANCE_THRESHOLD` of the asset `THRESHOLD_ASSET` OR /// the exchange function is a cancellation. /// Supported Exchange functions: + /// - batchFillOrders /// - batchFillOrdersNoThrow /// - batchFillOrKillOrders /// - fillOrder diff --git a/packages/contracts/test/extensions/balance_threshold_filter.ts b/packages/contracts/test/extensions/balance_threshold_filter.ts index 003bc2cdc..efe1ce49c 100644 --- a/packages/contracts/test/extensions/balance_threshold_filter.ts +++ b/packages/contracts/test/extensions/balance_threshold_filter.ts @@ -271,7 +271,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { await blockchainLifecycle.revertAsync(); }); - describe('General Sanity Checks', () => { + describe.only('General Sanity Checks', () => { it('should revert if the signed transaction is not intended for supported', async () => { // Create signed order without the fillOrder function selector const txDataBuf = ethUtil.toBuffer(compliantSignedFillOrderTx.data); @@ -317,7 +317,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { // @TODO - greater than 1 balance }); - describe('batchFillOrders', () => { + describe.only('batchFillOrders', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); compliantSignedOrder = await orderFactory.newSignedOrderAsync(); @@ -411,7 +411,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); }); - describe('batchFillOrdersNoThrow', () => { + describe.only('batchFillOrdersNoThrow', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); compliantSignedOrder = await orderFactory.newSignedOrderAsync(); @@ -612,166 +612,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); }); - /* - describe.only('fillOrderNoThrow', () => { - beforeEach(async () => { - erc20Balances = await erc20Wrapper.getBalancesAsync(); - compliantSignedOrder = await orderFactory.newSignedOrderAsync(); - }); - it('should transfer the correct amounts and validate both maker/taker when both maker and taker meet the balance threshold', async () => { - // Execute a valid fill - const txReceipt = await balanceThresholdWrapper.fillOrderNoThrowAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount}); - // Assert validated addresses - const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedFillOrderTx.signerAddress]; - assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); - // Check balances - const newBalances = await erc20Wrapper.getBalancesAsync(); - const makerAssetFillAmount = takerAssetFillAmount - .times(compliantSignedOrder.makerAssetAmount) - .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); - const makerFeePaid = compliantSignedOrder.makerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); - const takerFeePaid = compliantSignedOrder.takerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); - expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), - ); - expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount), - ); - expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount), - ); - expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), - ); - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)), - ); - }); - it('should revert if maker does not meet the balance threshold', async () => { - // Create signed order with non-compliant maker address - const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - senderAddress: compliantForwarderInstance.address, - makerAddress: nonCompliantAddress - }); - // Execute transaction - return expectTransactionFailedAsync( - balanceThresholdWrapper.fillOrderNoThrowAsync( - signedOrderWithBadMakerAddress, - compliantTakerAddress, - {takerAssetFillAmount} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - it('should revert if taker does not meet the balance threshold', async () => { - return expectTransactionFailedAsync( - nonCompliantBalanceThresholdWrapper.fillOrderNoThrowAsync( - compliantSignedOrder, - nonCompliantAddress, - {takerAssetFillAmount} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - }); - - describe.only('fillOrKillOrder', () => { - beforeEach(async () => { - erc20Balances = await erc20Wrapper.getBalancesAsync(); - compliantSignedOrder = await orderFactory.newSignedOrderAsync(); - }); - it('should transfer the correct amounts and validate both maker/taker when both maker and taker meet the balance threshold', async () => { - // Execute a valid fill - const takerAssetFillAmount_ = compliantSignedOrder.takerAssetAmount; - const txReceipt = await balanceThresholdWrapper.fillOrKillOrderAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount: takerAssetFillAmount_}); - // Assert validated addresses - const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedFillOrderTx.signerAddress]; - assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); - // Check balances - const newBalances = await erc20Wrapper.getBalancesAsync(); - const makerAssetFillAmount = takerAssetFillAmount_ - .times(compliantSignedOrder.makerAssetAmount) - .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); - const makerFeePaid = compliantSignedOrder.makerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); - const takerFeePaid = compliantSignedOrder.takerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); - expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount_), - ); - expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), - ); - expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount_), - ); - expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount), - ); - expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), - ); - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)), - ); - }); - it('should revert if maker does not meet the balance threshold', async () => { - // Create signed order with non-compliant maker address - const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - senderAddress: compliantForwarderInstance.address, - makerAddress: nonCompliantAddress - }); - // Execute transaction - return expectTransactionFailedAsync( - balanceThresholdWrapper.fillOrKillOrderAsync( - signedOrderWithBadMakerAddress, - compliantTakerAddress, - {takerAssetFillAmount} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - it('should revert if taker does not meet the balance threshold', async () => { - return expectTransactionFailedAsync( - nonCompliantBalanceThresholdWrapper.fillOrKillOrderAsync( - compliantSignedOrder, - nonCompliantAddress, - {takerAssetFillAmount} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - it('should revert if takerAssetFillAmount is not fully filled', async () => { - const tooBigTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.times(2); - return expectTransactionFailedAsync( - balanceThresholdWrapper.fillOrKillOrderAsync( - compliantSignedOrder, - compliantTakerAddress, - {takerAssetFillAmount: tooBigTakerAssetFillAmount} - ), - RevertReason.FailedExecution - ); - }); - }); - - */ - - describe('fillOrder', () => { + describe.only('fillOrder', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); compliantSignedOrder = await orderFactory.newSignedOrderAsync(); @@ -843,7 +684,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); }); - describe('fillOrderNoThrow', () => { + describe.only('fillOrderNoThrow', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); compliantSignedOrder = await orderFactory.newSignedOrderAsync(); @@ -915,7 +756,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); }); - describe('fillOrKillOrder', () => { + describe.only('fillOrKillOrder', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); compliantSignedOrder = await orderFactory.newSignedOrderAsync(); @@ -986,7 +827,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold ); }); - it('should revert if order is not fully filled', async () => { + it('should revert if takerAssetFillAmount is not fully filled', async () => { const tooBigTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.times(2); return expectTransactionFailedAsync( balanceThresholdWrapper.fillOrKillOrderAsync( -- cgit From a2df428afbd820b15ea108f721ab88f77a8d6dc1 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Fri, 7 Dec 2018 16:18:20 -0800 Subject: tests for marketSellOrders --- .../test/extensions/balance_threshold_filter.ts | 108 +++++++++++++++++++-- 1 file changed, 99 insertions(+), 9 deletions(-) (limited to 'packages') diff --git a/packages/contracts/test/extensions/balance_threshold_filter.ts b/packages/contracts/test/extensions/balance_threshold_filter.ts index efe1ce49c..4c927f1d1 100644 --- a/packages/contracts/test/extensions/balance_threshold_filter.ts +++ b/packages/contracts/test/extensions/balance_threshold_filter.ts @@ -271,7 +271,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { await blockchainLifecycle.revertAsync(); }); - describe.only('General Sanity Checks', () => { + describe('General Sanity Checks', () => { it('should revert if the signed transaction is not intended for supported', async () => { // Create signed order without the fillOrder function selector const txDataBuf = ethUtil.toBuffer(compliantSignedFillOrderTx.data); @@ -317,7 +317,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { // @TODO - greater than 1 balance }); - describe.only('batchFillOrders', () => { + describe('batchFillOrders', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); compliantSignedOrder = await orderFactory.newSignedOrderAsync(); @@ -411,7 +411,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); }); - describe.only('batchFillOrdersNoThrow', () => { + describe('batchFillOrdersNoThrow', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); compliantSignedOrder = await orderFactory.newSignedOrderAsync(); @@ -505,7 +505,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); }); - describe.only('batchFillOrKillOrders', () => { + describe('batchFillOrKillOrders', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); compliantSignedOrder = await orderFactory.newSignedOrderAsync(); @@ -612,7 +612,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); }); - describe.only('fillOrder', () => { + describe('fillOrder', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); compliantSignedOrder = await orderFactory.newSignedOrderAsync(); @@ -684,7 +684,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); }); - describe.only('fillOrderNoThrow', () => { + describe('fillOrderNoThrow', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); compliantSignedOrder = await orderFactory.newSignedOrderAsync(); @@ -756,7 +756,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); }); - describe.only('fillOrKillOrder', () => { + describe('fillOrKillOrder', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); compliantSignedOrder = await orderFactory.newSignedOrderAsync(); @@ -840,11 +840,101 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); }); - describe('marketBuyOrders', () => { + describe.only('marketSellOrders', () => { + beforeEach(async () => { + erc20Balances = await erc20Wrapper.getBalancesAsync(); + compliantSignedOrder = await orderFactory.newSignedOrderAsync(); + compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); + }); + it('should transfer the correct amounts and validate both makers/taker when both makers and taker meet the balance threshold', async () => { + // Execute a valid fill + const orders = [compliantSignedOrder, compliantSignedOrder2]; + const cumulativeTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.plus(takerAssetFillAmount); + const txReceipt = await balanceThresholdWrapper.marketSellOrdersAsync(orders, compliantTakerAddress, {takerAssetFillAmount: cumulativeTakerAssetFillAmount}); + // Assert validated addresses + const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; + assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); + // Check balances + const newBalances = await erc20Wrapper.getBalancesAsync(); + const makerAssetFillAmount2 = takerAssetFillAmount + .times(compliantSignedOrder.makerAssetAmount) + .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); + const makerFeePaid2 = compliantSignedOrder2.makerFee + .times(makerAssetFillAmount2) + .dividedToIntegerBy(compliantSignedOrder2.makerAssetAmount); + const takerFeePaid2 = compliantSignedOrder2.takerFee + .times(makerAssetFillAmount2) + .dividedToIntegerBy(compliantSignedOrder2.makerAssetAmount); + const takerFeePaid = compliantSignedOrder.takerFee.plus(takerFeePaid2); + const cumulativeMakerAssetFillAmount = compliantSignedOrder.makerAssetAmount.plus(makerAssetFillAmount2); + // Maker #1 + expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(compliantSignedOrder.makerAssetAmount), + ); + expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(compliantSignedOrder.takerAssetAmount), + ); + expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][zrxToken.address].minus(compliantSignedOrder.makerFee), + ); + // Maker #2 + expect(newBalances[compliantMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount2), + ); + expect(newBalances[compliantMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress2][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress2][zrxToken.address].minus(makerFeePaid2), + ); + // Taker + expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount), + ); + expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(cumulativeMakerAssetFillAmount), + ); + expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), + ); + // Fee recipient + expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[feeRecipientAddress][zrxToken.address].add(compliantSignedOrder.makerFee).add(makerFeePaid2).add(takerFeePaid), + ); + }); + it('should revert if one maker does not meet the balance threshold', async () => { + // Create order set with one non-compliant maker address + const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ + makerAddress: nonCompliantAddress + }); + const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; + // Execute transaction + return expectTransactionFailedAsync( + balanceThresholdWrapper.marketSellOrdersAsync( + orders, + compliantTakerAddress, + {takerAssetFillAmount} + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); + }); + it('should revert if taker does not meet the balance threshold', async () => { + const orders = [compliantSignedOrder, compliantSignedOrder2]; + return expectTransactionFailedAsync( + nonCompliantBalanceThresholdWrapper.marketSellOrdersAsync( + orders, + nonCompliantAddress, + {takerAssetFillAmount} + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); + }); }); - describe('marketBuyOrdersNoThrow', () => { + describe.only('marketBuyOrdersNoThrow', () => { + }); + describe('marketSellOrders', () => { }); -- cgit From 4f977aa51d6a5baa4cb602cbc1d71b1481aaaa3d Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Fri, 7 Dec 2018 16:18:59 -0800 Subject: marketSellNoThrow tests --- .../test/extensions/balance_threshold_filter.ts | 91 ++++++++++++++++++++++ 1 file changed, 91 insertions(+) (limited to 'packages') diff --git a/packages/contracts/test/extensions/balance_threshold_filter.ts b/packages/contracts/test/extensions/balance_threshold_filter.ts index 4c927f1d1..776712088 100644 --- a/packages/contracts/test/extensions/balance_threshold_filter.ts +++ b/packages/contracts/test/extensions/balance_threshold_filter.ts @@ -931,6 +931,97 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); }); + describe.only('marketSellOrdersNoThrow', () => { + beforeEach(async () => { + erc20Balances = await erc20Wrapper.getBalancesAsync(); + compliantSignedOrder = await orderFactory.newSignedOrderAsync(); + compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); + }); + it('should transfer the correct amounts and validate both makers/taker when both makers and taker meet the balance threshold', async () => { + // Execute a valid fill + const orders = [compliantSignedOrder, compliantSignedOrder2]; + const cumulativeTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.plus(takerAssetFillAmount); + const txReceipt = await balanceThresholdWrapper.marketSellOrdersNoThrowAsync(orders, compliantTakerAddress, {takerAssetFillAmount: cumulativeTakerAssetFillAmount}); + // Assert validated addresses + const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; + assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); + // Check balances + const newBalances = await erc20Wrapper.getBalancesAsync(); + const makerAssetFillAmount2 = takerAssetFillAmount + .times(compliantSignedOrder.makerAssetAmount) + .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); + const makerFeePaid2 = compliantSignedOrder2.makerFee + .times(makerAssetFillAmount2) + .dividedToIntegerBy(compliantSignedOrder2.makerAssetAmount); + const takerFeePaid2 = compliantSignedOrder2.takerFee + .times(makerAssetFillAmount2) + .dividedToIntegerBy(compliantSignedOrder2.makerAssetAmount); + const takerFeePaid = compliantSignedOrder.takerFee.plus(takerFeePaid2); + const cumulativeMakerAssetFillAmount = compliantSignedOrder.makerAssetAmount.plus(makerAssetFillAmount2); + // Maker #1 + expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(compliantSignedOrder.makerAssetAmount), + ); + expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(compliantSignedOrder.takerAssetAmount), + ); + expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][zrxToken.address].minus(compliantSignedOrder.makerFee), + ); + // Maker #2 + expect(newBalances[compliantMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount2), + ); + expect(newBalances[compliantMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress2][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress2][zrxToken.address].minus(makerFeePaid2), + ); + // Taker + expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount), + ); + expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(cumulativeMakerAssetFillAmount), + ); + expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), + ); + // Fee recipient + expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[feeRecipientAddress][zrxToken.address].add(compliantSignedOrder.makerFee).add(makerFeePaid2).add(takerFeePaid), + ); + }); + it('should revert if one maker does not meet the balance threshold', async () => { + // Create order set with one non-compliant maker address + const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ + makerAddress: nonCompliantAddress + }); + const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; + // Execute transaction + return expectTransactionFailedAsync( + balanceThresholdWrapper.marketSellOrdersNoThrowAsync( + orders, + compliantTakerAddress, + {takerAssetFillAmount} + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); + }); + it('should revert if taker does not meet the balance threshold', async () => { + const orders = [compliantSignedOrder, compliantSignedOrder2]; + return expectTransactionFailedAsync( + nonCompliantBalanceThresholdWrapper.marketSellOrdersNoThrowAsync( + orders, + nonCompliantAddress, + {takerAssetFillAmount} + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); + }); + }); + describe.only('marketBuyOrdersNoThrow', () => { }); -- cgit From 3ad72d96f416fdd2446681c41f0151986c0a22b8 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Fri, 7 Dec 2018 16:28:01 -0800 Subject: Tests for marketBuy invariants --- .../test/extensions/balance_threshold_filter.ts | 195 +++++++++++++++++++-- 1 file changed, 185 insertions(+), 10 deletions(-) (limited to 'packages') diff --git a/packages/contracts/test/extensions/balance_threshold_filter.ts b/packages/contracts/test/extensions/balance_threshold_filter.ts index 776712088..d12c28235 100644 --- a/packages/contracts/test/extensions/balance_threshold_filter.ts +++ b/packages/contracts/test/extensions/balance_threshold_filter.ts @@ -840,7 +840,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); }); - describe.only('marketSellOrders', () => { + describe('marketSellOrders', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); compliantSignedOrder = await orderFactory.newSignedOrderAsync(); @@ -931,7 +931,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); }); - describe.only('marketSellOrdersNoThrow', () => { + describe('marketSellOrdersNoThrow', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); compliantSignedOrder = await orderFactory.newSignedOrderAsync(); @@ -1022,17 +1022,192 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); }); - describe.only('marketBuyOrdersNoThrow', () => { - - }); - - - describe('marketSellOrders', () => { + describe.only('marketBuyOrders', () => { + beforeEach(async () => { + erc20Balances = await erc20Wrapper.getBalancesAsync(); + compliantSignedOrder = await orderFactory.newSignedOrderAsync(); + compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); + }); + it('should transfer the correct amounts and validate both makers/taker when both makers and taker meet the balance threshold', async () => { + // Execute a valid fill + const orders = [compliantSignedOrder, compliantSignedOrder2]; + const cumulativeTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.plus(takerAssetFillAmount); + const makerAssetFillAmount2 = takerAssetFillAmount + .times(compliantSignedOrder.makerAssetAmount) + .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); + const cumulativeMakerAssetFillAmount = compliantSignedOrder.makerAssetAmount.plus(makerAssetFillAmount2); + const txReceipt = await balanceThresholdWrapper.marketBuyOrdersAsync(orders, compliantTakerAddress, {makerAssetFillAmount: cumulativeMakerAssetFillAmount}); + // Assert validated addresses + const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; + assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); + // Check balances + const newBalances = await erc20Wrapper.getBalancesAsync(); + const makerFeePaid2 = compliantSignedOrder2.makerFee + .times(makerAssetFillAmount2) + .dividedToIntegerBy(compliantSignedOrder2.makerAssetAmount); + const takerFeePaid2 = compliantSignedOrder2.takerFee + .times(makerAssetFillAmount2) + .dividedToIntegerBy(compliantSignedOrder2.makerAssetAmount); + const takerFeePaid = compliantSignedOrder.takerFee.plus(takerFeePaid2); + // Maker #1 + expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(compliantSignedOrder.makerAssetAmount), + ); + expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(compliantSignedOrder.takerAssetAmount), + ); + expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][zrxToken.address].minus(compliantSignedOrder.makerFee), + ); + // Maker #2 + expect(newBalances[compliantMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount2), + ); + expect(newBalances[compliantMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress2][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress2][zrxToken.address].minus(makerFeePaid2), + ); + // Taker + expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount), + ); + expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(cumulativeMakerAssetFillAmount), + ); + expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), + ); + // Fee recipient + expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[feeRecipientAddress][zrxToken.address].add(compliantSignedOrder.makerFee).add(makerFeePaid2).add(takerFeePaid), + ); + }); + it('should revert if one maker does not meet the balance threshold', async () => { + // Create order set with one non-compliant maker address + const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ + makerAddress: nonCompliantAddress + }); + const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; + // Execute transaction + const dummyMakerAssetFillAmount = new BigNumber(0); + return expectTransactionFailedAsync( + balanceThresholdWrapper.marketBuyOrdersAsync( + orders, + compliantTakerAddress, + {makerAssetFillAmount: dummyMakerAssetFillAmount} + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); + }); + it('should revert if taker does not meet the balance threshold', async () => { + const orders = [compliantSignedOrder, compliantSignedOrder2]; + const dummyMakerAssetFillAmount = new BigNumber(0); + return expectTransactionFailedAsync( + nonCompliantBalanceThresholdWrapper.marketBuyOrdersAsync( + orders, + nonCompliantAddress, + {makerAssetFillAmount: dummyMakerAssetFillAmount} + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); + }); }); - describe('marketSellOrdersNoThrow', () => { + describe.only('marketBuyOrdersNoThrowAsync', () => { + beforeEach(async () => { + erc20Balances = await erc20Wrapper.getBalancesAsync(); + compliantSignedOrder = await orderFactory.newSignedOrderAsync(); + compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); + }); + it('should transfer the correct amounts and validate both makers/taker when both makers and taker meet the balance threshold', async () => { + // Execute a valid fill + const orders = [compliantSignedOrder, compliantSignedOrder2]; + const cumulativeTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.plus(takerAssetFillAmount); + const makerAssetFillAmount2 = takerAssetFillAmount + .times(compliantSignedOrder.makerAssetAmount) + .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); + const cumulativeMakerAssetFillAmount = compliantSignedOrder.makerAssetAmount.plus(makerAssetFillAmount2); + const txReceipt = await balanceThresholdWrapper.marketBuyOrdersNoThrowAsync(orders, compliantTakerAddress, {makerAssetFillAmount: cumulativeMakerAssetFillAmount}); + // Assert validated addresses + const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; + assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); + // Check balances + const newBalances = await erc20Wrapper.getBalancesAsync(); + const makerFeePaid2 = compliantSignedOrder2.makerFee + .times(makerAssetFillAmount2) + .dividedToIntegerBy(compliantSignedOrder2.makerAssetAmount); + const takerFeePaid2 = compliantSignedOrder2.takerFee + .times(makerAssetFillAmount2) + .dividedToIntegerBy(compliantSignedOrder2.makerAssetAmount); + const takerFeePaid = compliantSignedOrder.takerFee.plus(takerFeePaid2); + // Maker #1 + expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(compliantSignedOrder.makerAssetAmount), + ); + expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(compliantSignedOrder.takerAssetAmount), + ); + expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][zrxToken.address].minus(compliantSignedOrder.makerFee), + ); + // Maker #2 + expect(newBalances[compliantMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount2), + ); + expect(newBalances[compliantMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress2][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress2][zrxToken.address].minus(makerFeePaid2), + ); + // Taker + expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount), + ); + expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(cumulativeMakerAssetFillAmount), + ); + expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), + ); + // Fee recipient + expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[feeRecipientAddress][zrxToken.address].add(compliantSignedOrder.makerFee).add(makerFeePaid2).add(takerFeePaid), + ); + }); + it('should revert if one maker does not meet the balance threshold', async () => { + // Create order set with one non-compliant maker address + const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ + makerAddress: nonCompliantAddress + }); + const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; + // Execute transaction + const dummyMakerAssetFillAmount = new BigNumber(0); + return expectTransactionFailedAsync( + balanceThresholdWrapper.marketBuyOrdersNoThrowAsync( + orders, + compliantTakerAddress, + {makerAssetFillAmount: dummyMakerAssetFillAmount} + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); + }); + it('should revert if taker does not meet the balance threshold', async () => { + const orders = [compliantSignedOrder, compliantSignedOrder2]; + const dummyMakerAssetFillAmount = new BigNumber(0); + return expectTransactionFailedAsync( + nonCompliantBalanceThresholdWrapper.marketBuyOrdersNoThrowAsync( + orders, + nonCompliantAddress, + {makerAssetFillAmount: dummyMakerAssetFillAmount} + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); + }); }); - + describe('matchOrders', () => { }); -- cgit From 93b9c251ed735905d30a0daa4d90fc27c8625aa6 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Fri, 7 Dec 2018 17:54:27 -0800 Subject: Tests for MatchOrders --- .../MixinBalanceThresholdFilterCore.sol | 8 +- .../test/extensions/balance_threshold_filter.ts | 106 ++++++++++++++++++++- .../test/utils/balance_threshold_wrapper.ts | 4 + 3 files changed, 111 insertions(+), 7 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol b/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol index 2e058742b..303e1d9c2 100644 --- a/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol +++ b/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol @@ -133,7 +133,7 @@ contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { /// the running list of addresses to validate. /// @param orderParamIndex - Index of the order in the Exchange function's signature function recordMakerAddressFromOrder(orderParamIndex) { - let orderPtr := loadExchangeData(orderParamIndex) + let orderPtr := loadExchangeData(mul(orderParamIndex, 0x20)) let makerAddress := loadExchangeData(orderPtr) recordAddressToValidate(makerAddress) } @@ -143,7 +143,7 @@ contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { /// the running list of addresses to validate. /// @param orderArrayParamIndex - Index of the order array in the Exchange function's signature function recordMakerAddressesFromOrderArray(orderArrayParamIndex) { - let orderArrayPtr := loadExchangeData(orderArrayParamIndex) + let orderArrayPtr := loadExchangeData(mul(orderArrayParamIndex, 0x20)) let orderArrayLength := loadExchangeData(orderArrayPtr) let orderArrayElementPtr := add(orderArrayPtr, 0x20) let orderArrayElementEndPtr := add(orderArrayElementPtr, mul(orderArrayLength, 0x20)) @@ -258,7 +258,7 @@ contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { // This is to avoid corruption when making calls in the loop below. let freeMemPtr := addressesToValidateElementEndPtr mstore(0x40, freeMemPtr) - +/* // Validate addresses let thresholdAssetAddress := sload(THRESHOLD_ASSET_slot) let thresholdBalance := sload(THRESHOLD_BALANCE_slot) @@ -307,7 +307,7 @@ contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { // 64 -- strlen(AT_LEAST_ONE_ADDRESS_DOES_NOT_MEET_BALANCE_THRESHOLD) rounded up to nearest 32-byte word. revert(0, 132) } - } + }*/ // Record validated addresses validatedAddresses := addressesToValidate diff --git a/packages/contracts/test/extensions/balance_threshold_filter.ts b/packages/contracts/test/extensions/balance_threshold_filter.ts index d12c28235..7ca8a8e98 100644 --- a/packages/contracts/test/extensions/balance_threshold_filter.ts +++ b/packages/contracts/test/extensions/balance_threshold_filter.ts @@ -22,17 +22,20 @@ import { chaiSetup } from '../utils/chai_setup'; import { constants } from '../utils/constants'; import { ERC20Wrapper } from '../utils/erc20_wrapper'; import { ExchangeWrapper } from '../utils/exchange_wrapper'; +import { MatchOrderTester } from '../utils/match_order_tester'; import { OrderFactory } from '../utils/order_factory'; import { orderUtils } from '../utils/order_utils'; import { TransactionFactory } from '../utils/transaction_factory'; import { BalanceThresholdWrapper } from '../utils/balance_threshold_wrapper'; import { ContractName, ERC20BalancesByOwner, SignedTransaction } from '../utils/types'; import { provider, txDefaults, web3Wrapper } from '../utils/web3_wrapper'; +import { TestExchangeInternalsContract } from '../../generated-wrappers/test_exchange_internals'; import { MethodAbi, AbiDefinition } from 'ethereum-types'; import { AbiEncoder } from '@0x/utils'; import { Method } from '@0x/utils/lib/src/abi_encoder'; import { LogDecoder } from '../utils/log_decoder'; +import { ERC721Wrapper } from '../utils/erc721_wrapper'; chaiSetup.configure(); const expect = chai.expect; @@ -70,6 +73,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { let compliantSignedFillOrderTx: SignedTransaction; let logDecoder: LogDecoder; + let exchangeInternals: TestExchangeInternalsContract; const takerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(500), DECIMALS_DEFAULT); const makerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(1000), DECIMALS_DEFAULT); @@ -263,6 +267,13 @@ describe.only(ContractName.BalanceThresholdFilter, () => { balanceThresholdWrapper = new BalanceThresholdWrapper(compliantForwarderInstance, exchangeInstance, new TransactionFactory(takerPrivateKey, exchangeInstance.address), provider); const nonCompliantPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(nonCompliantAddress)]; nonCompliantBalanceThresholdWrapper = new BalanceThresholdWrapper(compliantForwarderInstance, exchangeInstance, new TransactionFactory(nonCompliantPrivateKey, exchangeInstance.address), provider); + + // Instantiate internal exchange contract + exchangeInternals = await TestExchangeInternalsContract.deployFrom0xArtifactAsync( + artifacts.TestExchangeInternals, + provider, + txDefaults, + ); }); beforeEach(async () => { await blockchainLifecycle.startAsync(); @@ -1022,7 +1033,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); }); - describe.only('marketBuyOrders', () => { + describe('marketBuyOrders', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); compliantSignedOrder = await orderFactory.newSignedOrderAsync(); @@ -1115,7 +1126,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); }); - describe.only('marketBuyOrdersNoThrowAsync', () => { + describe('marketBuyOrdersNoThrowAsync', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); compliantSignedOrder = await orderFactory.newSignedOrderAsync(); @@ -1208,7 +1219,96 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); }); - describe('matchOrders', () => { + describe.only('matchOrders', () => { + beforeEach(async () => { + erc20Balances = await erc20Wrapper.getBalancesAsync(); + compliantSignedOrder = await orderFactory.newSignedOrderAsync(); + compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); + }); + it.only('Should transfer correct amounts when both makers and taker meet the balance threshold', async () => { + // Test values/results taken from Match Orders test: + // 'Should transfer correct amounts when right order is fully filled and values pass isRoundingErrorFloor but fail isRoundingErrorCeil' + // Create orders to match + const signedOrderLeft = await orderFactory.newSignedOrderAsync({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(17), 0), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(98), 0), + makerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 18), + takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 18), + feeRecipientAddress: feeRecipientAddress, + }); + const signedOrderRight = await orderFactory2.newSignedOrderAsync({ + makerAssetData: assetDataUtils.encodeERC20AssetData(defaultTakerAssetAddress), + takerAssetData: assetDataUtils.encodeERC20AssetData(defaultMakerAssetAddress), + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(75), 0), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(13), 0), + makerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 18), + takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 18), + feeRecipientAddress: feeRecipientAddress, + }); + // Compute expected transfer amounts + const expectedTransferAmounts = { + // Left Maker + amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(13), 0), + amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(75), 0), + feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber('76.4705882352941176'), 16), // 76.47% + // Right Maker + amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(75), 0), + amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(13), 0), + feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% + // Taker + amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(0), 0), + feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber('76.5306122448979591'), 16), // 76.53% + feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% + }; + const txReceipt = await balanceThresholdWrapper.matchOrdersAsync(signedOrderLeft, signedOrderRight, compliantTakerAddress); + // Assert validated addresses + const expectedValidatedAddresseses = [signedOrderLeft.makerAddress, signedOrderRight.makerAddress, compliantTakerAddress]; + assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); + // Check balances + const newBalances = await erc20Wrapper.getBalancesAsync(); + expect( + newBalances[signedOrderLeft.makerAddress][defaultMakerAssetAddress], + 'Checking left maker egress ERC20 account balance', + ).to.be.bignumber.equal(erc20Balances[signedOrderLeft.makerAddress][defaultMakerAssetAddress].sub(expectedTransferAmounts.amountSoldByLeftMaker)); + expect( + newBalances[signedOrderRight.makerAddress][defaultTakerAssetAddress], + 'Checking right maker ingress ERC20 account balance', + ).to.be.bignumber.equal(erc20Balances[signedOrderRight.makerAddress][defaultTakerAssetAddress].sub(expectedTransferAmounts.amountSoldByRightMaker)); + expect( + newBalances[compliantTakerAddress][defaultMakerAssetAddress], + 'Checking taker ingress ERC20 account balance', + ).to.be.bignumber.equal(erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(expectedTransferAmounts.amountReceivedByTaker)); + expect( + newBalances[signedOrderLeft.makerAddress][defaultTakerAssetAddress], + 'Checking left maker ingress ERC20 account balance', + ).to.be.bignumber.equal(erc20Balances[signedOrderLeft.makerAddress][defaultTakerAssetAddress].add(expectedTransferAmounts.amountBoughtByLeftMaker)); + expect( + newBalances[signedOrderRight.makerAddress][defaultMakerAssetAddress], + 'Checking right maker egress ERC20 account balance', + ).to.be.bignumber.equal( + erc20Balances[signedOrderRight.makerAddress][defaultMakerAssetAddress].add(expectedTransferAmounts.amountBoughtByRightMaker), + ); + // Paid fees + expect( + newBalances[signedOrderLeft.makerAddress][zrxToken.address], + 'Checking left maker egress ERC20 account fees', + ).to.be.bignumber.equal(erc20Balances[signedOrderLeft.makerAddress][zrxToken.address].minus(expectedTransferAmounts.feePaidByLeftMaker)); + expect( + newBalances[signedOrderRight.makerAddress][zrxToken.address], + 'Checking right maker egress ERC20 account fees', + ).to.be.bignumber.equal(erc20Balances[signedOrderRight.makerAddress][zrxToken.address].minus(expectedTransferAmounts.feePaidByRightMaker)); + expect( + newBalances[compliantTakerAddress][zrxToken.address], + 'Checking taker egress ERC20 account fees', + ).to.be.bignumber.equal(erc20Balances[compliantTakerAddress][zrxToken.address].minus(expectedTransferAmounts.feePaidByTakerLeft).sub(expectedTransferAmounts.feePaidByTakerRight)); + // Received fees + expect( + newBalances[signedOrderLeft.feeRecipientAddress][zrxToken.address], + 'Checking left fee recipient ingress ERC20 account fees', + ).to.be.bignumber.equal( + erc20Balances[feeRecipientAddress][zrxToken.address].add(expectedTransferAmounts.feePaidByLeftMaker).add(expectedTransferAmounts.feePaidByRightMaker).add(expectedTransferAmounts.feePaidByTakerLeft).add(expectedTransferAmounts.feePaidByTakerRight), + ); + }); }); describe('cancelOrder', () => { diff --git a/packages/contracts/test/utils/balance_threshold_wrapper.ts b/packages/contracts/test/utils/balance_threshold_wrapper.ts index ac7bdd593..cff40aa52 100644 --- a/packages/contracts/test/utils/balance_threshold_wrapper.ts +++ b/packages/contracts/test/utils/balance_threshold_wrapper.ts @@ -227,6 +227,10 @@ export class BalanceThresholdWrapper { public getExchangeAddress(): string { return this._exchange.address; } + // Exchange functions + //abiEncodeFillOrder + //getFillOrderResultsAsync + // private async _executeTransaction(abiEncodedExchangeTxData: string, from: string, gas?: number): Promise { const signedExchangeTx = this._signerTransactionFactory.newSignedTransaction(abiEncodedExchangeTxData); const txOpts = _.isUndefined(gas) ? {from} : {from, gas}; -- cgit From 1883f4d2726e1a879be42b7bb6168f30afd486d9 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Fri, 7 Dec 2018 18:01:16 -0800 Subject: matchOrders test cases for balance threshold filter contract --- .../MixinBalanceThresholdFilterCore.sol | 4 +- .../test/extensions/balance_threshold_filter.ts | 44 +++++++++++++++++++++- 2 files changed, 45 insertions(+), 3 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol b/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol index 303e1d9c2..0ad8ccddf 100644 --- a/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol +++ b/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol @@ -258,7 +258,7 @@ contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { // This is to avoid corruption when making calls in the loop below. let freeMemPtr := addressesToValidateElementEndPtr mstore(0x40, freeMemPtr) -/* + // Validate addresses let thresholdAssetAddress := sload(THRESHOLD_ASSET_slot) let thresholdBalance := sload(THRESHOLD_BALANCE_slot) @@ -307,7 +307,7 @@ contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { // 64 -- strlen(AT_LEAST_ONE_ADDRESS_DOES_NOT_MEET_BALANCE_THRESHOLD) rounded up to nearest 32-byte word. revert(0, 132) } - }*/ + } // Record validated addresses validatedAddresses := addressesToValidate diff --git a/packages/contracts/test/extensions/balance_threshold_filter.ts b/packages/contracts/test/extensions/balance_threshold_filter.ts index 7ca8a8e98..0a03678b1 100644 --- a/packages/contracts/test/extensions/balance_threshold_filter.ts +++ b/packages/contracts/test/extensions/balance_threshold_filter.ts @@ -1225,7 +1225,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { compliantSignedOrder = await orderFactory.newSignedOrderAsync(); compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); }); - it.only('Should transfer correct amounts when both makers and taker meet the balance threshold', async () => { + it('Should transfer correct amounts when both makers and taker meet the balance threshold', async () => { // Test values/results taken from Match Orders test: // 'Should transfer correct amounts when right order is fully filled and values pass isRoundingErrorFloor but fail isRoundingErrorCeil' // Create orders to match @@ -1309,6 +1309,48 @@ describe.only(ContractName.BalanceThresholdFilter, () => { erc20Balances[feeRecipientAddress][zrxToken.address].add(expectedTransferAmounts.feePaidByLeftMaker).add(expectedTransferAmounts.feePaidByRightMaker).add(expectedTransferAmounts.feePaidByTakerLeft).add(expectedTransferAmounts.feePaidByTakerRight), ); }); + it('should revert if left maker does not meet the balance threshold', async () => { + // Create signed order with non-compliant maker address + const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ + senderAddress: compliantForwarderInstance.address, + makerAddress: nonCompliantAddress + }); + // Execute transaction + return expectTransactionFailedAsync( + balanceThresholdWrapper.matchOrdersAsync( + compliantSignedOrder, + signedOrderWithBadMakerAddress, + compliantTakerAddress, + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); + }); + it('should revert if right maker does not meet the balance threshold', async () => { + // Create signed order with non-compliant maker address + const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ + senderAddress: compliantForwarderInstance.address, + makerAddress: nonCompliantAddress + }); + // Execute transaction + return expectTransactionFailedAsync( + balanceThresholdWrapper.matchOrdersAsync( + signedOrderWithBadMakerAddress, + compliantSignedOrder, + compliantTakerAddress, + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); + }); + it('should revert if taker does not meet the balance threshold', async () => { + return expectTransactionFailedAsync( + nonCompliantBalanceThresholdWrapper.matchOrdersAsync( + compliantSignedOrder, + compliantSignedOrder, + nonCompliantAddress, + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); + }); }); describe('cancelOrder', () => { -- cgit From cb9ec18f962d3a44e75f291795fc59494a6226de Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 10 Dec 2018 15:11:00 -0800 Subject: Tests finished for balance threshold wrapper --- .../test/extensions/balance_threshold_filter.ts | 220 ++++++++++++++++++--- 1 file changed, 189 insertions(+), 31 deletions(-) (limited to 'packages') diff --git a/packages/contracts/test/extensions/balance_threshold_filter.ts b/packages/contracts/test/extensions/balance_threshold_filter.ts index 0a03678b1..d0e902eda 100644 --- a/packages/contracts/test/extensions/balance_threshold_filter.ts +++ b/packages/contracts/test/extensions/balance_threshold_filter.ts @@ -27,7 +27,7 @@ import { OrderFactory } from '../utils/order_factory'; import { orderUtils } from '../utils/order_utils'; import { TransactionFactory } from '../utils/transaction_factory'; import { BalanceThresholdWrapper } from '../utils/balance_threshold_wrapper'; -import { ContractName, ERC20BalancesByOwner, SignedTransaction } from '../utils/types'; +import { ContractName, ERC20BalancesByOwner, SignedTransaction, OrderStatus } from '../utils/types'; import { provider, txDefaults, web3Wrapper } from '../utils/web3_wrapper'; import { TestExchangeInternalsContract } from '../../generated-wrappers/test_exchange_internals'; @@ -62,12 +62,15 @@ describe.only(ContractName.BalanceThresholdFilter, () => { let orderFactory: OrderFactory; let orderFactory2: OrderFactory; + let nonCompliantOrderFactory: OrderFactory; let erc20Wrapper: ERC20Wrapper; let erc20Balances: ERC20BalancesByOwner; - let balanceThresholdWrapper: BalanceThresholdWrapper; + let takerBalanceThresholdWrapper: BalanceThresholdWrapper; + let makerBalanceThresholdWrapper: BalanceThresholdWrapper; let nonCompliantBalanceThresholdWrapper: BalanceThresholdWrapper; let takerTransactionFactory: TransactionFactory; + let makerTransactionFactory: TransactionFactory; let compliantSignedOrder: SignedOrder; let compliantSignedOrder2: SignedOrder; let compliantSignedFillOrderTx: SignedTransaction; @@ -170,6 +173,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { defaultOrderParams, } const makerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantMakerAddress)]; + takerTransactionFactory = new TransactionFactory(makerPrivateKey, exchangeInstance.address); orderFactory = new OrderFactory(makerPrivateKey, defaultOrderParams1); const defaultOrderParams2 = { makerAddress: compliantMakerAddress2, @@ -178,6 +182,15 @@ describe.only(ContractName.BalanceThresholdFilter, () => { } const secondMakerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantMakerAddress2)]; orderFactory2 = new OrderFactory(secondMakerPrivateKey, defaultOrderParams2); + + const nonCompliantPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(nonCompliantAddress)]; + const defaultNonCompliantOrderParams = { + makerAddress: nonCompliantAddress, + ... + defaultOrderParams, + }; + nonCompliantOrderFactory = new OrderFactory(nonCompliantPrivateKey, defaultNonCompliantOrderParams); + /* const compliantForwarderContract = new BalanceThresholdFilterContract( compliantForwarderInstance.abi, @@ -264,10 +277,12 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); throw new Error(`w`);*/ logDecoder = new LogDecoder(web3Wrapper); - balanceThresholdWrapper = new BalanceThresholdWrapper(compliantForwarderInstance, exchangeInstance, new TransactionFactory(takerPrivateKey, exchangeInstance.address), provider); - const nonCompliantPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(nonCompliantAddress)]; + takerBalanceThresholdWrapper = new BalanceThresholdWrapper(compliantForwarderInstance, exchangeInstance, new TransactionFactory(takerPrivateKey, exchangeInstance.address), provider); + makerBalanceThresholdWrapper = new BalanceThresholdWrapper(compliantForwarderInstance, exchangeInstance, new TransactionFactory(makerPrivateKey, exchangeInstance.address), provider); + nonCompliantBalanceThresholdWrapper = new BalanceThresholdWrapper(compliantForwarderInstance, exchangeInstance, new TransactionFactory(nonCompliantPrivateKey, exchangeInstance.address), provider); - + + // Instantiate internal exchange contract exchangeInternals = await TestExchangeInternalsContract.deployFrom0xArtifactAsync( artifacts.TestExchangeInternals, @@ -338,7 +353,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { // Execute a valid fill const orders = [compliantSignedOrder, compliantSignedOrder2]; const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; - const txReceipt = await balanceThresholdWrapper.batchFillOrdersAsync(orders, compliantTakerAddress, {takerAssetFillAmounts}); + const txReceipt = await takerBalanceThresholdWrapper.batchFillOrdersAsync(orders, compliantTakerAddress, {takerAssetFillAmounts}); // Assert validated addresses const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); @@ -400,7 +415,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; // Execute transaction return expectTransactionFailedAsync( - balanceThresholdWrapper.batchFillOrdersAsync( + takerBalanceThresholdWrapper.batchFillOrdersAsync( orders, compliantTakerAddress, {takerAssetFillAmounts} @@ -432,7 +447,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { // Execute a valid fill const orders = [compliantSignedOrder, compliantSignedOrder2]; const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; - const txReceipt = await balanceThresholdWrapper.batchFillOrdersNoThrowAsync(orders, compliantTakerAddress, {takerAssetFillAmounts}); + const txReceipt = await takerBalanceThresholdWrapper.batchFillOrdersNoThrowAsync(orders, compliantTakerAddress, {takerAssetFillAmounts}); // Assert validated addresses const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); @@ -494,7 +509,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; // Execute transaction return expectTransactionFailedAsync( - balanceThresholdWrapper.batchFillOrdersNoThrowAsync( + takerBalanceThresholdWrapper.batchFillOrdersNoThrowAsync( orders, compliantTakerAddress, {takerAssetFillAmounts} @@ -526,7 +541,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { // Execute a valid fill const orders = [compliantSignedOrder, compliantSignedOrder2]; const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; - const txReceipt = await balanceThresholdWrapper.batchFillOrKillOrdersAsync(orders, compliantTakerAddress, {takerAssetFillAmounts}); + const txReceipt = await takerBalanceThresholdWrapper.batchFillOrKillOrdersAsync(orders, compliantTakerAddress, {takerAssetFillAmounts}); // Assert validated addresses const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); @@ -588,7 +603,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; // Execute transaction return expectTransactionFailedAsync( - balanceThresholdWrapper.batchFillOrKillOrdersAsync( + takerBalanceThresholdWrapper.batchFillOrKillOrdersAsync( orders, compliantTakerAddress, {takerAssetFillAmounts} @@ -613,7 +628,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { const orders = [compliantSignedOrder, compliantSignedOrder2]; const takerAssetFillAmounts = [takerAssetFillAmount, tooBigTakerAssetFillAmount]; return expectTransactionFailedAsync( - balanceThresholdWrapper.batchFillOrKillOrdersAsync( + takerBalanceThresholdWrapper.batchFillOrKillOrdersAsync( orders, compliantTakerAddress, {takerAssetFillAmounts} @@ -630,7 +645,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); it('should transfer the correct amounts and validate both maker/taker when both maker and taker meet the balance threshold', async () => { // Execute a valid fill - const txReceipt = await balanceThresholdWrapper.fillOrderAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount}); + const txReceipt = await takerBalanceThresholdWrapper.fillOrderAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount}); // Assert validated addresses const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedFillOrderTx.signerAddress]; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); @@ -675,7 +690,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); // Execute transaction return expectTransactionFailedAsync( - balanceThresholdWrapper.fillOrderAsync( + takerBalanceThresholdWrapper.fillOrderAsync( signedOrderWithBadMakerAddress, compliantTakerAddress, {takerAssetFillAmount} @@ -702,7 +717,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); it('should transfer the correct amounts and validate both maker/taker when both maker and taker meet the balance threshold', async () => { // Execute a valid fill - const txReceipt = await balanceThresholdWrapper.fillOrderNoThrowAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount}); + const txReceipt = await takerBalanceThresholdWrapper.fillOrderNoThrowAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount}); // Assert validated addresses const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedFillOrderTx.signerAddress]; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); @@ -747,7 +762,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); // Execute transaction return expectTransactionFailedAsync( - balanceThresholdWrapper.fillOrderNoThrowAsync( + takerBalanceThresholdWrapper.fillOrderNoThrowAsync( signedOrderWithBadMakerAddress, compliantTakerAddress, {takerAssetFillAmount} @@ -775,7 +790,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { it('should transfer the correct amounts and validate both maker/taker when both maker and taker meet the balance threshold', async () => { // Execute a valid fill const takerAssetFillAmount_ = compliantSignedOrder.takerAssetAmount; - const txReceipt = await balanceThresholdWrapper.fillOrKillOrderAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount: takerAssetFillAmount_}); + const txReceipt = await takerBalanceThresholdWrapper.fillOrKillOrderAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount: takerAssetFillAmount_}); // Assert validated addresses const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedFillOrderTx.signerAddress]; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); @@ -820,7 +835,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); // Execute transaction return expectTransactionFailedAsync( - balanceThresholdWrapper.fillOrKillOrderAsync( + takerBalanceThresholdWrapper.fillOrKillOrderAsync( signedOrderWithBadMakerAddress, compliantTakerAddress, {takerAssetFillAmount} @@ -841,7 +856,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { it('should revert if takerAssetFillAmount is not fully filled', async () => { const tooBigTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.times(2); return expectTransactionFailedAsync( - balanceThresholdWrapper.fillOrKillOrderAsync( + takerBalanceThresholdWrapper.fillOrKillOrderAsync( compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount: tooBigTakerAssetFillAmount} @@ -861,7 +876,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { // Execute a valid fill const orders = [compliantSignedOrder, compliantSignedOrder2]; const cumulativeTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.plus(takerAssetFillAmount); - const txReceipt = await balanceThresholdWrapper.marketSellOrdersAsync(orders, compliantTakerAddress, {takerAssetFillAmount: cumulativeTakerAssetFillAmount}); + const txReceipt = await takerBalanceThresholdWrapper.marketSellOrdersAsync(orders, compliantTakerAddress, {takerAssetFillAmount: cumulativeTakerAssetFillAmount}); // Assert validated addresses const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); @@ -921,7 +936,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; // Execute transaction return expectTransactionFailedAsync( - balanceThresholdWrapper.marketSellOrdersAsync( + takerBalanceThresholdWrapper.marketSellOrdersAsync( orders, compliantTakerAddress, {takerAssetFillAmount} @@ -952,7 +967,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { // Execute a valid fill const orders = [compliantSignedOrder, compliantSignedOrder2]; const cumulativeTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.plus(takerAssetFillAmount); - const txReceipt = await balanceThresholdWrapper.marketSellOrdersNoThrowAsync(orders, compliantTakerAddress, {takerAssetFillAmount: cumulativeTakerAssetFillAmount}); + const txReceipt = await takerBalanceThresholdWrapper.marketSellOrdersNoThrowAsync(orders, compliantTakerAddress, {takerAssetFillAmount: cumulativeTakerAssetFillAmount}); // Assert validated addresses const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); @@ -1012,7 +1027,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; // Execute transaction return expectTransactionFailedAsync( - balanceThresholdWrapper.marketSellOrdersNoThrowAsync( + takerBalanceThresholdWrapper.marketSellOrdersNoThrowAsync( orders, compliantTakerAddress, {takerAssetFillAmount} @@ -1047,7 +1062,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { .times(compliantSignedOrder.makerAssetAmount) .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); const cumulativeMakerAssetFillAmount = compliantSignedOrder.makerAssetAmount.plus(makerAssetFillAmount2); - const txReceipt = await balanceThresholdWrapper.marketBuyOrdersAsync(orders, compliantTakerAddress, {makerAssetFillAmount: cumulativeMakerAssetFillAmount}); + const txReceipt = await takerBalanceThresholdWrapper.marketBuyOrdersAsync(orders, compliantTakerAddress, {makerAssetFillAmount: cumulativeMakerAssetFillAmount}); // Assert validated addresses const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); @@ -1104,7 +1119,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { // Execute transaction const dummyMakerAssetFillAmount = new BigNumber(0); return expectTransactionFailedAsync( - balanceThresholdWrapper.marketBuyOrdersAsync( + takerBalanceThresholdWrapper.marketBuyOrdersAsync( orders, compliantTakerAddress, {makerAssetFillAmount: dummyMakerAssetFillAmount} @@ -1140,7 +1155,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { .times(compliantSignedOrder.makerAssetAmount) .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); const cumulativeMakerAssetFillAmount = compliantSignedOrder.makerAssetAmount.plus(makerAssetFillAmount2); - const txReceipt = await balanceThresholdWrapper.marketBuyOrdersNoThrowAsync(orders, compliantTakerAddress, {makerAssetFillAmount: cumulativeMakerAssetFillAmount}); + const txReceipt = await takerBalanceThresholdWrapper.marketBuyOrdersNoThrowAsync(orders, compliantTakerAddress, {makerAssetFillAmount: cumulativeMakerAssetFillAmount}); // Assert validated addresses const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); @@ -1197,7 +1212,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { // Execute transaction const dummyMakerAssetFillAmount = new BigNumber(0); return expectTransactionFailedAsync( - balanceThresholdWrapper.marketBuyOrdersNoThrowAsync( + takerBalanceThresholdWrapper.marketBuyOrdersNoThrowAsync( orders, compliantTakerAddress, {makerAssetFillAmount: dummyMakerAssetFillAmount} @@ -1219,7 +1234,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); }); - describe.only('matchOrders', () => { + describe('matchOrders', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); compliantSignedOrder = await orderFactory.newSignedOrderAsync(); @@ -1260,7 +1275,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber('76.5306122448979591'), 16), // 76.53% feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% }; - const txReceipt = await balanceThresholdWrapper.matchOrdersAsync(signedOrderLeft, signedOrderRight, compliantTakerAddress); + const txReceipt = await takerBalanceThresholdWrapper.matchOrdersAsync(signedOrderLeft, signedOrderRight, compliantTakerAddress); // Assert validated addresses const expectedValidatedAddresseses = [signedOrderLeft.makerAddress, signedOrderRight.makerAddress, compliantTakerAddress]; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); @@ -1317,7 +1332,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); // Execute transaction return expectTransactionFailedAsync( - balanceThresholdWrapper.matchOrdersAsync( + takerBalanceThresholdWrapper.matchOrdersAsync( compliantSignedOrder, signedOrderWithBadMakerAddress, compliantTakerAddress, @@ -1333,7 +1348,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); // Execute transaction return expectTransactionFailedAsync( - balanceThresholdWrapper.matchOrdersAsync( + takerBalanceThresholdWrapper.matchOrdersAsync( signedOrderWithBadMakerAddress, compliantSignedOrder, compliantTakerAddress, @@ -1354,12 +1369,155 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); describe('cancelOrder', () => { + beforeEach(async () => { + erc20Balances = await erc20Wrapper.getBalancesAsync(); + compliantSignedOrder = await orderFactory.newSignedOrderAsync(); + compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); + }); + it('Should successfully cancel order if maker meets balance threshold', async () => { + // Verify order is not cancelled + const orderInfoBeforeCancelling = await makerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) + expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); + // Cancel + const txReceipt = await makerBalanceThresholdWrapper.cancelOrderAsync(compliantSignedOrder, compliantSignedOrder.makerAddress); + // Assert validated addresses + const expectedValidatedAddresseses: string[] = []; + assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); + // Check that order was cancelled + const orderInfoAfterCancelling = await makerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) + expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED); + }); + it('Should successfully cancel order if maker does not meet balance threshold', async () => { + // Create order where maker does not meet balance threshold + const signedOrderWithBadMakerAddress = await nonCompliantOrderFactory.newSignedOrderAsync({}); + // Verify order is not cancelled + const orderInfoBeforeCancelling = await nonCompliantBalanceThresholdWrapper.getOrderInfoAsync(signedOrderWithBadMakerAddress) + expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); + // Cancel + const txReceipt = await nonCompliantBalanceThresholdWrapper.cancelOrderAsync(signedOrderWithBadMakerAddress, signedOrderWithBadMakerAddress.makerAddress); + // Assert validated addresses + const expectedValidatedAddresseses: string[] = []; + assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); + // Check that order was cancelled + const orderInfoAfterCancelling = await makerBalanceThresholdWrapper.getOrderInfoAsync(signedOrderWithBadMakerAddress) + expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED); + }); }); describe('batchCancelOrders', () => { + beforeEach(async () => { + erc20Balances = await erc20Wrapper.getBalancesAsync(); + }); + it('Should successfully batch cancel orders if maker meets balance threshold', async () => { + // Create orders to cancel + const compliantSignedOrders = [ + await orderFactory.newSignedOrderAsync(), + await orderFactory.newSignedOrderAsync(), + await orderFactory.newSignedOrderAsync(), + ]; + // Verify orders are not cancelled + await _.each(compliantSignedOrders, async (compliantSignedOrder) => { + const orderInfoBeforeCancelling = await makerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) + return expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); + }); + // Cancel + const txReceipt = await makerBalanceThresholdWrapper.batchCancelOrdersAsync(compliantSignedOrders, compliantSignedOrder.makerAddress); + // Assert validated addresses + const expectedValidatedAddresseses: string[] = []; + assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); + // Check that order was cancelled + await _.each(compliantSignedOrders, async (compliantSignedOrder) => { + const orderInfoAfterCancelling = await makerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) + return expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED); + }); + }); + it('Should successfully batch cancel order if maker does not meet balance threshold', async () => { + // Create orders to cancel + const nonCompliantSignedOrders = [ + await nonCompliantOrderFactory.newSignedOrderAsync(), + await nonCompliantOrderFactory.newSignedOrderAsync(), + await nonCompliantOrderFactory.newSignedOrderAsync(), + ]; + // Verify orders are not cancelled + await _.each(nonCompliantSignedOrders, async (nonCompliantSignedOrder) => { + const orderInfoBeforeCancelling = await nonCompliantBalanceThresholdWrapper.getOrderInfoAsync(nonCompliantSignedOrder) + return expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); + }); + // Cancel + const txReceipt = await nonCompliantBalanceThresholdWrapper.batchCancelOrdersAsync(nonCompliantSignedOrders, nonCompliantAddress); + // Assert validated addresses + const expectedValidatedAddresseses: string[] = []; + assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); + // Check that order was cancelled + await _.each(nonCompliantSignedOrders, async (nonCompliantSignedOrder) => { + const orderInfoAfterCancelling = await nonCompliantBalanceThresholdWrapper.getOrderInfoAsync(nonCompliantSignedOrder) + return expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED); + }); + }); }); describe('cancelOrdersUpTo', () => { + beforeEach(async () => { + erc20Balances = await erc20Wrapper.getBalancesAsync(); + }); + it('Should successfully batch cancel orders if maker meets balance threshold', async () => { + // Create orders to cancel + const compliantSignedOrders = [ + await orderFactory.newSignedOrderAsync({salt: new BigNumber(0)}), + await orderFactory.newSignedOrderAsync({salt: new BigNumber(1)}), + await orderFactory.newSignedOrderAsync({salt: new BigNumber(2)}), + ]; + // Verify orders are not cancelled + await _.each(compliantSignedOrders, async (compliantSignedOrder) => { + const orderInfoBeforeCancelling = await makerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) + return expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); + }); + // Cancel + const cancelOrdersUpToThisSalt = new BigNumber(1); + const txReceipt = await makerBalanceThresholdWrapper.cancelOrdersUpToAsync(cancelOrdersUpToThisSalt, compliantSignedOrder.makerAddress); + // Assert validated addresses + const expectedValidatedAddresseses: string[] = []; + assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); + // Check that order was cancelled + await _.each(compliantSignedOrders, async (compliantSignedOrder, salt: number) => { + const orderInfoAfterCancelling = await makerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) + const saltAsBigNumber = new BigNumber(salt); + if (saltAsBigNumber.lessThanOrEqualTo(cancelOrdersUpToThisSalt)) { + return expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED); + } else { + return expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); + } + }); + }); + it('Should successfully batch cancel order if maker does not meet balance threshold', async () => { + // Create orders to cancel + const nonCompliantSignedOrders = [ + await nonCompliantOrderFactory.newSignedOrderAsync({salt: new BigNumber(0)}), + await nonCompliantOrderFactory.newSignedOrderAsync({salt: new BigNumber(1)}), + await nonCompliantOrderFactory.newSignedOrderAsync({salt: new BigNumber(2)}), + ]; + // Verify orders are not cancelled + await _.each(nonCompliantSignedOrders, async (nonCompliantSignedOrder) => { + const orderInfoBeforeCancelling = await nonCompliantBalanceThresholdWrapper.getOrderInfoAsync(nonCompliantSignedOrder) + return expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); + }); + // Cancel + const cancelOrdersUpToThisSalt = new BigNumber(1); + const txReceipt = await nonCompliantBalanceThresholdWrapper.cancelOrdersUpToAsync(cancelOrdersUpToThisSalt, nonCompliantAddress); + // Assert validated addresses + const expectedValidatedAddresseses: string[] = []; + assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); + // Check that order was cancelled + await _.each(nonCompliantSignedOrders, async (nonCompliantSignedOrder, salt: number) => { + const orderInfoAfterCancelling = await nonCompliantBalanceThresholdWrapper.getOrderInfoAsync(nonCompliantSignedOrder) + const saltAsBigNumber = new BigNumber(salt); + if (saltAsBigNumber.lessThanOrEqualTo(cancelOrdersUpToThisSalt)) { + return expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED); + } else { + return expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); + } + }); + }); }); }); // tslint:disable:max-file-line-count -- cgit From 8d6219296a4ac0c2ec46ae077eb87cebb19f8b55 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 10 Dec 2018 17:08:16 -0800 Subject: Removed Yes Token - its no longer needed to test Balance Threshold Filter --- .../YesComplianceToken/IYesComplianceToken.sol | 119 ---- .../YesComplianceToken/WyreERC721Token/ERC721.sol | 40 -- .../WyreERC721Token/ERC721Basic.sol | 47 -- .../WyreERC721Token/ERC721BasicToken.sol | 343 ----------- .../WyreERC721Token/ERC721Token.sol | 209 ------- .../YesComplianceToken/YesComplianceToken.sol | 648 --------------------- .../test/extensions/balance_threshold_filter.ts | 59 +- 7 files changed, 12 insertions(+), 1453 deletions(-) delete mode 100644 packages/contracts/contracts/tokens/YesComplianceToken/IYesComplianceToken.sol delete mode 100644 packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721.sol delete mode 100644 packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721Basic.sol delete mode 100644 packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721BasicToken.sol delete mode 100644 packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721Token.sol delete mode 100644 packages/contracts/contracts/tokens/YesComplianceToken/YesComplianceToken.sol (limited to 'packages') diff --git a/packages/contracts/contracts/tokens/YesComplianceToken/IYesComplianceToken.sol b/packages/contracts/contracts/tokens/YesComplianceToken/IYesComplianceToken.sol deleted file mode 100644 index a1c9b9671..000000000 --- a/packages/contracts/contracts/tokens/YesComplianceToken/IYesComplianceToken.sol +++ /dev/null @@ -1,119 +0,0 @@ -pragma solidity ^0.4.24; - -import "./WyreERC721Token/ERC721Token.sol"; - -/** - * @notice an ERC721 "yes" compliance token supporting a collection of country-specific attributions which answer specific - * compliance-related queries with YES. (attestations) - * - * primarily ERC721 is useful for the self-management of claiming addresses. a single token is more useful - * than a non-ERC721 interface because of interop with other 721-supporting systems/ui; it allows users to - * manage their financial stamp with flexibility using a well-established simple concept of non-fungible tokens. - * this interface is for anyone needing to carry around and otherwise manage their proof of compliance. - * - * the financial systems these users authenticate against have a different set of API requirements. they need - * more contextualization ability than a balance check to support distinctions of attestations, as well as geographic - * distinction. these integrations are made simpler as the language of the query more closely match the language of compliance. - * - * this interface describes, beyond 721, these simple compliance-specific interfaces (and their management tools) - * - * notes: - * - no address can be associated with more than one identity (though addresses may have more than token). issuance - * in this circumstance will fail - * - one person or business = one entity - * - one entity may have many tokens across many addresses; they can mint and burn tokens tied to their identity at will - * - two token types: control & non-control. both carry compliance proof - * - control tokens let their holders mint and burn (within the same entity) - * - non-control tokens are solely for compliance queries - * - a lock on the entity is used instead of token revocation to remove the cash burden assumed by a customer to - * redistribute a fleet of coins - * - all country codes should be via ISO-3166-1 - * - * any (non-view) methods not explicitly marked idempotent are not idempotent. - */ -contract YesComplianceTokenV1 is ERC721Token /*, ERC165 :should: */ { - - uint256 public constant OWNER_ENTITY_ID = 1; - - uint8 public constant YESMARK_OWNER = 128; - uint8 public constant YESMARK_VALIDATOR = 129; - - /* - todo events: entity updated, destroyed, ???? - Finalized - Attested - - */ - - /** - * @notice query api: returns true if the specified address has the given country/yes attestation. this - * is the primary method partners will use to query the active qualifications of any particular - * address. - */ - function isYes(uint256 _validatorEntityId, address _address, uint16 _countryCode, uint8 _yes) external view returns(bool) ; - - /** @notice same as isYes except as an imperative */ - function requireYes(uint256 _validatorEntityId, address _address, uint16 _countryCode, uint8 _yes) external view ; - - /** - * @notice retrieve all YES marks for an address in a particular country - * @param _validatorEntityId the validator ID to consider. or, use 0 for any of them - * @param _address the validator ID to consider, or 0 for any of them - * @param _countryCode the ISO-3166-1 country code - * @return (non-duplicate) array of YES marks present - */ - function getYes(uint256 _validatorEntityId, address _address, uint16 _countryCode) external view returns(uint8[] /* memory */); - - // function getCountries(uint256 _validatorEntityId, address _address) external view returns(uint16[] /* memory */); - - /** - * @notice create new tokens. fail if _to already - * belongs to a different entity and caller is not validator - * @param _control true if the new token is a control token (can mint, burn). aka NOT limited. - * @param _entityId the entity to mint for, supply 0 to use the entity tied to the caller - * @return the newly created token ID - */ - function mint(address _to, uint256 _entityId, bool _control) external returns (uint256); - - /** @notice shortcut to mint() + setYes() in one call, for a single country */ - function mint(address _to, uint256 _entityId, bool _control, uint16 _countryCode, uint8[] _yes) external returns (uint256); - - /** @notice destroys a specific token */ - function burn(uint256 _tokenId) external; - - /** @notice destroys the entire entity and all tokens */ - function burnEntity(uint256 _entityId) external; - - /** - * @notice adds a specific attestations (yes) to an entity. idempotent: will return normally even if the mark - * was already set by this validator - */ - function setYes(uint256 _entityId, uint16 _countryCode, uint8 _yes) external; - - /** - * @notice removes a attestation(s) from a specific validator for an entity. idempotent - */ - function clearYes(uint256 _entityId, uint16 _countryCode, uint8 _yes) external; - - /** @notice removes all attestations in a given country for a particular entity. idempotent */ - function clearYes(uint256 _entityId, uint16 _countryCode) external; - - /** @notice removes all attestations for a particular entity. idempotent */ - function clearYes(uint256 _entityId) external; - - /** @notice assigns a lock to an entity, rendering all isYes queries false. idempotent */ - function setLocked(uint256 _entityId, bool _lock) external; - - /** @notice checks whether or not a particular entity is locked */ - function isLocked(uint256 _entityId) external view returns(bool); - - /** @notice returns true if the specified token has been finalized (cannot be moved) */ - function isFinalized(uint256 _tokenId) external view returns(bool); - - /** @notice finalizes a token by ID preventing it from getting moved. idempotent */ - function finalize(uint256 _tokenId) external; - - /** @return the entity ID associated with an address (or fail if there is not one) */ - function getEntityId(address _address) external view returns(uint256); - -} \ No newline at end of file diff --git a/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721.sol b/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721.sol deleted file mode 100644 index 5b4907f13..000000000 --- a/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721.sol +++ /dev/null @@ -1,40 +0,0 @@ -pragma solidity ^0.4.21; - -import "./ERC721Basic.sol"; - - -/** - * @title ERC-721 Non-Fungible Token Standard, optional enumeration extension - * @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md - */ -contract ERC721Enumerable is ERC721Basic { - function totalSupply() public view returns (uint256); - function tokenOfOwnerByIndex( - address _owner, - uint256 _index - ) - public - view - returns (uint256 _tokenId); - - function tokenByIndex(uint256 _index) public view returns (uint256); -} - - -/** - * @title ERC-721 Non-Fungible Token Standard, optional metadata extension - * @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md - */ -contract ERC721Metadata is ERC721Basic { - function name() external view returns (string _name); - function symbol() external view returns (string _symbol); - function tokenURI(uint256 _tokenId) public view returns (string); -} - - -/** - * @title ERC-721 Non-Fungible Token Standard, full implementation interface - * @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md - */ -contract ERC721 is ERC721Basic, ERC721Enumerable, ERC721Metadata { -} \ No newline at end of file diff --git a/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721Basic.sol b/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721Basic.sol deleted file mode 100644 index 20f3c5812..000000000 --- a/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721Basic.sol +++ /dev/null @@ -1,47 +0,0 @@ -pragma solidity ^0.4.21; - -/** - * @title ERC721 Non-Fungible Token Standard basic interface - * @dev see https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md - */ -contract ERC721Basic { - event Transfer( - address indexed _from, - address indexed _to, - uint256 indexed _tokenId - ); - event Approval( - address indexed _owner, - address indexed _approved, - uint256 indexed _tokenId - ); - event ApprovalForAll( - address indexed _owner, - address indexed _operator, - bool _approved - ); - - function balanceOf(address _owner) public view returns (uint256 _balance); - function ownerOf(uint256 _tokenId) public view returns (address _owner); - function exists(uint256 _tokenId) public view returns (bool _exists); - - function approve(address _to, uint256 _tokenId) public; - function getApproved(uint256 _tokenId) - public view returns (address _operator); - - function setApprovalForAll(address _operator, bool _approved) public; - function isApprovedForAll(address _owner, address _operator) - public view returns (bool); - - function transferFrom(address _from, address _to, uint256 _tokenId) public; - function safeTransferFrom(address _from, address _to, uint256 _tokenId) - public; - - function safeTransferFrom( - address _from, - address _to, - uint256 _tokenId, - bytes _data - ) - public; -} \ No newline at end of file diff --git a/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721BasicToken.sol b/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721BasicToken.sol deleted file mode 100644 index 788e31580..000000000 --- a/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721BasicToken.sol +++ /dev/null @@ -1,343 +0,0 @@ -pragma solidity ^0.4.21; - -import "./ERC721Basic.sol"; -import "../../ERC721Token/IERC721Receiver.sol"; -import "../../../utils/SafeMath/SafeMath.sol"; - - -/** - * @title ERC721 Non-Fungible Token Standard basic implementation - * @dev see https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md - */ -contract ERC721BasicToken is ERC721Basic, SafeMath { - - bytes4 private constant InterfaceId_ERC721 = 0x80ac58cd; - /* - * 0x80ac58cd === - * bytes4(keccak256('balanceOf(address)')) ^ - * bytes4(keccak256('ownerOf(uint256)')) ^ - * bytes4(keccak256('approve(address,uint256)')) ^ - * bytes4(keccak256('getApproved(uint256)')) ^ - * bytes4(keccak256('setApprovalForAll(address,bool)')) ^ - * bytes4(keccak256('isApprovedForAll(address,address)')) ^ - * bytes4(keccak256('transferFrom(address,address,uint256)')) ^ - * bytes4(keccak256('safeTransferFrom(address,address,uint256)')) ^ - * bytes4(keccak256('safeTransferFrom(address,address,uint256,bytes)')) - */ - - bytes4 private constant InterfaceId_ERC721Exists = 0x4f558e79; - /* - * 0x4f558e79 === - * bytes4(keccak256('exists(uint256)')) - */ - - // Equals to `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))` - // which can be also obtained as `ERC721Receiver(0).onERC721Received.selector` - bytes4 private constant ERC721_RECEIVED = 0x150b7a02; - - // Mapping from token ID to owner - mapping (uint256 => address) internal tokenOwner; - - // Mapping from token ID to approved address - mapping (uint256 => address) internal tokenApprovals; - - // Mapping from owner to number of owned token - mapping (address => uint256) internal ownedTokensCount; - - // Mapping from owner to operator approvals - mapping (address => mapping (address => bool)) internal operatorApprovals; - - /** - * @dev Guarantees msg.sender is owner of the given token - * @param _tokenId uint256 ID of the token to validate its ownership belongs to msg.sender - */ - modifier onlyOwnerOf(uint256 _tokenId) { - require(ownerOf(_tokenId) == msg.sender); - _; - } - - /** - * @dev Checks msg.sender can transfer a token, by being owner, approved, or operator - * @param _tokenId uint256 ID of the token to validate - */ - modifier canTransfer(uint256 _tokenId) { - require(isApprovedOrOwner(msg.sender, _tokenId)); - _; - } - - /** - * @dev Gets the balance of the specified address - * @param _owner address to query the balance of - * @return uint256 representing the amount owned by the passed address - */ - function balanceOf(address _owner) public view returns (uint256) { - require(_owner != address(0)); - return ownedTokensCount[_owner]; - } - - /** - * @dev Gets the owner of the specified token ID - * @param _tokenId uint256 ID of the token to query the owner of - * @return owner address currently marked as the owner of the given token ID - */ - function ownerOf(uint256 _tokenId) public view returns (address) { - address owner = tokenOwner[_tokenId]; - require(owner != address(0)); - return owner; - } - - /** - * @dev Returns whether the specified token exists - * @param _tokenId uint256 ID of the token to query the existence of - * @return whether the token exists - */ - function exists(uint256 _tokenId) public view returns (bool) { - address owner = tokenOwner[_tokenId]; - return owner != address(0); - } - - /** - * @dev Approves another address to transfer the given token ID - * The zero address indicates there is no approved address. - * There can only be one approved address per token at a given time. - * Can only be called by the token owner or an approved operator. - * @param _to address to be approved for the given token ID - * @param _tokenId uint256 ID of the token to be approved - */ - function approve(address _to, uint256 _tokenId) public { - address owner = ownerOf(_tokenId); - require(_to != owner); - require(msg.sender == owner || isApprovedForAll(owner, msg.sender)); - - tokenApprovals[_tokenId] = _to; - emit Approval(owner, _to, _tokenId); - } - - /** - * @dev Gets the approved address for a token ID, or zero if no address set - * @param _tokenId uint256 ID of the token to query the approval of - * @return address currently approved for the given token ID - */ - function getApproved(uint256 _tokenId) public view returns (address) { - return tokenApprovals[_tokenId]; - } - - /** - * @dev Sets or unsets the approval of a given operator - * An operator is allowed to transfer all tokens of the sender on their behalf - * @param _to operator address to set the approval - * @param _approved representing the status of the approval to be set - */ - function setApprovalForAll(address _to, bool _approved) public { - require(_to != msg.sender); - operatorApprovals[msg.sender][_to] = _approved; - emit ApprovalForAll(msg.sender, _to, _approved); - } - - /** - * @dev Tells whether an operator is approved by a given owner - * @param _owner owner address which you want to query the approval of - * @param _operator operator address which you want to query the approval of - * @return bool whether the given operator is approved by the given owner - */ - function isApprovedForAll( - address _owner, - address _operator - ) - public - view - returns (bool) - { - return operatorApprovals[_owner][_operator]; - } - - /** - * @dev Transfers the ownership of a given token ID to another address - * Usage of this method is discouraged, use `safeTransferFrom` whenever possible - * Requires the msg sender to be the owner, approved, or operator - * @param _from current owner of the token - * @param _to address to receive the ownership of the given token ID - * @param _tokenId uint256 ID of the token to be transferred - */ - function transferFrom( - address _from, - address _to, - uint256 _tokenId - ) - public - canTransfer(_tokenId) - { - require(_from != address(0)); - require(_to != address(0)); - - clearApproval(_from, _tokenId); - removeTokenFrom(_from, _tokenId); - addTokenTo(_to, _tokenId); - - emit Transfer(_from, _to, _tokenId); - } - - /** - * @dev Safely transfers the ownership of a given token ID to another address - * If the target address is a contract, it must implement `onERC721Received`, - * which is called upon a safe transfer, and return the magic value - * `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`; otherwise, - * the transfer is reverted. - * - * Requires the msg sender to be the owner, approved, or operator - * @param _from current owner of the token - * @param _to address to receive the ownership of the given token ID - * @param _tokenId uint256 ID of the token to be transferred - */ - function safeTransferFrom( - address _from, - address _to, - uint256 _tokenId - ) - public - canTransfer(_tokenId) - { - // solium-disable-next-line arg-overflow - safeTransferFrom(_from, _to, _tokenId, ""); - } - - /** - * @dev Safely transfers the ownership of a given token ID to another address - * If the target address is a contract, it must implement `onERC721Received`, - * which is called upon a safe transfer, and return the magic value - * `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`; otherwise, - * the transfer is reverted. - * Requires the msg sender to be the owner, approved, or operator - * @param _from current owner of the token - * @param _to address to receive the ownership of the given token ID - * @param _tokenId uint256 ID of the token to be transferred - * @param _data bytes data to send along with a safe transfer check - */ - function safeTransferFrom( - address _from, - address _to, - uint256 _tokenId, - bytes _data - ) - public - canTransfer(_tokenId) - { - transferFrom(_from, _to, _tokenId); - // solium-disable-next-line arg-overflow - require(checkAndCallSafeTransfer(_from, _to, _tokenId, _data)); - } - - /** - * @dev Returns whether the given spender can transfer a given token ID - * @param _spender address of the spender to query - * @param _tokenId uint256 ID of the token to be transferred - * @return bool whether the msg.sender is approved for the given token ID, - * is an operator of the owner, or is the owner of the token - */ - function isApprovedOrOwner( - address _spender, - uint256 _tokenId - ) - internal - view - returns (bool) - { - address owner = ownerOf(_tokenId); - // Disable solium check because of - // https://github.com/duaraghav8/Solium/issues/175 - // solium-disable-next-line operator-whitespace - return ( - _spender == owner || - getApproved(_tokenId) == _spender || - isApprovedForAll(owner, _spender) - ); - } - - /** - * @dev Internal function to mint a new token - * Reverts if the given token ID already exists - * @param _to The address that will own the minted token - * @param _tokenId uint256 ID of the token to be minted by the msg.sender - */ - function _mint(address _to, uint256 _tokenId) internal { - require(_to != address(0)); - addTokenTo(_to, _tokenId); - emit Transfer(address(0), _to, _tokenId); - } - - /** - * @dev Internal function to burn a specific token - * Reverts if the token does not exist - * @param _tokenId uint256 ID of the token being burned by the msg.sender - */ - function _burn(address _owner, uint256 _tokenId) internal { - clearApproval(_owner, _tokenId); - removeTokenFrom(_owner, _tokenId); - emit Transfer(_owner, address(0), _tokenId); - } - - /** - * @dev Internal function to clear current approval of a given token ID - * Reverts if the given address is not indeed the owner of the token - * @param _owner owner of the token - * @param _tokenId uint256 ID of the token to be transferred - */ - function clearApproval(address _owner, uint256 _tokenId) internal { - require(ownerOf(_tokenId) == _owner); - if (tokenApprovals[_tokenId] != address(0)) { - tokenApprovals[_tokenId] = address(0); - } - } - - /** - * @dev Internal function to add a token ID to the list of a given address - * @param _to address representing the new owner of the given token ID - * @param _tokenId uint256 ID of the token to be added to the tokens list of the given address - */ - function addTokenTo(address _to, uint256 _tokenId) internal { - require(tokenOwner[_tokenId] == address(0)); - tokenOwner[_tokenId] = _to; - ownedTokensCount[_to] = safeAdd(ownedTokensCount[_to], 1); - } - - /** - * @dev Internal function to remove a token ID from the list of a given address - * @param _from address representing the previous owner of the given token ID - * @param _tokenId uint256 ID of the token to be removed from the tokens list of the given address - */ - function removeTokenFrom(address _from, uint256 _tokenId) internal { - require(ownerOf(_tokenId) == _from); - ownedTokensCount[_from] = safeSub(ownedTokensCount[_from], 1); - tokenOwner[_tokenId] = address(0); - } - - /** - * @dev Internal function to invoke `onERC721Received` on a target address - * The call is not executed if the target address is not a contract - * @param _from address representing the previous owner of the given token ID - * @param _to target address that will receive the tokens - * @param _tokenId uint256 ID of the token to be transferred - * @param _data bytes optional data to send along with the call - * @return whether the call correctly returned the expected magic value - */ - function checkAndCallSafeTransfer( - address _from, - address _to, - uint256 _tokenId, - bytes _data - ) - internal - returns (bool) - { - uint256 receiverCodeSize; - assembly { - receiverCodeSize := extcodesize(_to) - } - if (receiverCodeSize == 0) { - return true; - } - bytes4 retval = IERC721Receiver(_to).onERC721Received( - msg.sender, _from, _tokenId, _data); - return (retval == ERC721_RECEIVED); - } -} \ No newline at end of file diff --git a/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721Token.sol b/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721Token.sol deleted file mode 100644 index 832ff3784..000000000 --- a/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721Token.sol +++ /dev/null @@ -1,209 +0,0 @@ -pragma solidity ^0.4.21; - -import "./ERC721.sol"; -import "./ERC721BasicToken.sol"; - - -/** - * @title Full ERC721 Token - * This implementation includes all the required and some optional functionality of the ERC721 standard - * Moreover, it includes approve all functionality using operator terminology - * @dev see https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md - */ -contract ERC721Token is ERC721BasicToken, ERC721 { - - bytes4 private constant InterfaceId_ERC721Enumerable = 0x780e9d63; - /** - * 0x780e9d63 === - * bytes4(keccak256('totalSupply()')) ^ - * bytes4(keccak256('tokenOfOwnerByIndex(address,uint256)')) ^ - * bytes4(keccak256('tokenByIndex(uint256)')) - */ - - bytes4 private constant InterfaceId_ERC721Metadata = 0x5b5e139f; - /** - * 0x5b5e139f === - * bytes4(keccak256('name()')) ^ - * bytes4(keccak256('symbol()')) ^ - * bytes4(keccak256('tokenURI(uint256)')) - */ - - // Token name - string internal name_; - - // Token symbol - string internal symbol_; - - // Mapping from owner to list of owned token IDs - mapping(address => uint256[]) internal ownedTokens; - - // Mapping from token ID to index of the owner tokens list - mapping(uint256 => uint256) internal ownedTokensIndex; - - // Array with all token ids, used for enumeration - uint256[] internal allTokens; - - // Mapping from token id to position in the allTokens array - mapping(uint256 => uint256) internal allTokensIndex; - - // Optional mapping for token URIs - mapping(uint256 => string) internal tokenURIs; - - /** - * @dev Constructor function - */ - function initialize(string _name, string _symbol) public { - name_ = _name; - symbol_ = _symbol; - } - - /** - * @dev Gets the token name - * @return string representing the token name - */ - function name() external view returns (string) { - return name_; - } - - /** - * @dev Gets the token symbol - * @return string representing the token symbol - */ - function symbol() external view returns (string) { - return symbol_; - } - - /** - * @dev Returns an URI for a given token ID - * Throws if the token ID does not exist. May return an empty string. - * @param _tokenId uint256 ID of the token to query - */ - function tokenURI(uint256 _tokenId) public view returns (string) { - require(exists(_tokenId)); - return tokenURIs[_tokenId]; - } - - /** - * @dev Gets the token ID at a given index of the tokens list of the requested owner - * @param _owner address owning the tokens list to be accessed - * @param _index uint256 representing the index to be accessed of the requested tokens list - * @return uint256 token ID at the given index of the tokens list owned by the requested address - */ - function tokenOfOwnerByIndex( - address _owner, - uint256 _index - ) - public - view - returns (uint256) - { - require(_index < balanceOf(_owner)); - return ownedTokens[_owner][_index]; - } - - /** - * @dev Gets the total amount of tokens stored by the contract - * @return uint256 representing the total amount of tokens - */ - function totalSupply() public view returns (uint256) { - return allTokens.length; - } - - /** - * @dev Gets the token ID at a given index of all the tokens in this contract - * Reverts if the index is greater or equal to the total number of tokens - * @param _index uint256 representing the index to be accessed of the tokens list - * @return uint256 token ID at the given index of the tokens list - */ - function tokenByIndex(uint256 _index) public view returns (uint256) { - require(_index < totalSupply()); - return allTokens[_index]; - } - - /** - * @dev Internal function to set the token URI for a given token - * Reverts if the token ID does not exist - * @param _tokenId uint256 ID of the token to set its URI - * @param _uri string URI to assign - */ - function _setTokenURI(uint256 _tokenId, string _uri) internal { - require(exists(_tokenId)); - tokenURIs[_tokenId] = _uri; - } - - /** - * @dev Internal function to add a token ID to the list of a given address - * @param _to address representing the new owner of the given token ID - * @param _tokenId uint256 ID of the token to be added to the tokens list of the given address - */ - function addTokenTo(address _to, uint256 _tokenId) internal { - super.addTokenTo(_to, _tokenId); - uint256 length = ownedTokens[_to].length; - ownedTokens[_to].push(_tokenId); - ownedTokensIndex[_tokenId] = length; - } - - /** - * @dev Internal function to remove a token ID from the list of a given address - * @param _from address representing the previous owner of the given token ID - * @param _tokenId uint256 ID of the token to be removed from the tokens list of the given address - */ - function removeTokenFrom(address _from, uint256 _tokenId) internal { - super.removeTokenFrom(_from, _tokenId); - - uint256 tokenIndex = ownedTokensIndex[_tokenId]; - uint256 lastTokenIndex = safeSub(ownedTokens[_from].length, 1); - uint256 lastToken = ownedTokens[_from][lastTokenIndex]; - - ownedTokens[_from][tokenIndex] = lastToken; - ownedTokens[_from][lastTokenIndex] = 0; - // Note that this will handle single-element arrays. In that case, both tokenIndex and lastTokenIndex are going to - // be zero. Then we can make sure that we will remove _tokenId from the ownedTokens list since we are first swapping - // the lastToken to the first position, and then dropping the element placed in the last position of the list - - ownedTokens[_from].length--; - ownedTokensIndex[_tokenId] = 0; - ownedTokensIndex[lastToken] = tokenIndex; - } - - /** - * @dev Internal function to mint a new token - * Reverts if the given token ID already exists - * @param _to address the beneficiary that will own the minted token - * @param _tokenId uint256 ID of the token to be minted by the msg.sender - */ - function _mint(address _to, uint256 _tokenId) internal { - super._mint(_to, _tokenId); - - allTokensIndex[_tokenId] = allTokens.length; - allTokens.push(_tokenId); - } - - /** - * @dev Internal function to burn a specific token - * Reverts if the token does not exist - * @param _owner owner of the token to burn - * @param _tokenId uint256 ID of the token being burned by the msg.sender - */ - function _burn(address _owner, uint256 _tokenId) internal { - super._burn(_owner, _tokenId); - - // Clear metadata (if any) - if (bytes(tokenURIs[_tokenId]).length != 0) { - delete tokenURIs[_tokenId]; - } - - // Reorg all tokens array - uint256 tokenIndex = allTokensIndex[_tokenId]; - uint256 lastTokenIndex = safeSub(allTokens.length, 1); - uint256 lastToken = allTokens[lastTokenIndex]; - - allTokens[tokenIndex] = lastToken; - allTokens[lastTokenIndex] = 0; - - allTokens.length--; - allTokensIndex[_tokenId] = 0; - allTokensIndex[lastToken] = tokenIndex; - } - -} \ No newline at end of file diff --git a/packages/contracts/contracts/tokens/YesComplianceToken/YesComplianceToken.sol b/packages/contracts/contracts/tokens/YesComplianceToken/YesComplianceToken.sol deleted file mode 100644 index 65ea99d0c..000000000 --- a/packages/contracts/contracts/tokens/YesComplianceToken/YesComplianceToken.sol +++ /dev/null @@ -1,648 +0,0 @@ -pragma solidity ^0.4.24; - -import "./IYesComplianceToken.sol"; - -/** - * draft implementation of YES compliance token - * - * NOTE: i have done relatively few gas optimization tweaks (beyond using the sturctures necessary to avoid any - * linear time procedures). - * in some cases i am using a call structure which replicates some checks. this is for code clarity/security - - * i marked a few obvious ones which could be optimized for gas, but :meh: - * - * todo static owner should follow owner token? remove static owner? :security: :should: - * @author Tyson Malchow - */ -contract YesComplianceToken is YesComplianceTokenV1 { - - uint64 private constant MAX_TOKENS_PER_ENTITY = 10240; // completely arbitrary limit - uint64 private constant MAX_ENTITIES = 2**32-1; // bc using 32 bit index tracking - uint64 private constant MAX_VALIDATORS_PER_MARK = 2**32-1; // bc using 32 bit index tracking - uint64 private constant TOTAL_YES_MARKS = 255; // bc 'uint8 yes' - - // todo could shorten the entity IDs to anything 160+ to make this cheaper? - - /** @notice a single YES attestation */ - struct YesMark { - - /** @notice ISO-3166-1 country codes */ - uint16 countryCode; - - /** @notice the possibly-country-speicifc YES being marked. */ - uint8 yes; - - // 8 bits more space in this slot.. could upgrade yes to uint16? - - /** @notice the index of this mark in EntityRecord.yesMarks */ - uint32 yesMarkIdx; - - /** a list of the validator entities which have attested to this mark */ - uint256[] validatorEntityIds; - - /** @notice index of each validator entity ID in validatorEntityIds */ - mapping(uint256 => uint32) validatorEntityIdIdx; - - // uint8 entityListIdx; - } - - /** - * tracks the state for a single recognized entity - */ - struct EntityRecord { - - /** true marking this entity ID has been encountered */ - bool init; - - /** when true, this entity is effectively useless */ - bool locked; - - // 30 bits more space in this slot - - /** position of the entityId in allEntityIds */ - uint32 entityIdIdx; - - /** used for creating reliable token IDs, monotonically increasing */ - uint64 tokenIdCounter; - - /** indexed YES mark lookups */ - mapping(bytes4 => YesMark) yesMarkByKey; - - /** raw collection of all marks keys */ - bytes4[] yesMarkKeys; - - /** all tokens associated with this identity */ - uint256[] tokenIds; - - // trellis/tower connection ? - // civic connection ? - // erc725/735 connection ? - } - - /** - * @notice all fields we want to add per-token. - * - * there may never be more than just control flag, in which case it may make sense to collapse this - * to just a mapping(uint256 => bool) ? - */ - struct TokenRecord { - - /** position of the tokenId in EntityRecord.tokenIds */ - uint32 tokenIdIdx; - - /** true if this token has administrative superpowers (aka is _not_ limited) */ - bool control; - - /** true if this token cannot move */ - bool finalized; - - // 30 bits more in this slot - - // limitations: in/out? - } - - address public ownerAddress; - - mapping(uint256 => TokenRecord) public tokenRecordById; - mapping(uint256 => EntityRecord) public entityRecordById; - mapping(uint256 => uint256) public entityIdByTokenId; - - /** for entity enumeration. maximum of 2^256-1 total entities (i think we'll be ok) */ - uint256[] entityIds; - - constructor() public { - /* this space intentionally left blank */ - } - - /** - * constructor alternative: first-time initialization the contract/token (required because of upgradeability) - */ - function initialize(string _name, string _symbol) { - // require(super._symbol.length == 0 || _symbol == super._symbol); // cannot change symbol after first init bc that could fuck shit up - super.initialize(_name, _symbol); // init token info - - // grant the owner token - mint_I(ownerAddress, OWNER_ENTITY_ID, true); - - // ecosystem owner gets both owner and validator marks (self-attested) - setYes_I(OWNER_ENTITY_ID, OWNER_ENTITY_ID, 0, YESMARK_OWNER); - setYes_I(OWNER_ENTITY_ID, OWNER_ENTITY_ID, 0, YESMARK_VALIDATOR); - } - - /** - * executed in lieu of a constructor in a delegated context - */ - function _upgradeable_initialize() public { - - // some things are still tied to the owner (instead of the yesmark_owner :notsureif:) - ownerAddress = msg.sender; - } - - // YesComplianceTokenV1 Interface Methods -------------------------------------------------------------------------- - - function isYes(uint256 _validatorEntityId, address _address, uint16 _countryCode, uint8 _yes) external view returns(bool) { - return isYes_I(_validatorEntityId, _address, _countryCode, _yes); - } - - function requireYes(uint256 _validatorEntityId, address _address, uint16 _countryCode, uint8 _yes) external view { - require(isYes_I(_validatorEntityId, _address, _countryCode, _yes)); - } - - function getYes(uint256 _validatorEntityId, address _address, uint16 _countryCode) external view returns(uint8[] memory) { - if(balanceOf(_address) == 0) - return new uint8[](0); - - uint256 entityId = entityIdByTokenId[tokenOfOwnerByIndex(_address, 0)]; - EntityRecord storage e = entityRecordById[entityId]; - uint256 j = 0; - uint256 i; - - // locked always bails - if(e.locked) - return new uint8[](0); - - uint8[] memory r = new uint8[](e.yesMarkKeys.length); - - for(i = 0; i < e.yesMarkKeys.length; i++) { - YesMark storage m = e.yesMarkByKey[e.yesMarkKeys[i]]; - - // filter country code - if(m.countryCode != _countryCode) - continue; - - // filter explicit validator entity - if(_validatorEntityId > 0 - && m.validatorEntityIdIdx[_validatorEntityId] == 0 - && (m.validatorEntityIds.length == 0 || m.validatorEntityIds[0] == _validatorEntityId)) - continue; - - // matched, chyess - r[j++] = m.yes; - } - - // reduce array length - assembly { mstore(r, j) } - - return r; - } - - function mint(address _to, uint256 _entityId, bool _control) external returns (uint256) /* internally protected */{ - uint256 callerTokenId = tokenOfOwnerByIndex(msg.sender, 0); - uint256 callerEntityId = entityIdByTokenId[callerTokenId]; - - // make sure caller has a control token, at the least - require(tokenRecordById[callerTokenId].control, 'control token required'); - - // determine/validate the entity being minted for - uint256 realEntityId; - if(_entityId == 0 || _entityId == callerEntityId) { - // unspecified entity, or caller entity, can do! - realEntityId = callerEntityId; - - } else { - // otherwise make sure caller is a VALIDATOR, else fail - require(senderIsControlValidator(), 'illegal entity id'); // some duplicate checks/lookups, gas leak - realEntityId = _entityId; - } - - return mint_I(_to, realEntityId, _control); - } - - function mint(address _to, uint256 _entityId, bool _control, uint16 _countryCode, uint8[] _yes) external returns (uint256) /* internally protected */ { - // lazy warning: this is a 90% copy/paste job from the mint directly above this - - uint256 callerTokenId = tokenOfOwnerByIndex(msg.sender, 0); - uint256 callerEntityId = entityIdByTokenId[callerTokenId]; - - // make sure caller has a control token, at the least - require(tokenRecordById[callerTokenId].control, 'control token required'); - - // determine/validate the entity being minted for - uint256 realEntityId; - if(_entityId == 0 || _entityId == callerEntityId) { - // unspecified entity, or caller entity, can do! - realEntityId = callerEntityId; - - } else { - // otherwise make sure caller is a VALIDATOR, else fail - require(senderIsControlValidator()); // some duplicate checks/lookups, gas leak - realEntityId = _entityId; - } - - // mint the coin - uint256 tokenId = mint_I(_to, realEntityId, _control); - - // now set the attestations - require(_yes.length <= TOTAL_YES_MARKS); // safety - for(uint256 i = 0; i<_yes.length; i++) { - setYes_I(_entityId, _countryCode, _yes[i]); - } - - return tokenId; - } - - function getEntityId(address _address) external view returns (uint256) { - return entityIdByTokenId[tokenOfOwnerByIndex(_address, 0)]; - } - - function burn(uint256 _tokenId) external permission_control_tokenId(_tokenId) { - uint256 entityId = entityIdByTokenId[_tokenId]; - - EntityRecord storage e = entity(entityId); - TokenRecord storage t = tokenRecordById[_tokenId]; - - // remove token from entity - e.tokenIds[t.tokenIdIdx] = e.tokenIds[e.tokenIds.length - 1]; - e.tokenIds.length--; - - // update tracked index (of swapped, if present) - if(e.tokenIds.length > t.tokenIdIdx) - tokenRecordById[e.tokenIds[t.tokenIdIdx]].tokenIdIdx = t.tokenIdIdx; - - // remove token record - delete tokenRecordById[_tokenId]; - - // burn the actual token - super._burn(tokenOwner[_tokenId], _tokenId); - } - - function burnEntity(uint256 _entityId) external permission_control_entityId(_entityId) { // self-burn allowed - EntityRecord storage e = entity(_entityId); - - // burn all the tokens - for(uint256 i = 0; i < e.tokenIds.length; i++) { - uint256 tokenId = e.tokenIds[i]; - super._burn(tokenOwner[tokenId], tokenId); - } - - // clear all the marks - clearYes_I(_entityId); - - // clear out entity record - e.init = false; - e.locked = false; - e.entityIdIdx = 0; - e.tokenIdCounter = 0; - - assert(e.yesMarkKeys.length == 0); - assert(e.tokenIds.length == 0); - } - - function setYes(uint256 _entityId, uint16 _countryCode, uint8 _yes) external permission_validator { - setYes_I(_entityId, _countryCode, _yes); - } - - function clearYes(uint256 _entityId, uint16 _countryCode, uint8 _yes) external permission_validator { - require(_yes > 0); - require(_yes != 128); - - // special check against reserved country code 0 - if(_countryCode == 0) - require(senderIsEcosystemControl(), 'not authorized as ecosystem control'); // this is duplicating some things, gas leak - - EntityRecord storage e = entity(_entityId); - - uint256 callerTokenId = tokenOfOwnerByIndex(msg.sender, 0); - uint256 callerEntityId = entityIdByTokenId[callerTokenId]; - bytes4 key = yesKey(_countryCode, _yes); - - YesMark storage mark = e.yesMarkByKey[key]; - if(mark.yes == 0) - return; // not set by anyone, bail happily - - if(mark.validatorEntityIdIdx[callerEntityId] == 0 && - (mark.validatorEntityIds.length == 0 || mark.validatorEntityIds[0] != callerEntityId)) { - // set, but not by this validator, bail happily - return; - } - - clearYes_I(mark, e, callerEntityId); - } - - function clearYes(uint256 _entityId, uint16 _countryCode) external permission_validator { - // special check against 129 validator mark - if(_countryCode == 0) - require(senderIsEcosystemControl(), 'not authorized as ecosystem control (129)'); // this is duplicating some things, gas leak - - EntityRecord storage e = entity(_entityId); - - uint256 callerTokenId = tokenOfOwnerByIndex(msg.sender, 0); - uint256 callerEntityId = entityIdByTokenId[callerTokenId]; - uint256 i; - - for(i =0; i idx) - mark.validatorEntityIdIdx[mark.validatorEntityIds[idx]] = idx; - - // check if the entire mark needs deleting - if(mark.validatorEntityIds.length == 0) { - // yes, it does. swap/delete - idx = mark.yesMarkIdx; - e.yesMarkKeys[idx] = e.yesMarkKeys[e.yesMarkKeys.length - 1]; - e.yesMarkKeys.length--; - - // remap - if(e.yesMarkKeys.length > idx) - e.yesMarkByKey[e.yesMarkKeys[idx]].yesMarkIdx = idx; - - // delete mark - mark.countryCode = 0; - mark.yes = 0; - mark.yesMarkIdx = 0; - // assert(mark.validatorEntityIds.length == 0); - - return true; - } - - return false; - } - - function clearYes_I(uint256 _entityId) internal { - require(_entityId != OWNER_ENTITY_ID); - - EntityRecord storage e = entity(_entityId); - - // only ecosystem control can touch validators - if(!senderIsEcosystemControl()) - require(e.yesMarkByKey[yesKey(0, YESMARK_VALIDATOR)].yes == 0); - - uint256 callerTokenId = tokenOfOwnerByIndex(msg.sender, 0); - uint256 callerEntityId = entityIdByTokenId[callerTokenId]; - uint256 i; - - for(i =0; i 0 - || m.validatorEntityIds.length > 0 && m.validatorEntityIds[0] == _validatorEntityId; - } - - function setYes_I(uint256 _entityId, uint16 _countryCode, uint8 _yes) internal { - require(_yes > 0); - require(_yes != 128); - - // special check against 129 validator mark - if(_yes == 129) - require(senderIsEcosystemControl()); // this is duplicating some checks, gas leak - - uint256 callerTokenId = tokenOfOwnerByIndex(msg.sender, 0); - uint256 callerEntityId = entityIdByTokenId[callerTokenId]; - - setYes_I(callerEntityId, _entityId, _countryCode, _yes); - } - - function setYes_I(uint256 _validatorEntityId, uint256 _entityId, uint16 _countryCode, uint8 _yes) internal { - // assert(_yes > 0); - EntityRecord storage targetEntity = entity(_entityId); - - // locate existing mark - bytes4 key = yesKey(_countryCode, _yes); - YesMark storage mark = targetEntity.yesMarkByKey[key]; - - if(mark.yes == 0) { - require(targetEntity.yesMarkKeys.length < TOTAL_YES_MARKS); - - // new mark on the entity - mark.countryCode = _countryCode; - mark.yes = _yes; - mark.yesMarkIdx = uint32(targetEntity.yesMarkKeys.length); - targetEntity.yesMarkKeys.push(key); - - } else if(mark.validatorEntityIdIdx[_validatorEntityId] > 0 || - (mark.validatorEntityIds.length > 0 && mark.validatorEntityIds[0] == _validatorEntityId)) { - - // existing mark and the caller is already on it - /* - i'm inclined to make it do nothing in this case (instead of failing) since i'm not at this point positive how best - to distinguish error types to a caller, which would be required for a caller to know wtf to do in this case - (otherwise they need to query blockchain again) - (but that costs gas... :notsureif:) - */ - return; - } - - require(mark.validatorEntityIds.length < MAX_VALIDATORS_PER_MARK); - - // add this validator to the mark - mark.validatorEntityIdIdx[_validatorEntityId] = uint32(mark.validatorEntityIds.length); - mark.validatorEntityIds.push(_validatorEntityId); - } - - /** non-permissed internal minting impl */ - function mint_I(address _to, uint256 _entityId, bool _control) internal returns (uint256) { - EntityRecord storage e = entity(_entityId); - require(e.tokenIds.length < MAX_TOKENS_PER_ENTITY, 'token limit reached'); - require(e.tokenIdCounter < 2**64-1); // kind of ridiculous but whatever, safety first! - uint256 tokenId = uint256(keccak256(abi.encodePacked(_entityId, e.tokenIdCounter++))); - super._mint(_to, tokenId); - tokenRecordById[tokenId].tokenIdIdx = uint32(e.tokenIds.length); - tokenRecordById[tokenId].control = _control; - e.tokenIds.push(tokenId); - entityIdByTokenId[tokenId] = _entityId; - return tokenId; - } - - /** entity resolution (creation when needed) */ - function entity(uint256 _entityId) internal returns (EntityRecord storage) { - require(_entityId > 0); - EntityRecord storage e = entityRecordById[_entityId]; - if(e.init) return e; - require(entityIds.length < MAX_ENTITIES); - e.init = true; - e.entityIdIdx = uint32(entityIds.length); - entityIds.push(_entityId); - return e; - } - - /** override default addTokenTo for additional transaction limitations */ - function addTokenTo(address _to, uint256 _tokenId) internal { - uint256 entityId = entityIdByTokenId[_tokenId]; - - // ensure one owner cannot be associated with multiple entities - // NOTE: this breaks hotwallet integrations, at this point necessarily so - if(balanceOf(_to) > 0) { - uint256 prevEntityId = entityIdByTokenId[tokenOfOwnerByIndex(_to, 0)]; - require(prevEntityId == entityId, 'conflicting entities'); - } - - require(!tokenRecordById[_tokenId].finalized, 'token is finalized'); - - super.addTokenTo(_to, _tokenId); - } - - /** the sender is the same entity as the one specified */ - function senderIsEntity_ByEntityId(uint256 _entityId) internal view returns (bool) { - return _entityId == entityIdByTokenId[tokenOfOwnerByIndex(msg.sender, 0)]; - } - - /** the sender is the same entity as the one specified, and the sender is a control for that entity */ - function senderIsControl_ByEntityId(uint256 _entityId) internal view returns (bool) { - if(balanceOf(msg.sender) == 0) - return false; - uint256 tokenId = tokenOfOwnerByIndex(msg.sender, 0); - uint256 senderEntityId = entityIdByTokenId[tokenId]; - return _entityId == senderEntityId && tokenRecordById[tokenId].control; - } - - /** the sender is a non-locked validator via control token */ - function senderIsControlValidator() internal view returns (bool) { - if(balanceOf(msg.sender) == 0) - return false; - uint256 tokenId = tokenOfOwnerByIndex(msg.sender, 0); - uint256 senderEntityId = entityIdByTokenId[tokenId]; - EntityRecord storage e = entityRecordById[senderEntityId]; - return tokenRecordById[tokenId].control - && !e.locked - && entityRecordById[senderEntityId].yesMarkByKey[yesKey(0, YESMARK_VALIDATOR)].yes > 0; - } - - /** the sender is the same entity as the one tied to the token specified */ - function senderIsEntity_ByTokenId(uint256 _tokenId) internal view returns (bool) { - if(balanceOf(msg.sender) == 0) - return false; - return entityIdByTokenId[_tokenId] == entityIdByTokenId[tokenOfOwnerByIndex(msg.sender, 0)]; - } - - /** the sender is the same entity as the one tied to the token specified, and the sender is a control for that entity */ - function senderIsControl_ByTokenId(uint256 _tokenId) internal view returns (bool) { - if(balanceOf(msg.sender) == 0) - return false; - uint256 senderEntityId = entityIdByTokenId[tokenOfOwnerByIndex(msg.sender, 0)]; - return entityIdByTokenId[_tokenId] == senderEntityId && tokenRecordById[_tokenId].control; - } - - /** checks if sender is the singular ecosystem owner */ - function senderIsEcosystemControl() internal view returns (bool) { - // todo deprecate ownerAddress ?! - return msg.sender == ownerAddress || senderIsControl_ByEntityId(OWNER_ENTITY_ID); - } - - /** a key for a YES attestation mark */ - function yesKey(uint16 _countryCode, uint8 _yes) internal pure returns(bytes4) { - return bytes4(keccak256(abi.encodePacked(_countryCode, _yes))); - } - - // PERMISSIONS MODIFIERS ---------------------------------------------------------------- - - modifier permission_validator { - require(senderIsControlValidator(), 'not authorized as validator'); - _; - } - - modifier permission_super { - require(senderIsEcosystemControl(), 'not authorized as ecosystem control'); - _; - } - -// modifier permission_access_entityId(uint256 _entityId) { -// require(senderIsEcosystemControl() || senderIsEntity_ByEntityId(_entityId)); -// _; -// } - - modifier permission_control_entityId(uint256 _entityId) { - require(senderIsEcosystemControl() || senderIsControl_ByEntityId(_entityId), 'not authorized entity controller'); - _; - } - - modifier permission_access_tokenId(uint256 _tokenId) { - require(senderIsEcosystemControl() || senderIsEntity_ByTokenId(_tokenId)); - _; - } - - modifier permission_control_tokenId(uint256 _tokenId) { - require(senderIsEcosystemControl() || senderIsControl_ByTokenId(_tokenId), 'not authorized token controller'); - _; - } - -} \ No newline at end of file diff --git a/packages/contracts/test/extensions/balance_threshold_filter.ts b/packages/contracts/test/extensions/balance_threshold_filter.ts index d0e902eda..8e78ed992 100644 --- a/packages/contracts/test/extensions/balance_threshold_filter.ts +++ b/packages/contracts/test/extensions/balance_threshold_filter.ts @@ -11,7 +11,6 @@ import { TransactionReceiptWithDecodedLogs } from 'ethereum-types'; import { DummyERC20TokenContract } from '../../generated-wrappers/dummy_erc20_token'; import { ExchangeContract } from '../../generated-wrappers/exchange'; import { BalanceThresholdFilterContract } from '../../generated-wrappers/balance_threshold_filter'; -import { YesComplianceTokenContract } from '../../generated-wrappers/yes_compliance_token'; import { artifacts } from '../../src/artifacts'; import { @@ -112,6 +111,11 @@ describe.only(ContractName.BalanceThresholdFilter, () => { ] = accounts); // Create wrappers erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner); + let compliantAddresses = _.cloneDeepWith(usedAddresses); + _.remove(compliantAddresses, (address: string) => { + return address === nonCompliantAddress; + }); + const erc721Wrapper = new ERC721Wrapper(provider, compliantAddresses, owner); // Deploy ERC20 tokens const numDummyErc20ToDeploy = 3; let erc20TokenA: DummyERC20TokenContract; @@ -123,12 +127,6 @@ describe.only(ContractName.BalanceThresholdFilter, () => { defaultMakerAssetAddress = erc20TokenA.address; defaultTakerAssetAddress = erc20TokenB.address; zrxAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address); - // Deploy Yes Token - const yesTokenInstance = await YesComplianceTokenContract.deployFrom0xArtifactAsync( - artifacts.YesComplianceToken, - provider, - txDefaults, - ); // Create proxies const erc20Proxy = await erc20Wrapper.deployProxyAsync(); await erc20Wrapper.setBalancesAndAllowancesAsync(); @@ -146,14 +144,18 @@ describe.only(ContractName.BalanceThresholdFilter, () => { from: owner, }); // Deploy Compliant Forwarder - const erc721BalanceThreshold = new BigNumber(1); + const balanceThreshold = new BigNumber(1); + await erc721Wrapper.deployProxyAsync(); + const [balanceThresholdAsset] = await erc721Wrapper.deployDummyTokensAsync(); + await erc721Wrapper.setBalancesAndAllowancesAsync(); + const balance = await balanceThresholdAsset.balanceOf.callAsync(compliantTakerAddress); compliantForwarderInstance = await BalanceThresholdFilterContract.deployFrom0xArtifactAsync( artifacts.BalanceThresholdFilter, provider, txDefaults, exchangeInstance.address, - yesTokenInstance.address, - erc721BalanceThreshold + balanceThresholdAsset.address, + balanceThreshold ); // Default order parameters const defaultOrderParams = { @@ -199,43 +201,6 @@ describe.only(ContractName.BalanceThresholdFilter, () => { ); forwarderWrapper = new ForwarderWrapper(compliantForwarderContract, provider); */ - // Initialize Yes Token - await yesTokenInstance._upgradeable_initialize.sendTransactionAsync({ from: owner }); - const yesTokenName = 'YesToken'; - const yesTokenTicker = 'YEET'; - await yesTokenInstance.initialize.sendTransactionAsync(yesTokenName, yesTokenTicker, { from: owner }); - // Verify Maker / Taker - const addressesCanControlTheirToken = true; - const compliantMakerCountryCode = new BigNumber(519); - const compliantMakerYesMark = new BigNumber(1); - const compliantMakerEntityId = new BigNumber(2); - await yesTokenInstance.mint2.sendTransactionAsync( - compliantMakerAddress, - compliantMakerEntityId, - addressesCanControlTheirToken, - compliantMakerCountryCode, - [compliantMakerYesMark], - { from: owner }, - ); - const compliantTakerCountryCode = new BigNumber(519); - const compliantTakerYesMark = new BigNumber(1); - const compliantTakerEntityId = new BigNumber(2); - await yesTokenInstance.mint2.sendTransactionAsync( - compliantTakerAddress, - compliantTakerEntityId, - addressesCanControlTheirToken, - compliantTakerCountryCode, - [compliantTakerYesMark], - { from: owner }, - ); - await yesTokenInstance.mint2.sendTransactionAsync( - compliantMakerAddress2, - compliantTakerEntityId, - addressesCanControlTheirToken, - compliantTakerCountryCode, - [compliantTakerYesMark], - { from: owner }, - ); // Create Valid/Invalid orders const takerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantTakerAddress)]; takerTransactionFactory = new TransactionFactory(takerPrivateKey, exchangeInstance.address); -- cgit From 6d673ac942fc1d0df7f8eeb56168ba3a4029878e Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 10 Dec 2018 17:28:20 -0800 Subject: Exchange Selectors - sorted --- .../utils/ExchangeSelectors/ExchangeSelectors.sol | 203 ++++++++++----------- 1 file changed, 96 insertions(+), 107 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/utils/ExchangeSelectors/ExchangeSelectors.sol b/packages/contracts/contracts/utils/ExchangeSelectors/ExchangeSelectors.sol index d4c9bef28..c361fd075 100644 --- a/packages/contracts/contracts/utils/ExchangeSelectors/ExchangeSelectors.sol +++ b/packages/contracts/contracts/utils/ExchangeSelectors/ExchangeSelectors.sol @@ -20,143 +20,132 @@ pragma solidity 0.4.24; contract ExchangeSelectors { - // filled - bytes4 constant filledSelector = 0x288cdc91; - bytes4 constant filledSelectorGenerator = bytes4(keccak256('filled(bytes32)')); - // batchFillOrders - bytes4 constant batchFillOrdersSelector = 0x297bb70b; - bytes4 constant batchFillOrdersSelectorGenerator = bytes4(keccak256('batchFillOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256[],bytes[])')); + // allowedValidators + bytes4 constant allowedValidatorsSelector = 0x7b8e3514; + bytes4 constant allowedValidatorsSelectorGenerator = bytes4(keccak256('allowedValidators(address,address)')); - // cancelled - bytes4 constant cancelledSelector = 0x2ac12622; - bytes4 constant cancelledSelectorGenerator = bytes4(keccak256('cancelled(bytes32)')); + // assetProxies + bytes4 constant assetProxiesSelector = 0x3fd3c997; + bytes4 constant assetProxiesSelectorGenerator = bytes4(keccak256('assetProxies(bytes4)')); - // preSign - bytes4 constant preSignSelector = 0x3683ef8e; - bytes4 constant preSignSelectorGenerator = bytes4(keccak256('preSign(bytes32,address,bytes)')); + // batchCancelOrders + bytes4 constant batchCancelOrdersSelector = 0x4ac14782; + bytes4 constant batchCancelOrdersSelectorGenerator = bytes4(keccak256('batchCancelOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[])')); - // matchOrders - bytes4 constant matchOrdersSelector = 0x3c28d861; - bytes4 constant matchOrdersSelectorGenerator = bytes4(keccak256('matchOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),(address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),bytes,bytes)')); + // batchFillOrKillOrders + bytes4 constant batchFillOrKillOrdersSelector = 0x4d0ae546; + bytes4 constant batchFillOrKillOrdersSelectorGenerator = bytes4(keccak256('batchFillOrKillOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256[],bytes[])')); - // fillOrderNoThrow - bytes4 constant fillOrderNoThrowSelector = 0x3e228bae; - bytes4 constant fillOrderNoThrowSelectorGenerator = bytes4(keccak256('fillOrderNoThrow((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),uint256,bytes)')); + // batchFillOrders + bytes4 constant batchFillOrdersSelector = 0x297bb70b; + bytes4 constant batchFillOrdersSelectorGenerator = bytes4(keccak256('batchFillOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256[],bytes[])')); - // assetProxies - bytes4 constant assetProxiesSelector = 0x3fd3c997; - bytes4 constant assetProxiesSelectorGenerator = bytes4(keccak256('assetProxies(bytes4)')); + // batchFillOrdersNoThrow + bytes4 constant batchFillOrdersNoThrowSelector = 0x50dde190; + bytes4 constant batchFillOrdersNoThrowSelectorGenerator = bytes4(keccak256('batchFillOrdersNoThrow((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256[],bytes[])')); - // batchCancelOrders - bytes4 constant batchCancelOrdersSelector = 0x4ac14782; - bytes4 constant batchCancelOrdersSelectorGenerator = bytes4(keccak256('batchCancelOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[])')); + // cancelOrder + bytes4 constant cancelOrderSelector = 0xd46b02c3; + bytes4 constant cancelOrderSelectorGenerator = bytes4(keccak256('cancelOrder((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes))')); - // batchFillOrKillOrders - bytes4 constant batchFillOrKillOrdersSelector = 0x4d0ae546; - bytes4 constant batchFillOrKillOrdersSelectorGenerator = bytes4(keccak256('batchFillOrKillOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256[],bytes[])')); + // cancelOrdersUpTo + bytes4 constant cancelOrdersUpToSelector = 0x4f9559b1; + bytes4 constant cancelOrdersUpToSelectorGenerator = bytes4(keccak256('cancelOrdersUpTo(uint256)')); - // cancelOrdersUpTo - bytes4 constant cancelOrdersUpToSelector = 0x4f9559b1; - bytes4 constant cancelOrdersUpToSelectorGenerator = bytes4(keccak256('cancelOrdersUpTo(uint256)')); + // cancelled + bytes4 constant cancelledSelector = 0x2ac12622; + bytes4 constant cancelledSelectorGenerator = bytes4(keccak256('cancelled(bytes32)')); - // batchFillOrdersNoThrow - bytes4 constant batchFillOrdersNoThrowSelector = 0x50dde190; - bytes4 constant batchFillOrdersNoThrowSelectorGenerator = bytes4(keccak256('batchFillOrdersNoThrow((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256[],bytes[])')); + // currentContextAddress + bytes4 constant currentContextAddressSelector = 0xeea086ba; + bytes4 constant currentContextAddressSelectorGenerator = bytes4(keccak256('currentContextAddress()')); - // getAssetProxy - bytes4 constant getAssetProxySelector = 0x60704108; - bytes4 constant getAssetProxySelectorGenerator = bytes4(keccak256('getAssetProxy(bytes4)')); + // executeTransaction + bytes4 constant executeTransactionSelector = 0xbfc8bfce; + bytes4 constant executeTransactionSelectorGenerator = bytes4(keccak256('executeTransaction(uint256,address,bytes,bytes)')); - // transactions - bytes4 constant transactionsSelector = 0x642f2eaf; - bytes4 constant transactionsSelectorGenerator = bytes4(keccak256('transactions(bytes32)')); + // fillOrKillOrder + bytes4 constant fillOrKillOrderSelector = 0x64a3bc15; + bytes4 constant fillOrKillOrderSelectorGenerator = bytes4(keccak256('fillOrKillOrder((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),uint256,bytes)')); - // fillOrKillOrder - bytes4 constant fillOrKillOrderSelector = 0x64a3bc15; - bytes4 constant fillOrKillOrderSelectorGenerator = bytes4(keccak256('fillOrKillOrder((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),uint256,bytes)')); + // fillOrder + bytes4 constant fillOrderSelector = 0xb4be83d5; + bytes4 constant fillOrderSelectorGenerator = bytes4(keccak256('fillOrder((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),uint256,bytes)')); - // setSignatureValidatorApproval - bytes4 constant setSignatureValidatorApprovalSelector = 0x77fcce68; - bytes4 constant setSignatureValidatorApprovalSelectorGenerator = bytes4(keccak256('setSignatureValidatorApproval(address,bool)')); + // fillOrderNoThrow + bytes4 constant fillOrderNoThrowSelector = 0x3e228bae; + bytes4 constant fillOrderNoThrowSelectorGenerator = bytes4(keccak256('fillOrderNoThrow((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),uint256,bytes)')); - // allowedValidators - bytes4 constant allowedValidatorsSelector = 0x7b8e3514; - bytes4 constant allowedValidatorsSelectorGenerator = bytes4(keccak256('allowedValidators(address,address)')); + // filled + bytes4 constant filledSelector = 0x288cdc91; + bytes4 constant filledSelectorGenerator = bytes4(keccak256('filled(bytes32)')); - // marketSellOrders - bytes4 constant marketSellOrdersSelector = 0x7e1d9808; - bytes4 constant marketSellOrdersSelectorGenerator = bytes4(keccak256('marketSellOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256,bytes[])')); + // getAssetProxy + bytes4 constant getAssetProxySelector = 0x60704108; + bytes4 constant getAssetProxySelectorGenerator = bytes4(keccak256('getAssetProxy(bytes4)')); - // getOrdersInfo - bytes4 constant getOrdersInfoSelector = 0x7e9d74dc; - bytes4 constant getOrdersInfoSelectorGenerator = bytes4(keccak256('getOrdersInfo((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[])')); + // getOrderInfo + bytes4 constant getOrderInfoSelector = 0xc75e0a81; + bytes4 constant getOrderInfoSelectorGenerator = bytes4(keccak256('getOrderInfo((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes))')); - // preSigned - bytes4 constant preSignedSelector = 0x82c174d0; - bytes4 constant preSignedSelectorGenerator = bytes4(keccak256('preSigned(bytes32,address)')); + // getOrdersInfo + bytes4 constant getOrdersInfoSelector = 0x7e9d74dc; + bytes4 constant getOrdersInfoSelectorGenerator = bytes4(keccak256('getOrdersInfo((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[])')); - // owner - bytes4 constant ownerSelector = 0x8da5cb5b; - bytes4 constant ownerSelectorGenerator = bytes4(keccak256('owner()')); + // isValidSignature + bytes4 constant isValidSignatureSelector = 0x93634702; + bytes4 constant isValidSignatureSelectorGenerator = bytes4(keccak256('isValidSignature(bytes32,address,bytes)')); - // isValidSignature - bytes4 constant isValidSignatureSelector = 0x93634702; - bytes4 constant isValidSignatureSelectorGenerator = bytes4(keccak256('isValidSignature(bytes32,address,bytes)')); + // marketBuyOrders + bytes4 constant marketBuyOrdersSelector = 0xe5fa431b; + bytes4 constant marketBuyOrdersSelectorGenerator = bytes4(keccak256('marketBuyOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256,bytes[])')); - // marketBuyOrdersNoThrow - bytes4 constant marketBuyOrdersNoThrowSelector = 0xa3e20380; - bytes4 constant marketBuyOrdersNoThrowSelectorGenerator = bytes4(keccak256('marketBuyOrdersNoThrow((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256,bytes[])')); + // marketBuyOrdersNoThrow + bytes4 constant marketBuyOrdersNoThrowSelector = 0xa3e20380; + bytes4 constant marketBuyOrdersNoThrowSelectorGenerator = bytes4(keccak256('marketBuyOrdersNoThrow((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256,bytes[])')); - // fillOrder - bytes4 constant fillOrderSelector = 0xb4be83d5; - bytes4 constant fillOrderSelectorGenerator = bytes4(keccak256('fillOrder((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),uint256,bytes)')); + // marketSellOrders + bytes4 constant marketSellOrdersSelector = 0x7e1d9808; + bytes4 constant marketSellOrdersSelectorGenerator = bytes4(keccak256('marketSellOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256,bytes[])')); - // executeTransaction - bytes4 constant executeTransactionSelector = 0xbfc8bfce; - bytes4 constant executeTransactionSelectorGenerator = bytes4(keccak256('executeTransaction(uint256,address,bytes,bytes)')); + // marketSellOrdersNoThrow + bytes4 constant marketSellOrdersNoThrowSelector = 0xdd1c7d18; + bytes4 constant marketSellOrdersNoThrowSelectorGenerator = bytes4(keccak256('marketSellOrdersNoThrow((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256,bytes[])')); - // registerAssetProxy - bytes4 constant registerAssetProxySelector = 0xc585bb93; - bytes4 constant registerAssetProxySelectorGenerator = bytes4(keccak256('registerAssetProxy(address)')); + // matchOrders + bytes4 constant matchOrdersSelector = 0x3c28d861; + bytes4 constant matchOrdersSelectorGenerator = bytes4(keccak256('matchOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),(address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),bytes,bytes)')); - // getOrderInfo - bytes4 constant getOrderInfoSelector = 0xc75e0a81; - bytes4 constant getOrderInfoSelectorGenerator = bytes4(keccak256('getOrderInfo((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes))')); + // orderEpoch + bytes4 constant orderEpochSelector = 0xd9bfa73e; + bytes4 constant orderEpochSelectorGenerator = bytes4(keccak256('orderEpoch(address,address)')); - // cancelOrder - bytes4 constant cancelOrderSelector = 0xd46b02c3; - bytes4 constant cancelOrderSelectorGenerator = bytes4(keccak256('cancelOrder((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes))')); + // owner + bytes4 constant ownerSelector = 0x8da5cb5b; + bytes4 constant ownerSelectorGenerator = bytes4(keccak256('owner()')); - // orderEpoch - bytes4 constant orderEpochSelector = 0xd9bfa73e; - bytes4 constant orderEpochSelectorGenerator = bytes4(keccak256('orderEpoch(address,address)')); + // preSign + bytes4 constant preSignSelector = 0x3683ef8e; + bytes4 constant preSignSelectorGenerator = bytes4(keccak256('preSign(bytes32,address,bytes)')); - // ZRX_ASSET_DATA - bytes4 constant ZRX_ASSET_DATASelector = 0xdb123b1a; - bytes4 constant ZRX_ASSET_DATASelectorGenerator = bytes4(keccak256('ZRX_ASSET_DATA()')); + // preSigned + bytes4 constant preSignedSelector = 0x82c174d0; + bytes4 constant preSignedSelectorGenerator = bytes4(keccak256('preSigned(bytes32,address)')); - // marketSellOrdersNoThrow - bytes4 constant marketSellOrdersNoThrowSelector = 0xdd1c7d18; - bytes4 constant marketSellOrdersNoThrowSelectorGenerator = bytes4(keccak256('marketSellOrdersNoThrow((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256,bytes[])')); + // registerAssetProxy + bytes4 constant registerAssetProxySelector = 0xc585bb93; + bytes4 constant registerAssetProxySelectorGenerator = bytes4(keccak256('registerAssetProxy(address)')); - // EIP712_DOMAIN_HASH - bytes4 constant EIP712_DOMAIN_HASHSelector = 0xe306f779; - bytes4 constant EIP712_DOMAIN_HASHSelectorGenerator = bytes4(keccak256('EIP712_DOMAIN_HASH()')); + // setSignatureValidatorApproval + bytes4 constant setSignatureValidatorApprovalSelector = 0x77fcce68; + bytes4 constant setSignatureValidatorApprovalSelectorGenerator = bytes4(keccak256('setSignatureValidatorApproval(address,bool)')); - // marketBuyOrders - bytes4 constant marketBuyOrdersSelector = 0xe5fa431b; - bytes4 constant marketBuyOrdersSelectorGenerator = bytes4(keccak256('marketBuyOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256,bytes[])')); + // transactions + bytes4 constant transactionsSelector = 0x642f2eaf; + bytes4 constant transactionsSelectorGenerator = bytes4(keccak256('transactions(bytes32)')); - // currentContextAddress - bytes4 constant currentContextAddressSelector = 0xeea086ba; - bytes4 constant currentContextAddressSelectorGenerator = bytes4(keccak256('currentContextAddress()')); - - // transferOwnership - bytes4 constant transferOwnershipSelector = 0xf2fde38b; - bytes4 constant transferOwnershipSelectorGenerator = bytes4(keccak256('transferOwnership(address)')); - - // VERSION - bytes4 constant VERSIONSelector = 0xffa1ad74; - bytes4 constant VERSIONSelectorGenerator = bytes4(keccak256('VERSION()')); + // transferOwnership + bytes4 constant transferOwnershipSelector = 0xf2fde38b; + bytes4 constant transferOwnershipSelectorGenerator = bytes4(keccak256('transferOwnership(address)')); } \ No newline at end of file -- cgit From 8799f9bb90c6bcfffc74550d74ed87422467f9e1 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 10 Dec 2018 18:25:19 -0800 Subject: Test for ERC20 balance threshold --- .../test/extensions/balance_threshold_filter.ts | 260 ++++++++++++--------- 1 file changed, 146 insertions(+), 114 deletions(-) (limited to 'packages') diff --git a/packages/contracts/test/extensions/balance_threshold_filter.ts b/packages/contracts/test/extensions/balance_threshold_filter.ts index 8e78ed992..c4723e7aa 100644 --- a/packages/contracts/test/extensions/balance_threshold_filter.ts +++ b/packages/contracts/test/extensions/balance_threshold_filter.ts @@ -1,6 +1,6 @@ import { BlockchainLifecycle } from '@0x/dev-utils'; import { assetDataUtils } from '@0x/order-utils'; -import { RevertReason, SignedOrder } from '@0x/types'; +import { RevertReason, SignedOrder, Order } from '@0x/types'; import { BigNumber } from '@0x/utils'; import { Web3Wrapper } from '@0x/web3-wrapper'; import * as chai from 'chai'; @@ -64,9 +64,10 @@ describe.only(ContractName.BalanceThresholdFilter, () => { let nonCompliantOrderFactory: OrderFactory; let erc20Wrapper: ERC20Wrapper; let erc20Balances: ERC20BalancesByOwner; - let takerBalanceThresholdWrapper: BalanceThresholdWrapper; - let makerBalanceThresholdWrapper: BalanceThresholdWrapper; - let nonCompliantBalanceThresholdWrapper: BalanceThresholdWrapper; + let erc20TakerBalanceThresholdWrapper: BalanceThresholdWrapper; + let erc721TakerBalanceThresholdWrapper: BalanceThresholdWrapper; + let erc721MakerBalanceThresholdWrapper: BalanceThresholdWrapper; + let erc721NonCompliantBalanceThresholdWrapper: BalanceThresholdWrapper; let takerTransactionFactory: TransactionFactory; let makerTransactionFactory: TransactionFactory; @@ -77,11 +78,14 @@ describe.only(ContractName.BalanceThresholdFilter, () => { let logDecoder: LogDecoder; let exchangeInternals: TestExchangeInternalsContract; + let defaultOrderParams: Partial; + const takerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(500), DECIMALS_DEFAULT); const makerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(1000), DECIMALS_DEFAULT); const takerAssetFillAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(250), DECIMALS_DEFAULT); - let compliantForwarderInstance: BalanceThresholdFilterContract; + let erc721CompliantForwarderInstance: BalanceThresholdFilterContract; + let erc20CompliantForwarderInstance: BalanceThresholdFilterContract; const assertValidatedAddressesLog = async (txReceipt: TransactionReceiptWithDecodedLogs, expectedValidatedAddresses: string[]) => { expect(txReceipt.logs.length).to.be.gte(1); @@ -117,10 +121,11 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); const erc721Wrapper = new ERC721Wrapper(provider, compliantAddresses, owner); // Deploy ERC20 tokens - const numDummyErc20ToDeploy = 3; + const numDummyErc20ToDeploy = 4; let erc20TokenA: DummyERC20TokenContract; let erc20TokenB: DummyERC20TokenContract; - [erc20TokenA, erc20TokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync( + let erc20BalanceThresholdAsset: DummyERC20TokenContract; + [erc20TokenA, erc20TokenB, zrxToken, erc20BalanceThresholdAsset] = await erc20Wrapper.deployDummyTokensAsync( numDummyErc20ToDeploy, constants.DUMMY_TOKEN_DECIMALS, ); @@ -144,21 +149,30 @@ describe.only(ContractName.BalanceThresholdFilter, () => { from: owner, }); // Deploy Compliant Forwarder - const balanceThreshold = new BigNumber(1); + const erc721alanceThreshold = new BigNumber(1); await erc721Wrapper.deployProxyAsync(); - const [balanceThresholdAsset] = await erc721Wrapper.deployDummyTokensAsync(); + const [erc721BalanceThresholdAsset] = await erc721Wrapper.deployDummyTokensAsync(); await erc721Wrapper.setBalancesAndAllowancesAsync(); - const balance = await balanceThresholdAsset.balanceOf.callAsync(compliantTakerAddress); - compliantForwarderInstance = await BalanceThresholdFilterContract.deployFrom0xArtifactAsync( + erc721CompliantForwarderInstance = await BalanceThresholdFilterContract.deployFrom0xArtifactAsync( + artifacts.BalanceThresholdFilter, + provider, + txDefaults, + exchangeInstance.address, + erc721BalanceThresholdAsset.address, + erc721alanceThreshold + ); + const erc20BalanceThreshold = Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 10); + erc20CompliantForwarderInstance = await BalanceThresholdFilterContract.deployFrom0xArtifactAsync( artifacts.BalanceThresholdFilter, provider, txDefaults, exchangeInstance.address, - balanceThresholdAsset.address, - balanceThreshold + erc20BalanceThresholdAsset.address, + erc20BalanceThreshold ); + // Default order parameters - const defaultOrderParams = { + defaultOrderParams = { exchangeAddress: exchangeInstance.address, feeRecipientAddress, makerAssetData: assetDataUtils.encodeERC20AssetData(defaultMakerAssetAddress), @@ -167,7 +181,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { takerAssetAmount, makerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), DECIMALS_DEFAULT), takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(150), DECIMALS_DEFAULT), - senderAddress: compliantForwarderInstance.address, + senderAddress: erc721CompliantForwarderInstance.address, }; const defaultOrderParams1 = { makerAddress: compliantMakerAddress, @@ -192,20 +206,11 @@ describe.only(ContractName.BalanceThresholdFilter, () => { defaultOrderParams, }; nonCompliantOrderFactory = new OrderFactory(nonCompliantPrivateKey, defaultNonCompliantOrderParams); - - /* - const compliantForwarderContract = new BalanceThresholdFilterContract( - compliantForwarderInstance.abi, - compliantForwarderInstance.address, - provider, - ); - forwarderWrapper = new ForwarderWrapper(compliantForwarderContract, provider); - */ // Create Valid/Invalid orders const takerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantTakerAddress)]; takerTransactionFactory = new TransactionFactory(takerPrivateKey, exchangeInstance.address); compliantSignedOrder = await orderFactory.newSignedOrderAsync({ - senderAddress: compliantForwarderInstance.address, + senderAddress: erc721CompliantForwarderInstance.address, }); const compliantSignedOrderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress( compliantSignedOrder, @@ -219,35 +224,12 @@ describe.only(ContractName.BalanceThresholdFilter, () => { compliantSignedOrderWithoutExchangeAddressData, ); - /* - _.each(exchangeInstance.abi, (abiDefinition: AbiDefinition) => { - try { - const method = new Method(abiDefinition as MethodAbi); - console.log(method.getSignature()); - if (!method.getSignature().startsWith('matchOrders')) { - return; - } - console.log(`FOUND IT`); - const signedOrderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress( - compliantSignedOrder, - ); - const args = [signedOrderWithoutExchangeAddress, signedOrderWithoutExchangeAddress, compliantSignedOrder.signature, compliantSignedOrder.signature]; - console.log(method.encode(args, {annotate: true})); - //console.log('\n', `// ${method.getDataItem().name}`); - //console.log(`bytes4 constant ${method.getDataItem().name}Selector = ${method.getSelector()};`); - //console.log(`bytes4 constant ${method.getDataItem().name}SelectorGenerator = byes4(keccak256('${method.getSignature()}'));`); - } catch(e) { - console.log(`encoding failed: ${e}`); - } - }); - throw new Error(`w`);*/ logDecoder = new LogDecoder(web3Wrapper); - takerBalanceThresholdWrapper = new BalanceThresholdWrapper(compliantForwarderInstance, exchangeInstance, new TransactionFactory(takerPrivateKey, exchangeInstance.address), provider); - makerBalanceThresholdWrapper = new BalanceThresholdWrapper(compliantForwarderInstance, exchangeInstance, new TransactionFactory(makerPrivateKey, exchangeInstance.address), provider); + erc20TakerBalanceThresholdWrapper = new BalanceThresholdWrapper(erc20CompliantForwarderInstance, exchangeInstance, new TransactionFactory(takerPrivateKey, exchangeInstance.address), provider); + erc721TakerBalanceThresholdWrapper = new BalanceThresholdWrapper(erc721CompliantForwarderInstance, exchangeInstance, new TransactionFactory(takerPrivateKey, exchangeInstance.address), provider); + erc721MakerBalanceThresholdWrapper = new BalanceThresholdWrapper(erc721CompliantForwarderInstance, exchangeInstance, new TransactionFactory(makerPrivateKey, exchangeInstance.address), provider); + erc721NonCompliantBalanceThresholdWrapper = new BalanceThresholdWrapper(erc721CompliantForwarderInstance, exchangeInstance, new TransactionFactory(nonCompliantPrivateKey, exchangeInstance.address), provider); - nonCompliantBalanceThresholdWrapper = new BalanceThresholdWrapper(compliantForwarderInstance, exchangeInstance, new TransactionFactory(nonCompliantPrivateKey, exchangeInstance.address), provider); - - // Instantiate internal exchange contract exchangeInternals = await TestExchangeInternalsContract.deployFrom0xArtifactAsync( artifacts.TestExchangeInternals, @@ -262,7 +244,57 @@ describe.only(ContractName.BalanceThresholdFilter, () => { await blockchainLifecycle.revertAsync(); }); - describe('General Sanity Checks', () => { + describe.only('General Sanity Checks', () => { + beforeEach(async () => { + erc20Balances = await erc20Wrapper.getBalancesAsync(); + compliantSignedOrder = await orderFactory.newSignedOrderAsync(); + compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); + }); + it.only('should transfer the correct amounts and validate both maker/taker when both maker and taker exceed the balance threshold of an ERC20 token', async () => { + const compliantSignedOrderERC20Sender = await orderFactory.newSignedOrderAsync({ + ... + defaultOrderParams, + makerAddress: compliantMakerAddress, + senderAddress: erc20TakerBalanceThresholdWrapper.getBalanceThresholdAddress(), + }); + // Execute a valid fill + const txReceipt = await erc20TakerBalanceThresholdWrapper.fillOrderAsync(compliantSignedOrderERC20Sender, compliantTakerAddress, {takerAssetFillAmount}); + // Assert validated addresses + const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedFillOrderTx.signerAddress]; + assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); + // Check balances + const newBalances = await erc20Wrapper.getBalancesAsync(); + const makerAssetFillAmount = takerAssetFillAmount + .times(compliantSignedOrder.makerAssetAmount) + .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); + const makerFeePaid = compliantSignedOrder.makerFee + .times(makerAssetFillAmount) + .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); + const takerFeePaid = compliantSignedOrder.takerFee + .times(makerAssetFillAmount) + .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); + expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), + ); + expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount), + ); + expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount), + ); + expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), + ); + expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)), + ); + }); it('should revert if the signed transaction is not intended for supported', async () => { // Create signed order without the fillOrder function selector const txDataBuf = ethUtil.toBuffer(compliantSignedFillOrderTx.data); @@ -273,7 +305,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { const txDataBufWithBadSelector = Buffer.concat([badSelectorBuf, txDataBufMinusSelector]); const txDataBufWithBadSelectorHex = ethUtil.bufferToHex(txDataBufWithBadSelector); // Call compliant forwarder - return expectTransactionFailedWithoutReasonAsync(compliantForwarderInstance.executeTransaction.sendTransactionAsync( + return expectTransactionFailedWithoutReasonAsync(erc721CompliantForwarderInstance.executeTransaction.sendTransactionAsync( compliantSignedFillOrderTx.salt, compliantSignedFillOrderTx.signerAddress, txDataBufWithBadSelectorHex, @@ -298,7 +330,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { signedOrderWithoutExchangeAddressData, ); // Call compliant forwarder - return expectTransactionFailedWithoutReasonAsync(compliantForwarderInstance.executeTransaction.sendTransactionAsync( + return expectTransactionFailedWithoutReasonAsync(erc721CompliantForwarderInstance.executeTransaction.sendTransactionAsync( signedFillOrderTx.salt, signedFillOrderTx.signerAddress, signedFillOrderTx.data, @@ -318,7 +350,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { // Execute a valid fill const orders = [compliantSignedOrder, compliantSignedOrder2]; const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; - const txReceipt = await takerBalanceThresholdWrapper.batchFillOrdersAsync(orders, compliantTakerAddress, {takerAssetFillAmounts}); + const txReceipt = await erc721TakerBalanceThresholdWrapper.batchFillOrdersAsync(orders, compliantTakerAddress, {takerAssetFillAmounts}); // Assert validated addresses const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); @@ -380,7 +412,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; // Execute transaction return expectTransactionFailedAsync( - takerBalanceThresholdWrapper.batchFillOrdersAsync( + erc721TakerBalanceThresholdWrapper.batchFillOrdersAsync( orders, compliantTakerAddress, {takerAssetFillAmounts} @@ -392,7 +424,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { const orders = [compliantSignedOrder, compliantSignedOrder2]; const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; return expectTransactionFailedAsync( - nonCompliantBalanceThresholdWrapper.batchFillOrdersAsync( + erc721NonCompliantBalanceThresholdWrapper.batchFillOrdersAsync( orders, nonCompliantAddress, {takerAssetFillAmounts} @@ -412,7 +444,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { // Execute a valid fill const orders = [compliantSignedOrder, compliantSignedOrder2]; const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; - const txReceipt = await takerBalanceThresholdWrapper.batchFillOrdersNoThrowAsync(orders, compliantTakerAddress, {takerAssetFillAmounts}); + const txReceipt = await erc721TakerBalanceThresholdWrapper.batchFillOrdersNoThrowAsync(orders, compliantTakerAddress, {takerAssetFillAmounts}); // Assert validated addresses const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); @@ -474,7 +506,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; // Execute transaction return expectTransactionFailedAsync( - takerBalanceThresholdWrapper.batchFillOrdersNoThrowAsync( + erc721TakerBalanceThresholdWrapper.batchFillOrdersNoThrowAsync( orders, compliantTakerAddress, {takerAssetFillAmounts} @@ -486,7 +518,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { const orders = [compliantSignedOrder, compliantSignedOrder2]; const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; return expectTransactionFailedAsync( - nonCompliantBalanceThresholdWrapper.batchFillOrdersNoThrowAsync( + erc721NonCompliantBalanceThresholdWrapper.batchFillOrdersNoThrowAsync( orders, nonCompliantAddress, {takerAssetFillAmounts} @@ -506,7 +538,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { // Execute a valid fill const orders = [compliantSignedOrder, compliantSignedOrder2]; const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; - const txReceipt = await takerBalanceThresholdWrapper.batchFillOrKillOrdersAsync(orders, compliantTakerAddress, {takerAssetFillAmounts}); + const txReceipt = await erc721TakerBalanceThresholdWrapper.batchFillOrKillOrdersAsync(orders, compliantTakerAddress, {takerAssetFillAmounts}); // Assert validated addresses const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); @@ -568,7 +600,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; // Execute transaction return expectTransactionFailedAsync( - takerBalanceThresholdWrapper.batchFillOrKillOrdersAsync( + erc721TakerBalanceThresholdWrapper.batchFillOrKillOrdersAsync( orders, compliantTakerAddress, {takerAssetFillAmounts} @@ -580,7 +612,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { const orders = [compliantSignedOrder, compliantSignedOrder2]; const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; return expectTransactionFailedAsync( - nonCompliantBalanceThresholdWrapper.batchFillOrKillOrdersAsync( + erc721NonCompliantBalanceThresholdWrapper.batchFillOrKillOrdersAsync( orders, nonCompliantAddress, {takerAssetFillAmounts} @@ -593,7 +625,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { const orders = [compliantSignedOrder, compliantSignedOrder2]; const takerAssetFillAmounts = [takerAssetFillAmount, tooBigTakerAssetFillAmount]; return expectTransactionFailedAsync( - takerBalanceThresholdWrapper.batchFillOrKillOrdersAsync( + erc721TakerBalanceThresholdWrapper.batchFillOrKillOrdersAsync( orders, compliantTakerAddress, {takerAssetFillAmounts} @@ -610,7 +642,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); it('should transfer the correct amounts and validate both maker/taker when both maker and taker meet the balance threshold', async () => { // Execute a valid fill - const txReceipt = await takerBalanceThresholdWrapper.fillOrderAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount}); + const txReceipt = await erc721TakerBalanceThresholdWrapper.fillOrderAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount}); // Assert validated addresses const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedFillOrderTx.signerAddress]; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); @@ -650,12 +682,12 @@ describe.only(ContractName.BalanceThresholdFilter, () => { it('should revert if maker does not meet the balance threshold', async () => { // Create signed order with non-compliant maker address const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - senderAddress: compliantForwarderInstance.address, + senderAddress: erc721CompliantForwarderInstance.address, makerAddress: nonCompliantAddress }); // Execute transaction return expectTransactionFailedAsync( - takerBalanceThresholdWrapper.fillOrderAsync( + erc721TakerBalanceThresholdWrapper.fillOrderAsync( signedOrderWithBadMakerAddress, compliantTakerAddress, {takerAssetFillAmount} @@ -665,7 +697,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); it('should revert if taker does not meet the balance threshold', async () => { return expectTransactionFailedAsync( - nonCompliantBalanceThresholdWrapper.fillOrderAsync( + erc721NonCompliantBalanceThresholdWrapper.fillOrderAsync( compliantSignedOrder, nonCompliantAddress, {takerAssetFillAmount} @@ -682,7 +714,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); it('should transfer the correct amounts and validate both maker/taker when both maker and taker meet the balance threshold', async () => { // Execute a valid fill - const txReceipt = await takerBalanceThresholdWrapper.fillOrderNoThrowAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount}); + const txReceipt = await erc721TakerBalanceThresholdWrapper.fillOrderNoThrowAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount}); // Assert validated addresses const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedFillOrderTx.signerAddress]; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); @@ -722,12 +754,12 @@ describe.only(ContractName.BalanceThresholdFilter, () => { it('should revert if maker does not meet the balance threshold', async () => { // Create signed order with non-compliant maker address const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - senderAddress: compliantForwarderInstance.address, + senderAddress: erc721CompliantForwarderInstance.address, makerAddress: nonCompliantAddress }); // Execute transaction return expectTransactionFailedAsync( - takerBalanceThresholdWrapper.fillOrderNoThrowAsync( + erc721TakerBalanceThresholdWrapper.fillOrderNoThrowAsync( signedOrderWithBadMakerAddress, compliantTakerAddress, {takerAssetFillAmount} @@ -737,7 +769,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); it('should revert if taker does not meet the balance threshold', async () => { return expectTransactionFailedAsync( - nonCompliantBalanceThresholdWrapper.fillOrderNoThrowAsync( + erc721NonCompliantBalanceThresholdWrapper.fillOrderNoThrowAsync( compliantSignedOrder, nonCompliantAddress, {takerAssetFillAmount} @@ -755,7 +787,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { it('should transfer the correct amounts and validate both maker/taker when both maker and taker meet the balance threshold', async () => { // Execute a valid fill const takerAssetFillAmount_ = compliantSignedOrder.takerAssetAmount; - const txReceipt = await takerBalanceThresholdWrapper.fillOrKillOrderAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount: takerAssetFillAmount_}); + const txReceipt = await erc721TakerBalanceThresholdWrapper.fillOrKillOrderAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount: takerAssetFillAmount_}); // Assert validated addresses const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedFillOrderTx.signerAddress]; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); @@ -795,12 +827,12 @@ describe.only(ContractName.BalanceThresholdFilter, () => { it('should revert if maker does not meet the balance threshold', async () => { // Create signed order with non-compliant maker address const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - senderAddress: compliantForwarderInstance.address, + senderAddress: erc721CompliantForwarderInstance.address, makerAddress: nonCompliantAddress }); // Execute transaction return expectTransactionFailedAsync( - takerBalanceThresholdWrapper.fillOrKillOrderAsync( + erc721TakerBalanceThresholdWrapper.fillOrKillOrderAsync( signedOrderWithBadMakerAddress, compliantTakerAddress, {takerAssetFillAmount} @@ -810,7 +842,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); it('should revert if taker does not meet the balance threshold', async () => { return expectTransactionFailedAsync( - nonCompliantBalanceThresholdWrapper.fillOrKillOrderAsync( + erc721NonCompliantBalanceThresholdWrapper.fillOrKillOrderAsync( compliantSignedOrder, nonCompliantAddress, {takerAssetFillAmount} @@ -821,7 +853,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { it('should revert if takerAssetFillAmount is not fully filled', async () => { const tooBigTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.times(2); return expectTransactionFailedAsync( - takerBalanceThresholdWrapper.fillOrKillOrderAsync( + erc721TakerBalanceThresholdWrapper.fillOrKillOrderAsync( compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount: tooBigTakerAssetFillAmount} @@ -841,7 +873,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { // Execute a valid fill const orders = [compliantSignedOrder, compliantSignedOrder2]; const cumulativeTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.plus(takerAssetFillAmount); - const txReceipt = await takerBalanceThresholdWrapper.marketSellOrdersAsync(orders, compliantTakerAddress, {takerAssetFillAmount: cumulativeTakerAssetFillAmount}); + const txReceipt = await erc721TakerBalanceThresholdWrapper.marketSellOrdersAsync(orders, compliantTakerAddress, {takerAssetFillAmount: cumulativeTakerAssetFillAmount}); // Assert validated addresses const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); @@ -901,7 +933,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; // Execute transaction return expectTransactionFailedAsync( - takerBalanceThresholdWrapper.marketSellOrdersAsync( + erc721TakerBalanceThresholdWrapper.marketSellOrdersAsync( orders, compliantTakerAddress, {takerAssetFillAmount} @@ -912,7 +944,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { it('should revert if taker does not meet the balance threshold', async () => { const orders = [compliantSignedOrder, compliantSignedOrder2]; return expectTransactionFailedAsync( - nonCompliantBalanceThresholdWrapper.marketSellOrdersAsync( + erc721NonCompliantBalanceThresholdWrapper.marketSellOrdersAsync( orders, nonCompliantAddress, {takerAssetFillAmount} @@ -932,7 +964,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { // Execute a valid fill const orders = [compliantSignedOrder, compliantSignedOrder2]; const cumulativeTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.plus(takerAssetFillAmount); - const txReceipt = await takerBalanceThresholdWrapper.marketSellOrdersNoThrowAsync(orders, compliantTakerAddress, {takerAssetFillAmount: cumulativeTakerAssetFillAmount}); + const txReceipt = await erc721TakerBalanceThresholdWrapper.marketSellOrdersNoThrowAsync(orders, compliantTakerAddress, {takerAssetFillAmount: cumulativeTakerAssetFillAmount}); // Assert validated addresses const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); @@ -992,7 +1024,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; // Execute transaction return expectTransactionFailedAsync( - takerBalanceThresholdWrapper.marketSellOrdersNoThrowAsync( + erc721TakerBalanceThresholdWrapper.marketSellOrdersNoThrowAsync( orders, compliantTakerAddress, {takerAssetFillAmount} @@ -1003,7 +1035,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { it('should revert if taker does not meet the balance threshold', async () => { const orders = [compliantSignedOrder, compliantSignedOrder2]; return expectTransactionFailedAsync( - nonCompliantBalanceThresholdWrapper.marketSellOrdersNoThrowAsync( + erc721NonCompliantBalanceThresholdWrapper.marketSellOrdersNoThrowAsync( orders, nonCompliantAddress, {takerAssetFillAmount} @@ -1027,7 +1059,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { .times(compliantSignedOrder.makerAssetAmount) .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); const cumulativeMakerAssetFillAmount = compliantSignedOrder.makerAssetAmount.plus(makerAssetFillAmount2); - const txReceipt = await takerBalanceThresholdWrapper.marketBuyOrdersAsync(orders, compliantTakerAddress, {makerAssetFillAmount: cumulativeMakerAssetFillAmount}); + const txReceipt = await erc721TakerBalanceThresholdWrapper.marketBuyOrdersAsync(orders, compliantTakerAddress, {makerAssetFillAmount: cumulativeMakerAssetFillAmount}); // Assert validated addresses const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); @@ -1084,7 +1116,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { // Execute transaction const dummyMakerAssetFillAmount = new BigNumber(0); return expectTransactionFailedAsync( - takerBalanceThresholdWrapper.marketBuyOrdersAsync( + erc721TakerBalanceThresholdWrapper.marketBuyOrdersAsync( orders, compliantTakerAddress, {makerAssetFillAmount: dummyMakerAssetFillAmount} @@ -1096,7 +1128,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { const orders = [compliantSignedOrder, compliantSignedOrder2]; const dummyMakerAssetFillAmount = new BigNumber(0); return expectTransactionFailedAsync( - nonCompliantBalanceThresholdWrapper.marketBuyOrdersAsync( + erc721NonCompliantBalanceThresholdWrapper.marketBuyOrdersAsync( orders, nonCompliantAddress, {makerAssetFillAmount: dummyMakerAssetFillAmount} @@ -1120,7 +1152,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { .times(compliantSignedOrder.makerAssetAmount) .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); const cumulativeMakerAssetFillAmount = compliantSignedOrder.makerAssetAmount.plus(makerAssetFillAmount2); - const txReceipt = await takerBalanceThresholdWrapper.marketBuyOrdersNoThrowAsync(orders, compliantTakerAddress, {makerAssetFillAmount: cumulativeMakerAssetFillAmount}); + const txReceipt = await erc721TakerBalanceThresholdWrapper.marketBuyOrdersNoThrowAsync(orders, compliantTakerAddress, {makerAssetFillAmount: cumulativeMakerAssetFillAmount}); // Assert validated addresses const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); @@ -1177,7 +1209,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { // Execute transaction const dummyMakerAssetFillAmount = new BigNumber(0); return expectTransactionFailedAsync( - takerBalanceThresholdWrapper.marketBuyOrdersNoThrowAsync( + erc721TakerBalanceThresholdWrapper.marketBuyOrdersNoThrowAsync( orders, compliantTakerAddress, {makerAssetFillAmount: dummyMakerAssetFillAmount} @@ -1189,7 +1221,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { const orders = [compliantSignedOrder, compliantSignedOrder2]; const dummyMakerAssetFillAmount = new BigNumber(0); return expectTransactionFailedAsync( - nonCompliantBalanceThresholdWrapper.marketBuyOrdersNoThrowAsync( + erc721NonCompliantBalanceThresholdWrapper.marketBuyOrdersNoThrowAsync( orders, nonCompliantAddress, {makerAssetFillAmount: dummyMakerAssetFillAmount} @@ -1240,7 +1272,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber('76.5306122448979591'), 16), // 76.53% feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% }; - const txReceipt = await takerBalanceThresholdWrapper.matchOrdersAsync(signedOrderLeft, signedOrderRight, compliantTakerAddress); + const txReceipt = await erc721TakerBalanceThresholdWrapper.matchOrdersAsync(signedOrderLeft, signedOrderRight, compliantTakerAddress); // Assert validated addresses const expectedValidatedAddresseses = [signedOrderLeft.makerAddress, signedOrderRight.makerAddress, compliantTakerAddress]; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); @@ -1292,12 +1324,12 @@ describe.only(ContractName.BalanceThresholdFilter, () => { it('should revert if left maker does not meet the balance threshold', async () => { // Create signed order with non-compliant maker address const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - senderAddress: compliantForwarderInstance.address, + senderAddress: erc721CompliantForwarderInstance.address, makerAddress: nonCompliantAddress }); // Execute transaction return expectTransactionFailedAsync( - takerBalanceThresholdWrapper.matchOrdersAsync( + erc721TakerBalanceThresholdWrapper.matchOrdersAsync( compliantSignedOrder, signedOrderWithBadMakerAddress, compliantTakerAddress, @@ -1308,12 +1340,12 @@ describe.only(ContractName.BalanceThresholdFilter, () => { it('should revert if right maker does not meet the balance threshold', async () => { // Create signed order with non-compliant maker address const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - senderAddress: compliantForwarderInstance.address, + senderAddress: erc721CompliantForwarderInstance.address, makerAddress: nonCompliantAddress }); // Execute transaction return expectTransactionFailedAsync( - takerBalanceThresholdWrapper.matchOrdersAsync( + erc721TakerBalanceThresholdWrapper.matchOrdersAsync( signedOrderWithBadMakerAddress, compliantSignedOrder, compliantTakerAddress, @@ -1323,7 +1355,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); it('should revert if taker does not meet the balance threshold', async () => { return expectTransactionFailedAsync( - nonCompliantBalanceThresholdWrapper.matchOrdersAsync( + erc721NonCompliantBalanceThresholdWrapper.matchOrdersAsync( compliantSignedOrder, compliantSignedOrder, nonCompliantAddress, @@ -1341,30 +1373,30 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); it('Should successfully cancel order if maker meets balance threshold', async () => { // Verify order is not cancelled - const orderInfoBeforeCancelling = await makerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) + const orderInfoBeforeCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); // Cancel - const txReceipt = await makerBalanceThresholdWrapper.cancelOrderAsync(compliantSignedOrder, compliantSignedOrder.makerAddress); + const txReceipt = await erc721MakerBalanceThresholdWrapper.cancelOrderAsync(compliantSignedOrder, compliantSignedOrder.makerAddress); // Assert validated addresses const expectedValidatedAddresseses: string[] = []; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); // Check that order was cancelled - const orderInfoAfterCancelling = await makerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) + const orderInfoAfterCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED); }); it('Should successfully cancel order if maker does not meet balance threshold', async () => { // Create order where maker does not meet balance threshold const signedOrderWithBadMakerAddress = await nonCompliantOrderFactory.newSignedOrderAsync({}); // Verify order is not cancelled - const orderInfoBeforeCancelling = await nonCompliantBalanceThresholdWrapper.getOrderInfoAsync(signedOrderWithBadMakerAddress) + const orderInfoBeforeCancelling = await erc721NonCompliantBalanceThresholdWrapper.getOrderInfoAsync(signedOrderWithBadMakerAddress) expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); // Cancel - const txReceipt = await nonCompliantBalanceThresholdWrapper.cancelOrderAsync(signedOrderWithBadMakerAddress, signedOrderWithBadMakerAddress.makerAddress); + const txReceipt = await erc721NonCompliantBalanceThresholdWrapper.cancelOrderAsync(signedOrderWithBadMakerAddress, signedOrderWithBadMakerAddress.makerAddress); // Assert validated addresses const expectedValidatedAddresseses: string[] = []; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); // Check that order was cancelled - const orderInfoAfterCancelling = await makerBalanceThresholdWrapper.getOrderInfoAsync(signedOrderWithBadMakerAddress) + const orderInfoAfterCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync(signedOrderWithBadMakerAddress) expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED); }); }); @@ -1382,17 +1414,17 @@ describe.only(ContractName.BalanceThresholdFilter, () => { ]; // Verify orders are not cancelled await _.each(compliantSignedOrders, async (compliantSignedOrder) => { - const orderInfoBeforeCancelling = await makerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) + const orderInfoBeforeCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) return expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); }); // Cancel - const txReceipt = await makerBalanceThresholdWrapper.batchCancelOrdersAsync(compliantSignedOrders, compliantSignedOrder.makerAddress); + const txReceipt = await erc721MakerBalanceThresholdWrapper.batchCancelOrdersAsync(compliantSignedOrders, compliantSignedOrder.makerAddress); // Assert validated addresses const expectedValidatedAddresseses: string[] = []; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); // Check that order was cancelled await _.each(compliantSignedOrders, async (compliantSignedOrder) => { - const orderInfoAfterCancelling = await makerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) + const orderInfoAfterCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) return expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED); }); }); @@ -1405,17 +1437,17 @@ describe.only(ContractName.BalanceThresholdFilter, () => { ]; // Verify orders are not cancelled await _.each(nonCompliantSignedOrders, async (nonCompliantSignedOrder) => { - const orderInfoBeforeCancelling = await nonCompliantBalanceThresholdWrapper.getOrderInfoAsync(nonCompliantSignedOrder) + const orderInfoBeforeCancelling = await erc721NonCompliantBalanceThresholdWrapper.getOrderInfoAsync(nonCompliantSignedOrder) return expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); }); // Cancel - const txReceipt = await nonCompliantBalanceThresholdWrapper.batchCancelOrdersAsync(nonCompliantSignedOrders, nonCompliantAddress); + const txReceipt = await erc721NonCompliantBalanceThresholdWrapper.batchCancelOrdersAsync(nonCompliantSignedOrders, nonCompliantAddress); // Assert validated addresses const expectedValidatedAddresseses: string[] = []; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); // Check that order was cancelled await _.each(nonCompliantSignedOrders, async (nonCompliantSignedOrder) => { - const orderInfoAfterCancelling = await nonCompliantBalanceThresholdWrapper.getOrderInfoAsync(nonCompliantSignedOrder) + const orderInfoAfterCancelling = await erc721NonCompliantBalanceThresholdWrapper.getOrderInfoAsync(nonCompliantSignedOrder) return expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED); }); }); @@ -1434,18 +1466,18 @@ describe.only(ContractName.BalanceThresholdFilter, () => { ]; // Verify orders are not cancelled await _.each(compliantSignedOrders, async (compliantSignedOrder) => { - const orderInfoBeforeCancelling = await makerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) + const orderInfoBeforeCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) return expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); }); // Cancel const cancelOrdersUpToThisSalt = new BigNumber(1); - const txReceipt = await makerBalanceThresholdWrapper.cancelOrdersUpToAsync(cancelOrdersUpToThisSalt, compliantSignedOrder.makerAddress); + const txReceipt = await erc721MakerBalanceThresholdWrapper.cancelOrdersUpToAsync(cancelOrdersUpToThisSalt, compliantSignedOrder.makerAddress); // Assert validated addresses const expectedValidatedAddresseses: string[] = []; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); // Check that order was cancelled await _.each(compliantSignedOrders, async (compliantSignedOrder, salt: number) => { - const orderInfoAfterCancelling = await makerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) + const orderInfoAfterCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) const saltAsBigNumber = new BigNumber(salt); if (saltAsBigNumber.lessThanOrEqualTo(cancelOrdersUpToThisSalt)) { return expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED); @@ -1463,18 +1495,18 @@ describe.only(ContractName.BalanceThresholdFilter, () => { ]; // Verify orders are not cancelled await _.each(nonCompliantSignedOrders, async (nonCompliantSignedOrder) => { - const orderInfoBeforeCancelling = await nonCompliantBalanceThresholdWrapper.getOrderInfoAsync(nonCompliantSignedOrder) + const orderInfoBeforeCancelling = await erc721NonCompliantBalanceThresholdWrapper.getOrderInfoAsync(nonCompliantSignedOrder) return expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); }); // Cancel const cancelOrdersUpToThisSalt = new BigNumber(1); - const txReceipt = await nonCompliantBalanceThresholdWrapper.cancelOrdersUpToAsync(cancelOrdersUpToThisSalt, nonCompliantAddress); + const txReceipt = await erc721NonCompliantBalanceThresholdWrapper.cancelOrdersUpToAsync(cancelOrdersUpToThisSalt, nonCompliantAddress); // Assert validated addresses const expectedValidatedAddresseses: string[] = []; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); // Check that order was cancelled await _.each(nonCompliantSignedOrders, async (nonCompliantSignedOrder, salt: number) => { - const orderInfoAfterCancelling = await nonCompliantBalanceThresholdWrapper.getOrderInfoAsync(nonCompliantSignedOrder) + const orderInfoAfterCancelling = await erc721NonCompliantBalanceThresholdWrapper.getOrderInfoAsync(nonCompliantSignedOrder) const saltAsBigNumber = new BigNumber(salt); if (saltAsBigNumber.lessThanOrEqualTo(cancelOrdersUpToThisSalt)) { return expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED); -- cgit From 4417c76b130655da3b14209148191d43037e8f8a Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 11 Dec 2018 14:47:52 -0800 Subject: Ported Balance Threshold Filter to new contract directory structure --- .../BalanceThresholdFilter.sol | 44 - .../MixinBalanceThresholdFilterCore.sol | 319 ---- .../interfaces/IThresholdAsset.sol | 30 - .../mixins/MBalanceThresholdFilterCore.sol | 81 -- .../test/extensions/balance_threshold_filter.ts | 1521 -------------------- .../test/utils/balance_threshold_wrapper.ts | 247 ---- 6 files changed, 2242 deletions(-) delete mode 100644 packages/contracts/contracts/extensions/BalanceThresholdFilter/BalanceThresholdFilter.sol delete mode 100644 packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol delete mode 100644 packages/contracts/contracts/extensions/BalanceThresholdFilter/interfaces/IThresholdAsset.sol delete mode 100644 packages/contracts/contracts/extensions/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol delete mode 100644 packages/contracts/test/extensions/balance_threshold_filter.ts delete mode 100644 packages/contracts/test/utils/balance_threshold_wrapper.ts (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/BalanceThresholdFilter/BalanceThresholdFilter.sol b/packages/contracts/contracts/extensions/BalanceThresholdFilter/BalanceThresholdFilter.sol deleted file mode 100644 index bf4a94509..000000000 --- a/packages/contracts/contracts/extensions/BalanceThresholdFilter/BalanceThresholdFilter.sol +++ /dev/null @@ -1,44 +0,0 @@ -/* - - Copyright 2018 ZeroEx Intl. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -*/ - -pragma solidity 0.4.24; -pragma experimental ABIEncoderV2; - -import "../../protocol/Exchange/interfaces/IExchange.sol"; -import "./interfaces/IThresholdAsset.sol"; -import "./MixinBalanceThresholdFilterCore.sol"; - - -contract BalanceThresholdFilter is MixinBalanceThresholdFilterCore { - - /// @dev Constructs BalanceThresholdFilter. - /// @param exchange Address of 0x exchange. - /// @param thresholdAsset The asset that must be held by makers/takers. - /// @param thresholdBalance The minimum balance of `thresholdAsset` that must be held by makers/takers. - constructor( - address exchange, - address thresholdAsset, - uint256 thresholdBalance - ) - public - { - EXCHANGE = IExchange(exchange); - THRESHOLD_ASSET = IThresholdAsset(thresholdAsset); - THRESHOLD_BALANCE = thresholdBalance; - } -} \ No newline at end of file diff --git a/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol b/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol deleted file mode 100644 index 0ad8ccddf..000000000 --- a/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol +++ /dev/null @@ -1,319 +0,0 @@ -/* - - Copyright 2018 ZeroEx Intl. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -*/ - -pragma solidity 0.4.24; -pragma experimental ABIEncoderV2; - -import "./mixins/MBalanceThresholdFilterCore.sol"; - - -contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { - - /// @dev Executes an Exchange transaction iff the maker and taker meet - /// the hold at least `BALANCE_THRESHOLD` of the asset `THRESHOLD_ASSET` OR - /// the exchange function is a cancellation. - /// Supported Exchange functions: - /// - batchFillOrders - /// - batchFillOrdersNoThrow - /// - batchFillOrKillOrders - /// - fillOrder - /// - fillOrderNoThrow - /// - fillOrKillOrder - /// - marketBuyOrders - /// - marketBuyOrdersNoThrow - /// - marketSellOrders - /// - marketSellOrdersNoThrow - /// - matchOrders - /// - cancelOrder - /// - batchCancelOrders - /// - cancelOrdersUpTo - /// Trying to call any other exchange function will throw. - /// @param salt Arbitrary number to ensure uniqueness of transaction hash. - /// @param signerAddress Address of transaction signer. - /// @param signedExchangeTransaction AbiV2 encoded calldata. - /// @param signature Proof of signer transaction by signer. - function executeTransaction( - uint256 salt, - address signerAddress, - bytes signedExchangeTransaction, - bytes signature - ) - external - { - // Validate addresses. - validateBalanceThresholdsOrRevert(); - - // All addresses are valid. Execute fillOrder. - EXCHANGE.executeTransaction( - salt, - signerAddress, - signedExchangeTransaction, - signature - ); - } - - /// @dev Validates addresses meet the balance threshold specified by `BALANCE_THRESHOLD` - /// for the asset `THRESHOLD_ASSET`. If one address does not meet the thresold - /// then this function will revert. Which addresses are validated depends on - /// which Exchange function is to be called (defined by `signedExchangeTransaction` above). - /// No parameters are taken as this function reads arguments directly from calldata, to save gas. - /// If all addresses are valid then this function emits a ValidatedAddresses event, listing all - /// of the addresses whose balance thresholds it checked. - function validateBalanceThresholdsOrRevert() - internal - { - // Addresses that are validated below. - address[] memory validatedAddresses; - - ///// Do not add variables after this point. ///// - ///// The assembly block may overwrite their values. ///// - - // Validate addresses - assembly { - /// @dev Emulates the `calldataload` opcode on the embedded Exchange calldata, - /// which is accessed through `signedExchangeTransaction`. - /// @param offset - Offset into the Exchange calldata. - /// @return value - Corresponding 32 byte value stored at `offset`. - function exchangeCalldataload(offset) -> value { - // Pointer to exchange transaction - // 0x04 for calldata selector - // 0x40 to access `signedExchangeTransaction`, which is the third parameter - let exchangeTxPtr := calldataload(0x44) - - // Offset into Exchange calldata - // We compute this by adding 0x24 to the `exchangeTxPtr` computed above. - // 0x04 for calldata selector - // 0x20 for length field of `signedExchangeTransaction` - let exchangeCalldataOffset := add(exchangeTxPtr, add(0x24, offset)) - value := calldataload(exchangeCalldataOffset) - } - - /// @dev Convenience function that skips the 4 byte selector when loading - /// from the embedded Exchange calldata. - /// @param offset - Offset into the Exchange calldata (minus the 4 byte selector) - /// @return value - Corresponding 32 byte value stored at `offset` + 4. - function loadExchangeData(offset) -> value { - value := exchangeCalldataload(add(offset, 0x4)) - } - - /// @dev A running list is maintained of addresses to validate. - /// This function records an address in this array. - /// @param addressToValidate - Address to record for validation. - function recordAddressToValidate(addressToValidate) { - // Compute `addressesToValidate` memory offset - let addressesToValidate_ := mload(0x40) - let nAddressesToValidate_ := mload(addressesToValidate_) - - // Increment length - nAddressesToValidate_ := add(mload(addressesToValidate_), 0x01) - mstore(addressesToValidate_, nAddressesToValidate_) - - // Append address to validate - let offset := mul(nAddressesToValidate_, 0x20) - mstore(add(addressesToValidate_, offset), addressToValidate) - } - - /// @dev Extracts the maker address from an order stored in the Exchange calldata - /// (which is embedded in `signedExchangeTransaction`), and records it in - /// the running list of addresses to validate. - /// @param orderParamIndex - Index of the order in the Exchange function's signature - function recordMakerAddressFromOrder(orderParamIndex) { - let orderPtr := loadExchangeData(mul(orderParamIndex, 0x20)) - let makerAddress := loadExchangeData(orderPtr) - recordAddressToValidate(makerAddress) - } - - /// @dev Extracts the maker addresses from an array of orders stored in the Exchange calldata - /// (which is embedded in `signedExchangeTransaction`), and records them in - /// the running list of addresses to validate. - /// @param orderArrayParamIndex - Index of the order array in the Exchange function's signature - function recordMakerAddressesFromOrderArray(orderArrayParamIndex) { - let orderArrayPtr := loadExchangeData(mul(orderArrayParamIndex, 0x20)) - let orderArrayLength := loadExchangeData(orderArrayPtr) - let orderArrayElementPtr := add(orderArrayPtr, 0x20) - let orderArrayElementEndPtr := add(orderArrayElementPtr, mul(orderArrayLength, 0x20)) - for {let orderPtrOffset := orderArrayElementPtr} lt(orderPtrOffset, orderArrayElementEndPtr) {orderPtrOffset := add(orderPtrOffset, 0x20)} { - let orderPtr := loadExchangeData(orderPtrOffset) - let makerAddress := loadExchangeData(add(orderPtr, orderArrayElementPtr)) - recordAddressToValidate(makerAddress) - } - } - - /// @dev Records address of signer in the running list of addresses to validate. - /// @note: We cannot access `signerAddress` directly from within the asm function, - /// so it is loaded from the calldata. - function recordSignerAddress() { - // Load the signer address from calldata - // 0x04 for selector - // 0x20 to access `signerAddress`, which is the second parameter. - let signerAddress_ := calldataload(0x24) - recordAddressToValidate(signerAddress_) - } - - /// @dev Records addresses to be validated when Exchange transaction is a batch fill variant. - /// This is one of: batchFillOrders, batchFillOrKillOrders, batchFillNoThrow - /// Reference signature: (Order[],uint256[],bytes[]) - function recordAddressesForBatchFillVariant() { - // Record maker addresses from order array (parameter index 0) - // The signer is the taker for these orders and must also be validated. - recordMakerAddressesFromOrderArray(0) - recordSignerAddress() - } - - /// @dev Records addresses to be validated when Exchange transaction is a fill order variant. - /// This is one of: fillOrder, fillOrKillOrder, fillOrderNoThrow - /// Reference signature: (Order,uint256,bytes) - function recordAddressesForFillOrderVariant() { - // Record maker address from the order (param index 0) - // The signer is the taker for this order and must also be validated. - recordMakerAddressFromOrder(0) - recordSignerAddress() - } - - /// @dev Records addresses to be validated when Exchange transaction is a market fill variant. - /// This is one of: marketBuyOrders, marketBuyOrdersNoThrow, marketSellOrders, marketSellOrdersNoThrow - /// Reference signature: (Order[],uint256,bytes[]) - function recordAddressesForMarketFillVariant() { - // Record maker addresses from order array (parameter index 0) - // The signer is the taker for these orders and must also be validated. - recordMakerAddressesFromOrderArray(0) - recordSignerAddress() - } - - /// @dev Records addresses to be validated when Exchange transaction is matchOrders. - /// Reference signature: matchOrders(Order,Order) - function recordAddressesForMatchOrders() { - // Record maker address from both orders (param indices 0 & 1). - // The signer is the taker and must also be validated. - recordMakerAddressFromOrder(0) - recordMakerAddressFromOrder(1) - recordSignerAddress() - } - - ///// Record Addresses to Validate ///// - - // Addresses needing validation depends on which Exchange function is being called. - // Step 1/2 Read the exchange function selector. - let exchangeFunctionSelector := and( - exchangeCalldataload(0x0), - 0xffffffff00000000000000000000000000000000000000000000000000000000 - ) - - // Step 2/2 Extract addresses to validate based on this selector. - // See ../../utils/ExchangeSelectors/ExchangeSelectors.sol for selectors - switch exchangeFunctionSelector - case 0x297bb70b00000000000000000000000000000000000000000000000000000000 { recordAddressesForBatchFillVariant() } // batchFillOrders - case 0x50dde19000000000000000000000000000000000000000000000000000000000 { recordAddressesForBatchFillVariant() } // batchFillOrdersNoThrow - case 0x4d0ae54600000000000000000000000000000000000000000000000000000000 { recordAddressesForBatchFillVariant() } // batchFillOrKillOrders - case 0xb4be83d500000000000000000000000000000000000000000000000000000000 { recordAddressesForFillOrderVariant() } // fillOrder - case 0x3e228bae00000000000000000000000000000000000000000000000000000000 { recordAddressesForFillOrderVariant() } // fillOrderNoThrow - case 0x64a3bc1500000000000000000000000000000000000000000000000000000000 { recordAddressesForFillOrderVariant() } // fillOrKillOrder - case 0xe5fa431b00000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketBuyOrders - case 0xa3e2038000000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketBuyOrdersNoThrow - case 0x7e1d980800000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketSellOrders - case 0xdd1c7d1800000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketSellOrdersNoThrow - case 0x3c28d86100000000000000000000000000000000000000000000000000000000 { recordAddressesForMatchOrders() } // matchOrders - case 0xd46b02c300000000000000000000000000000000000000000000000000000000 {} // cancelOrder - case 0x4ac1478200000000000000000000000000000000000000000000000000000000 {} // batchCancelOrders - case 0x4f9559b100000000000000000000000000000000000000000000000000000000 {} // cancelOrdersUpTo - default { - // Revert with `Error("INVALID_OR_BLOCKED_EXCHANGE_SELECTOR")` - mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) - mstore(0x20, 0x0000002000000000000000000000000000000000000000000000000000000000) - mstore(0x40, 0x00000024494e56414c49445f4f525f424c4f434b45445f45584348414e47455f) - mstore(0x60, 0x53454c4543544f52000000000000000000000000000000000000000000000000) - mstore(0x80, 0x00000000) - // Revert length calculation: - // 4 -- error selector - // 32 -- offset to string - // 32 -- string length field - // 64 -- strlen(INVALID_OR_BLOCKED_EXCHANGE_SELECTOR) rounded up to nearest 32-byte word. - revert(0, 132) - } - - ///// Validate Recorded Addresses ///// - - // Load from memory the addresses to validate - let addressesToValidate := mload(0x40) - let addressesToValidateLength := mload(addressesToValidate) - let addressesToValidateElementPtr := add(addressesToValidate, 0x20) - let addressesToValidateElementEndPtr := add(addressesToValidateElementPtr, mul(addressesToValidateLength, 0x20)) - - // Set free memory pointer to after `addressesToValidate` array. - // This is to avoid corruption when making calls in the loop below. - let freeMemPtr := addressesToValidateElementEndPtr - mstore(0x40, freeMemPtr) - - // Validate addresses - let thresholdAssetAddress := sload(THRESHOLD_ASSET_slot) - let thresholdBalance := sload(THRESHOLD_BALANCE_slot) - for {let addressToValidate := addressesToValidateElementPtr} lt(addressToValidate, addressesToValidateElementEndPtr) {addressToValidate := add(addressToValidate, 0x20)} { - // Construct calldata for `THRESHOLD_ASSET.balanceOf` - mstore(freeMemPtr, 0x70a0823100000000000000000000000000000000000000000000000000000000) - mstore(add(freeMemPtr, 0x04), mload(addressToValidate)) - - // call `THRESHOLD_ASSET.balanceOf` - let success := call( - gas, // forward all gas - thresholdAssetAddress, // call address of asset proxy - 0, // don't send any ETH - freeMemPtr, // pointer to start of input - 0x24, // length of input (one padded address) - freeMemPtr, // write output to next free memory offset - 0x20 // reserve space for return balance (0x20 bytes) - ) - if eq(success, 0) { - // @TODO Revert with `Error("BALANCE_QUERY_FAILED")` - mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) - mstore(0x20, 0x0000002000000000000000000000000000000000000000000000000000000000) - mstore(0x40, 0x0000001442414c414e43455f51554552595f4641494c45440000000000000000) - mstore(0x60, 0x00000000) - // Revert length calculation: - // 4 -- error selector - // 32 -- offset to string - // 32 -- string length field - // 32 -- strlen(BALANCE_QUERY_FAILED) rounded up to nearest 32-byte word. - revert(0, 100) - } - - // Revert if balance not held - let addressBalance := mload(freeMemPtr) - if lt(addressBalance, thresholdBalance) { - // Revert with `Error("AT_LEAST_ONE_ADDRESS_DOES_NOT_MEET_BALANCE_THRESHOLD")` - mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) - mstore(0x20, 0x0000002000000000000000000000000000000000000000000000000000000000) - mstore(0x40, 0x0000003441545f4c454153545f4f4e455f414444524553535f444f45535f4e4f) - mstore(0x60, 0x545f4d4545545f42414c414e43455f5448524553484f4c440000000000000000) - mstore(0x80, 0x00000000) - // Revert length calculation: - // 4 -- error selector - // 32 -- offset to string - // 32 -- string length field - // 64 -- strlen(AT_LEAST_ONE_ADDRESS_DOES_NOT_MEET_BALANCE_THRESHOLD) rounded up to nearest 32-byte word. - revert(0, 132) - } - } - - // Record validated addresses - validatedAddresses := addressesToValidate - } - - ///// If we hit this point then all addresses are valid ///// - emit ValidatedAddresses(validatedAddresses); - } -} \ No newline at end of file diff --git a/packages/contracts/contracts/extensions/BalanceThresholdFilter/interfaces/IThresholdAsset.sol b/packages/contracts/contracts/extensions/BalanceThresholdFilter/interfaces/IThresholdAsset.sol deleted file mode 100644 index 61acaba0a..000000000 --- a/packages/contracts/contracts/extensions/BalanceThresholdFilter/interfaces/IThresholdAsset.sol +++ /dev/null @@ -1,30 +0,0 @@ -/* - - Copyright 2018 ZeroEx Intl. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -*/ - -pragma solidity 0.4.24; - -contract IThresholdAsset { - - /// @param _owner The address from which the balance will be retrieved - /// @return Balance of owner - function balanceOf(address _owner) - external - view - returns (uint256); - -} \ No newline at end of file diff --git a/packages/contracts/contracts/extensions/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol b/packages/contracts/contracts/extensions/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol deleted file mode 100644 index 612fd481c..000000000 --- a/packages/contracts/contracts/extensions/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol +++ /dev/null @@ -1,81 +0,0 @@ -/* - - Copyright 2018 ZeroEx Intl. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -*/ - -pragma solidity 0.4.24; -pragma experimental ABIEncoderV2; - -import "../../../protocol/Exchange/interfaces/IExchange.sol"; -import "../interfaces/IThresholdAsset.sol"; - - -contract MBalanceThresholdFilterCore { - - // Points to 0x exchange contract - IExchange internal EXCHANGE; - - // The asset that must be held by makers/takers - IThresholdAsset internal THRESHOLD_ASSET; - - // The minimum balance of `THRESHOLD_ASSET` that must be held by makers/takers - uint256 internal THRESHOLD_BALANCE; - - // Addresses that hold at least `THRESHOLD_BALANCE` of `THRESHOLD_ASSET` - event ValidatedAddresses ( - address[] addresses - ); - - /// @dev Executes an Exchange transaction iff the maker and taker meet - /// the hold at least `BALANCE_THRESHOLD` of the asset `THRESHOLD_ASSET` OR - /// the exchange function is a cancellation. - /// Supported Exchange functions: - /// - batchFillOrders - /// - batchFillOrdersNoThrow - /// - batchFillOrKillOrders - /// - fillOrder - /// - fillOrderNoThrow - /// - fillOrKillOrder - /// - marketBuyOrders - /// - marketBuyOrdersNoThrow - /// - marketSellOrders - /// - marketSellOrdersNoThrow - /// - matchOrders - /// - cancelOrder - /// - batchCancelOrders - /// - cancelOrdersUpTo - /// Trying to call any other exchange function will throw. - /// @param salt Arbitrary number to ensure uniqueness of transaction hash. - /// @param signerAddress Address of transaction signer. - /// @param signedExchangeTransaction AbiV2 encoded calldata. - /// @param signature Proof of signer transaction by signer. - function executeTransaction( - uint256 salt, - address signerAddress, - bytes signedExchangeTransaction, - bytes signature - ) - external; - - /// @dev Validates addresses meet the balance threshold specified by `BALANCE_THRESHOLD` - /// for the asset `THRESHOLD_ASSET`. If one address does not meet the thresold - /// then this function will revert. Which addresses are validated depends on - /// which Exchange function is to be called (defined by `signedExchangeTransaction` above). - /// No parameters are taken as this function reads arguments directly from calldata, to save gas. - /// If all addresses are valid then this function emits a ValidatedAddresses event, listing all - /// of the addresses whose balance thresholds it checked. - function validateBalanceThresholdsOrRevert() internal; -} \ No newline at end of file diff --git a/packages/contracts/test/extensions/balance_threshold_filter.ts b/packages/contracts/test/extensions/balance_threshold_filter.ts deleted file mode 100644 index c4723e7aa..000000000 --- a/packages/contracts/test/extensions/balance_threshold_filter.ts +++ /dev/null @@ -1,1521 +0,0 @@ -import { BlockchainLifecycle } from '@0x/dev-utils'; -import { assetDataUtils } from '@0x/order-utils'; -import { RevertReason, SignedOrder, Order } from '@0x/types'; -import { BigNumber } from '@0x/utils'; -import { Web3Wrapper } from '@0x/web3-wrapper'; -import * as chai from 'chai'; -import * as ethUtil from 'ethereumjs-util'; -import * as _ from 'lodash'; -import { TransactionReceiptWithDecodedLogs } from 'ethereum-types'; - -import { DummyERC20TokenContract } from '../../generated-wrappers/dummy_erc20_token'; -import { ExchangeContract } from '../../generated-wrappers/exchange'; -import { BalanceThresholdFilterContract } from '../../generated-wrappers/balance_threshold_filter'; - -import { artifacts } from '../../src/artifacts'; -import { - expectTransactionFailedAsync, - expectTransactionFailedWithoutReasonAsync, -} from '../utils/assertions'; -import { chaiSetup } from '../utils/chai_setup'; -import { constants } from '../utils/constants'; -import { ERC20Wrapper } from '../utils/erc20_wrapper'; -import { ExchangeWrapper } from '../utils/exchange_wrapper'; -import { MatchOrderTester } from '../utils/match_order_tester'; -import { OrderFactory } from '../utils/order_factory'; -import { orderUtils } from '../utils/order_utils'; -import { TransactionFactory } from '../utils/transaction_factory'; -import { BalanceThresholdWrapper } from '../utils/balance_threshold_wrapper'; -import { ContractName, ERC20BalancesByOwner, SignedTransaction, OrderStatus } from '../utils/types'; -import { provider, txDefaults, web3Wrapper } from '../utils/web3_wrapper'; -import { TestExchangeInternalsContract } from '../../generated-wrappers/test_exchange_internals'; - -import { MethodAbi, AbiDefinition } from 'ethereum-types'; -import { AbiEncoder } from '@0x/utils'; -import { Method } from '@0x/utils/lib/src/abi_encoder'; -import { LogDecoder } from '../utils/log_decoder'; -import { ERC721Wrapper } from '../utils/erc721_wrapper'; - -chaiSetup.configure(); -const expect = chai.expect; -const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); -const DECIMALS_DEFAULT = 18; - -interface ValidatedAddressesLog { - args: {addresses: string[]} -} - -describe.only(ContractName.BalanceThresholdFilter, () => { - let compliantMakerAddress: string; - let compliantMakerAddress2: string; - let owner: string; - let compliantTakerAddress: string; - let feeRecipientAddress: string; - let nonCompliantAddress: string; - let defaultMakerAssetAddress: string; - let defaultTakerAssetAddress: string; - let zrxAssetData: string; - let zrxToken: DummyERC20TokenContract; - let exchangeInstance: ExchangeContract; - let exchangeWrapper: ExchangeWrapper; - - let orderFactory: OrderFactory; - let orderFactory2: OrderFactory; - let nonCompliantOrderFactory: OrderFactory; - let erc20Wrapper: ERC20Wrapper; - let erc20Balances: ERC20BalancesByOwner; - let erc20TakerBalanceThresholdWrapper: BalanceThresholdWrapper; - let erc721TakerBalanceThresholdWrapper: BalanceThresholdWrapper; - let erc721MakerBalanceThresholdWrapper: BalanceThresholdWrapper; - let erc721NonCompliantBalanceThresholdWrapper: BalanceThresholdWrapper; - - let takerTransactionFactory: TransactionFactory; - let makerTransactionFactory: TransactionFactory; - let compliantSignedOrder: SignedOrder; - let compliantSignedOrder2: SignedOrder; - let compliantSignedFillOrderTx: SignedTransaction; - - let logDecoder: LogDecoder; - let exchangeInternals: TestExchangeInternalsContract; - - let defaultOrderParams: Partial; - - const takerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(500), DECIMALS_DEFAULT); - const makerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(1000), DECIMALS_DEFAULT); - const takerAssetFillAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(250), DECIMALS_DEFAULT); - - let erc721CompliantForwarderInstance: BalanceThresholdFilterContract; - let erc20CompliantForwarderInstance: BalanceThresholdFilterContract; - - const assertValidatedAddressesLog = async (txReceipt: TransactionReceiptWithDecodedLogs, expectedValidatedAddresses: string[]) => { - expect(txReceipt.logs.length).to.be.gte(1); - const validatedAddressesLog = (txReceipt.logs[0] as any) as ValidatedAddressesLog; - const validatedAddresses = validatedAddressesLog.args.addresses; - // @HACK-hysz: Nested addresses are not translated to lower-case but this will change once - // the new ABI Encoder/Decoder is used by the contract templates. - let validatedAddressesNormalized: string[] = []; - _.each(validatedAddresses, (address) => { - const normalizedAddress = _.toLower(address); - validatedAddressesNormalized.push(normalizedAddress); - }); - expect(validatedAddressesNormalized).to.be.deep.equal(expectedValidatedAddresses); - }; - - before(async () => { - // Create accounts - await blockchainLifecycle.startAsync(); - const accounts = await web3Wrapper.getAvailableAddressesAsync(); - const usedAddresses = ([ - owner, - compliantMakerAddress, - compliantMakerAddress2, - compliantTakerAddress, - feeRecipientAddress, - nonCompliantAddress, - ] = accounts); - // Create wrappers - erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner); - let compliantAddresses = _.cloneDeepWith(usedAddresses); - _.remove(compliantAddresses, (address: string) => { - return address === nonCompliantAddress; - }); - const erc721Wrapper = new ERC721Wrapper(provider, compliantAddresses, owner); - // Deploy ERC20 tokens - const numDummyErc20ToDeploy = 4; - let erc20TokenA: DummyERC20TokenContract; - let erc20TokenB: DummyERC20TokenContract; - let erc20BalanceThresholdAsset: DummyERC20TokenContract; - [erc20TokenA, erc20TokenB, zrxToken, erc20BalanceThresholdAsset] = await erc20Wrapper.deployDummyTokensAsync( - numDummyErc20ToDeploy, - constants.DUMMY_TOKEN_DECIMALS, - ); - defaultMakerAssetAddress = erc20TokenA.address; - defaultTakerAssetAddress = erc20TokenB.address; - zrxAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address); - // Create proxies - const erc20Proxy = await erc20Wrapper.deployProxyAsync(); - await erc20Wrapper.setBalancesAndAllowancesAsync(); - // Deploy Exchange congtract - exchangeInstance = await ExchangeContract.deployFrom0xArtifactAsync( - artifacts.Exchange, - provider, - txDefaults, - zrxAssetData, - ); - exchangeWrapper = new ExchangeWrapper(exchangeInstance, provider); - // Register proxies - await exchangeWrapper.registerAssetProxyAsync(erc20Proxy.address, owner); - await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(exchangeInstance.address, { - from: owner, - }); - // Deploy Compliant Forwarder - const erc721alanceThreshold = new BigNumber(1); - await erc721Wrapper.deployProxyAsync(); - const [erc721BalanceThresholdAsset] = await erc721Wrapper.deployDummyTokensAsync(); - await erc721Wrapper.setBalancesAndAllowancesAsync(); - erc721CompliantForwarderInstance = await BalanceThresholdFilterContract.deployFrom0xArtifactAsync( - artifacts.BalanceThresholdFilter, - provider, - txDefaults, - exchangeInstance.address, - erc721BalanceThresholdAsset.address, - erc721alanceThreshold - ); - const erc20BalanceThreshold = Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 10); - erc20CompliantForwarderInstance = await BalanceThresholdFilterContract.deployFrom0xArtifactAsync( - artifacts.BalanceThresholdFilter, - provider, - txDefaults, - exchangeInstance.address, - erc20BalanceThresholdAsset.address, - erc20BalanceThreshold - ); - - // Default order parameters - defaultOrderParams = { - exchangeAddress: exchangeInstance.address, - feeRecipientAddress, - makerAssetData: assetDataUtils.encodeERC20AssetData(defaultMakerAssetAddress), - takerAssetData: assetDataUtils.encodeERC20AssetData(defaultTakerAssetAddress), - makerAssetAmount, - takerAssetAmount, - makerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), DECIMALS_DEFAULT), - takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(150), DECIMALS_DEFAULT), - senderAddress: erc721CompliantForwarderInstance.address, - }; - const defaultOrderParams1 = { - makerAddress: compliantMakerAddress, - ... - defaultOrderParams, - } - const makerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantMakerAddress)]; - takerTransactionFactory = new TransactionFactory(makerPrivateKey, exchangeInstance.address); - orderFactory = new OrderFactory(makerPrivateKey, defaultOrderParams1); - const defaultOrderParams2 = { - makerAddress: compliantMakerAddress2, - ... - defaultOrderParams, - } - const secondMakerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantMakerAddress2)]; - orderFactory2 = new OrderFactory(secondMakerPrivateKey, defaultOrderParams2); - - const nonCompliantPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(nonCompliantAddress)]; - const defaultNonCompliantOrderParams = { - makerAddress: nonCompliantAddress, - ... - defaultOrderParams, - }; - nonCompliantOrderFactory = new OrderFactory(nonCompliantPrivateKey, defaultNonCompliantOrderParams); - // Create Valid/Invalid orders - const takerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantTakerAddress)]; - takerTransactionFactory = new TransactionFactory(takerPrivateKey, exchangeInstance.address); - compliantSignedOrder = await orderFactory.newSignedOrderAsync({ - senderAddress: erc721CompliantForwarderInstance.address, - }); - const compliantSignedOrderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress( - compliantSignedOrder, - ); - const compliantSignedOrderWithoutExchangeAddressData = exchangeInstance.fillOrder.getABIEncodedTransactionData( - compliantSignedOrderWithoutExchangeAddress, - takerAssetFillAmount, - compliantSignedOrder.signature, - ); - compliantSignedFillOrderTx = takerTransactionFactory.newSignedTransaction( - compliantSignedOrderWithoutExchangeAddressData, - ); - - logDecoder = new LogDecoder(web3Wrapper); - erc20TakerBalanceThresholdWrapper = new BalanceThresholdWrapper(erc20CompliantForwarderInstance, exchangeInstance, new TransactionFactory(takerPrivateKey, exchangeInstance.address), provider); - erc721TakerBalanceThresholdWrapper = new BalanceThresholdWrapper(erc721CompliantForwarderInstance, exchangeInstance, new TransactionFactory(takerPrivateKey, exchangeInstance.address), provider); - erc721MakerBalanceThresholdWrapper = new BalanceThresholdWrapper(erc721CompliantForwarderInstance, exchangeInstance, new TransactionFactory(makerPrivateKey, exchangeInstance.address), provider); - erc721NonCompliantBalanceThresholdWrapper = new BalanceThresholdWrapper(erc721CompliantForwarderInstance, exchangeInstance, new TransactionFactory(nonCompliantPrivateKey, exchangeInstance.address), provider); - - // Instantiate internal exchange contract - exchangeInternals = await TestExchangeInternalsContract.deployFrom0xArtifactAsync( - artifacts.TestExchangeInternals, - provider, - txDefaults, - ); - }); - beforeEach(async () => { - await blockchainLifecycle.startAsync(); - }); - afterEach(async () => { - await blockchainLifecycle.revertAsync(); - }); - - describe.only('General Sanity Checks', () => { - beforeEach(async () => { - erc20Balances = await erc20Wrapper.getBalancesAsync(); - compliantSignedOrder = await orderFactory.newSignedOrderAsync(); - compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); - }); - it.only('should transfer the correct amounts and validate both maker/taker when both maker and taker exceed the balance threshold of an ERC20 token', async () => { - const compliantSignedOrderERC20Sender = await orderFactory.newSignedOrderAsync({ - ... - defaultOrderParams, - makerAddress: compliantMakerAddress, - senderAddress: erc20TakerBalanceThresholdWrapper.getBalanceThresholdAddress(), - }); - // Execute a valid fill - const txReceipt = await erc20TakerBalanceThresholdWrapper.fillOrderAsync(compliantSignedOrderERC20Sender, compliantTakerAddress, {takerAssetFillAmount}); - // Assert validated addresses - const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedFillOrderTx.signerAddress]; - assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); - // Check balances - const newBalances = await erc20Wrapper.getBalancesAsync(); - const makerAssetFillAmount = takerAssetFillAmount - .times(compliantSignedOrder.makerAssetAmount) - .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); - const makerFeePaid = compliantSignedOrder.makerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); - const takerFeePaid = compliantSignedOrder.takerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); - expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), - ); - expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount), - ); - expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount), - ); - expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), - ); - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)), - ); - }); - it('should revert if the signed transaction is not intended for supported', async () => { - // Create signed order without the fillOrder function selector - const txDataBuf = ethUtil.toBuffer(compliantSignedFillOrderTx.data); - const selectorLengthInBytes = 4; - const txDataBufMinusSelector = txDataBuf.slice(selectorLengthInBytes); - const badSelector = '0x00000000'; - const badSelectorBuf = ethUtil.toBuffer(badSelector); - const txDataBufWithBadSelector = Buffer.concat([badSelectorBuf, txDataBufMinusSelector]); - const txDataBufWithBadSelectorHex = ethUtil.bufferToHex(txDataBufWithBadSelector); - // Call compliant forwarder - return expectTransactionFailedWithoutReasonAsync(erc721CompliantForwarderInstance.executeTransaction.sendTransactionAsync( - compliantSignedFillOrderTx.salt, - compliantSignedFillOrderTx.signerAddress, - txDataBufWithBadSelectorHex, - compliantSignedFillOrderTx.signature, - )); - }); - it('should revert if senderAddress is not set to the compliant forwarding contract', async () => { - // Create signed order with incorrect senderAddress - const notBalanceThresholdFilterAddress = zrxToken.address; - const signedOrderWithBadSenderAddress = await orderFactory.newSignedOrderAsync({ - senderAddress: notBalanceThresholdFilterAddress, - }); - const signedOrderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress( - signedOrderWithBadSenderAddress, - ); - const signedOrderWithoutExchangeAddressData = exchangeInstance.fillOrder.getABIEncodedTransactionData( - signedOrderWithoutExchangeAddress, - takerAssetFillAmount, - compliantSignedOrder.signature, - ); - const signedFillOrderTx = takerTransactionFactory.newSignedTransaction( - signedOrderWithoutExchangeAddressData, - ); - // Call compliant forwarder - return expectTransactionFailedWithoutReasonAsync(erc721CompliantForwarderInstance.executeTransaction.sendTransactionAsync( - signedFillOrderTx.salt, - signedFillOrderTx.signerAddress, - signedFillOrderTx.data, - signedFillOrderTx.signature, - )); - }); - // @TODO - greater than 1 balance - }); - - describe('batchFillOrders', () => { - beforeEach(async () => { - erc20Balances = await erc20Wrapper.getBalancesAsync(); - compliantSignedOrder = await orderFactory.newSignedOrderAsync(); - compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); - }); - it('should transfer the correct amounts and validate both makers/taker when both maker and taker meet the balance threshold', async () => { - // Execute a valid fill - const orders = [compliantSignedOrder, compliantSignedOrder2]; - const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; - const txReceipt = await erc721TakerBalanceThresholdWrapper.batchFillOrdersAsync(orders, compliantTakerAddress, {takerAssetFillAmounts}); - // Assert validated addresses - const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; - assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); - // Check balances - const newBalances = await erc20Wrapper.getBalancesAsync(); - const cumulativeTakerAssetFillAmount = takerAssetFillAmount.times(2); - const makerAssetFillAmount = takerAssetFillAmount - .times(compliantSignedOrder.makerAssetAmount) - .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); - const makerFeePaid = compliantSignedOrder.makerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); - const takerFeePaid = compliantSignedOrder.takerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount) - .times(2); - // Maker #1 - expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), - ); - // Maker #2 - expect(newBalances[compliantMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress2][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][zrxToken.address].minus(makerFeePaid), - ); - // Taker - expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount), - ); - - expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount.times(2)), - ); - expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), - ); - // Fee recipient - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.times(2).add(takerFeePaid)), - ); - }); - it('should revert if one maker does not meet the balance threshold', async () => { - // Create order set with one non-compliant maker address - const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; - const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - makerAddress: nonCompliantAddress - }); - const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; - // Execute transaction - return expectTransactionFailedAsync( - erc721TakerBalanceThresholdWrapper.batchFillOrdersAsync( - orders, - compliantTakerAddress, - {takerAssetFillAmounts} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - it('should revert if taker does not meet the balance threshold', async () => { - const orders = [compliantSignedOrder, compliantSignedOrder2]; - const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; - return expectTransactionFailedAsync( - erc721NonCompliantBalanceThresholdWrapper.batchFillOrdersAsync( - orders, - nonCompliantAddress, - {takerAssetFillAmounts} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - }); - - describe('batchFillOrdersNoThrow', () => { - beforeEach(async () => { - erc20Balances = await erc20Wrapper.getBalancesAsync(); - compliantSignedOrder = await orderFactory.newSignedOrderAsync(); - compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); - }); - it('should transfer the correct amounts and validate both makers/taker when both maker and taker meet the balance threshold', async () => { - // Execute a valid fill - const orders = [compliantSignedOrder, compliantSignedOrder2]; - const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; - const txReceipt = await erc721TakerBalanceThresholdWrapper.batchFillOrdersNoThrowAsync(orders, compliantTakerAddress, {takerAssetFillAmounts}); - // Assert validated addresses - const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; - assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); - // Check balances - const newBalances = await erc20Wrapper.getBalancesAsync(); - const cumulativeTakerAssetFillAmount = takerAssetFillAmount.times(2); - const makerAssetFillAmount = takerAssetFillAmount - .times(compliantSignedOrder.makerAssetAmount) - .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); - const makerFeePaid = compliantSignedOrder.makerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); - const takerFeePaid = compliantSignedOrder.takerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount) - .times(2); - // Maker #1 - expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), - ); - // Maker #2 - expect(newBalances[compliantMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress2][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][zrxToken.address].minus(makerFeePaid), - ); - // Taker - expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount), - ); - - expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount.times(2)), - ); - expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), - ); - // Fee recipient - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.times(2).add(takerFeePaid)), - ); - }); - it('should revert if one maker does not meet the balance threshold', async () => { - // Create order set with one non-compliant maker address - const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; - const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - makerAddress: nonCompliantAddress - }); - const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; - // Execute transaction - return expectTransactionFailedAsync( - erc721TakerBalanceThresholdWrapper.batchFillOrdersNoThrowAsync( - orders, - compliantTakerAddress, - {takerAssetFillAmounts} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - it('should revert if taker does not meet the balance threshold', async () => { - const orders = [compliantSignedOrder, compliantSignedOrder2]; - const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; - return expectTransactionFailedAsync( - erc721NonCompliantBalanceThresholdWrapper.batchFillOrdersNoThrowAsync( - orders, - nonCompliantAddress, - {takerAssetFillAmounts} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - }); - - describe('batchFillOrKillOrders', () => { - beforeEach(async () => { - erc20Balances = await erc20Wrapper.getBalancesAsync(); - compliantSignedOrder = await orderFactory.newSignedOrderAsync(); - compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); - }); - it('should transfer the correct amounts and validate both makers/taker when both makers and taker meet the balance threshold', async () => { - // Execute a valid fill - const orders = [compliantSignedOrder, compliantSignedOrder2]; - const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; - const txReceipt = await erc721TakerBalanceThresholdWrapper.batchFillOrKillOrdersAsync(orders, compliantTakerAddress, {takerAssetFillAmounts}); - // Assert validated addresses - const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; - assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); - // Check balances - const newBalances = await erc20Wrapper.getBalancesAsync(); - const cumulativeTakerAssetFillAmount = takerAssetFillAmount.times(2); - const makerAssetFillAmount = takerAssetFillAmount - .times(compliantSignedOrder.makerAssetAmount) - .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); - const makerFeePaid = compliantSignedOrder.makerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); - const takerFeePaid = compliantSignedOrder.takerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount) - .times(2); - // Maker #1 - expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), - ); - // Maker #2 - expect(newBalances[compliantMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress2][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][zrxToken.address].minus(makerFeePaid), - ); - // Taker - expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount), - ); - - expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount.times(2)), - ); - expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), - ); - // Fee recipient - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.times(2).add(takerFeePaid)), - ); - }); - it('should revert if one maker does not meet the balance threshold', async () => { - // Create order set with one non-compliant maker address - const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; - const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - makerAddress: nonCompliantAddress - }); - const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; - // Execute transaction - return expectTransactionFailedAsync( - erc721TakerBalanceThresholdWrapper.batchFillOrKillOrdersAsync( - orders, - compliantTakerAddress, - {takerAssetFillAmounts} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - it('should revert if taker does not meet the balance threshold', async () => { - const orders = [compliantSignedOrder, compliantSignedOrder2]; - const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; - return expectTransactionFailedAsync( - erc721NonCompliantBalanceThresholdWrapper.batchFillOrKillOrdersAsync( - orders, - nonCompliantAddress, - {takerAssetFillAmounts} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - it('should revert if one takerAssetFillAmount is not fully filled', async () => { - const tooBigTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.times(2); - const orders = [compliantSignedOrder, compliantSignedOrder2]; - const takerAssetFillAmounts = [takerAssetFillAmount, tooBigTakerAssetFillAmount]; - return expectTransactionFailedAsync( - erc721TakerBalanceThresholdWrapper.batchFillOrKillOrdersAsync( - orders, - compliantTakerAddress, - {takerAssetFillAmounts} - ), - RevertReason.FailedExecution - ); - }); - }); - - describe('fillOrder', () => { - beforeEach(async () => { - erc20Balances = await erc20Wrapper.getBalancesAsync(); - compliantSignedOrder = await orderFactory.newSignedOrderAsync(); - }); - it('should transfer the correct amounts and validate both maker/taker when both maker and taker meet the balance threshold', async () => { - // Execute a valid fill - const txReceipt = await erc721TakerBalanceThresholdWrapper.fillOrderAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount}); - // Assert validated addresses - const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedFillOrderTx.signerAddress]; - assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); - // Check balances - const newBalances = await erc20Wrapper.getBalancesAsync(); - const makerAssetFillAmount = takerAssetFillAmount - .times(compliantSignedOrder.makerAssetAmount) - .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); - const makerFeePaid = compliantSignedOrder.makerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); - const takerFeePaid = compliantSignedOrder.takerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); - expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), - ); - expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount), - ); - expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount), - ); - expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), - ); - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)), - ); - }); - it('should revert if maker does not meet the balance threshold', async () => { - // Create signed order with non-compliant maker address - const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - senderAddress: erc721CompliantForwarderInstance.address, - makerAddress: nonCompliantAddress - }); - // Execute transaction - return expectTransactionFailedAsync( - erc721TakerBalanceThresholdWrapper.fillOrderAsync( - signedOrderWithBadMakerAddress, - compliantTakerAddress, - {takerAssetFillAmount} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - it('should revert if taker does not meet the balance threshold', async () => { - return expectTransactionFailedAsync( - erc721NonCompliantBalanceThresholdWrapper.fillOrderAsync( - compliantSignedOrder, - nonCompliantAddress, - {takerAssetFillAmount} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - }); - - describe('fillOrderNoThrow', () => { - beforeEach(async () => { - erc20Balances = await erc20Wrapper.getBalancesAsync(); - compliantSignedOrder = await orderFactory.newSignedOrderAsync(); - }); - it('should transfer the correct amounts and validate both maker/taker when both maker and taker meet the balance threshold', async () => { - // Execute a valid fill - const txReceipt = await erc721TakerBalanceThresholdWrapper.fillOrderNoThrowAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount}); - // Assert validated addresses - const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedFillOrderTx.signerAddress]; - assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); - // Check balances - const newBalances = await erc20Wrapper.getBalancesAsync(); - const makerAssetFillAmount = takerAssetFillAmount - .times(compliantSignedOrder.makerAssetAmount) - .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); - const makerFeePaid = compliantSignedOrder.makerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); - const takerFeePaid = compliantSignedOrder.takerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); - expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), - ); - expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount), - ); - expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount), - ); - expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), - ); - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)), - ); - }); - it('should revert if maker does not meet the balance threshold', async () => { - // Create signed order with non-compliant maker address - const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - senderAddress: erc721CompliantForwarderInstance.address, - makerAddress: nonCompliantAddress - }); - // Execute transaction - return expectTransactionFailedAsync( - erc721TakerBalanceThresholdWrapper.fillOrderNoThrowAsync( - signedOrderWithBadMakerAddress, - compliantTakerAddress, - {takerAssetFillAmount} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - it('should revert if taker does not meet the balance threshold', async () => { - return expectTransactionFailedAsync( - erc721NonCompliantBalanceThresholdWrapper.fillOrderNoThrowAsync( - compliantSignedOrder, - nonCompliantAddress, - {takerAssetFillAmount} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - }); - - describe('fillOrKillOrder', () => { - beforeEach(async () => { - erc20Balances = await erc20Wrapper.getBalancesAsync(); - compliantSignedOrder = await orderFactory.newSignedOrderAsync(); - }); - it('should transfer the correct amounts and validate both maker/taker when both maker and taker meet the balance threshold', async () => { - // Execute a valid fill - const takerAssetFillAmount_ = compliantSignedOrder.takerAssetAmount; - const txReceipt = await erc721TakerBalanceThresholdWrapper.fillOrKillOrderAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount: takerAssetFillAmount_}); - // Assert validated addresses - const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedFillOrderTx.signerAddress]; - assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); - // Check balances - const newBalances = await erc20Wrapper.getBalancesAsync(); - const makerAssetFillAmount = takerAssetFillAmount_ - .times(compliantSignedOrder.makerAssetAmount) - .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); - const makerFeePaid = compliantSignedOrder.makerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); - const takerFeePaid = compliantSignedOrder.takerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); - expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount_), - ); - expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), - ); - expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount_), - ); - expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount), - ); - expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), - ); - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)), - ); - }); - it('should revert if maker does not meet the balance threshold', async () => { - // Create signed order with non-compliant maker address - const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - senderAddress: erc721CompliantForwarderInstance.address, - makerAddress: nonCompliantAddress - }); - // Execute transaction - return expectTransactionFailedAsync( - erc721TakerBalanceThresholdWrapper.fillOrKillOrderAsync( - signedOrderWithBadMakerAddress, - compliantTakerAddress, - {takerAssetFillAmount} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - it('should revert if taker does not meet the balance threshold', async () => { - return expectTransactionFailedAsync( - erc721NonCompliantBalanceThresholdWrapper.fillOrKillOrderAsync( - compliantSignedOrder, - nonCompliantAddress, - {takerAssetFillAmount} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - it('should revert if takerAssetFillAmount is not fully filled', async () => { - const tooBigTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.times(2); - return expectTransactionFailedAsync( - erc721TakerBalanceThresholdWrapper.fillOrKillOrderAsync( - compliantSignedOrder, - compliantTakerAddress, - {takerAssetFillAmount: tooBigTakerAssetFillAmount} - ), - RevertReason.FailedExecution - ); - }); - }); - - describe('marketSellOrders', () => { - beforeEach(async () => { - erc20Balances = await erc20Wrapper.getBalancesAsync(); - compliantSignedOrder = await orderFactory.newSignedOrderAsync(); - compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); - }); - it('should transfer the correct amounts and validate both makers/taker when both makers and taker meet the balance threshold', async () => { - // Execute a valid fill - const orders = [compliantSignedOrder, compliantSignedOrder2]; - const cumulativeTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.plus(takerAssetFillAmount); - const txReceipt = await erc721TakerBalanceThresholdWrapper.marketSellOrdersAsync(orders, compliantTakerAddress, {takerAssetFillAmount: cumulativeTakerAssetFillAmount}); - // Assert validated addresses - const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; - assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); - // Check balances - const newBalances = await erc20Wrapper.getBalancesAsync(); - const makerAssetFillAmount2 = takerAssetFillAmount - .times(compliantSignedOrder.makerAssetAmount) - .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); - const makerFeePaid2 = compliantSignedOrder2.makerFee - .times(makerAssetFillAmount2) - .dividedToIntegerBy(compliantSignedOrder2.makerAssetAmount); - const takerFeePaid2 = compliantSignedOrder2.takerFee - .times(makerAssetFillAmount2) - .dividedToIntegerBy(compliantSignedOrder2.makerAssetAmount); - const takerFeePaid = compliantSignedOrder.takerFee.plus(takerFeePaid2); - const cumulativeMakerAssetFillAmount = compliantSignedOrder.makerAssetAmount.plus(makerAssetFillAmount2); - // Maker #1 - expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(compliantSignedOrder.makerAssetAmount), - ); - expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(compliantSignedOrder.takerAssetAmount), - ); - expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][zrxToken.address].minus(compliantSignedOrder.makerFee), - ); - // Maker #2 - expect(newBalances[compliantMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount2), - ); - expect(newBalances[compliantMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress2][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][zrxToken.address].minus(makerFeePaid2), - ); - // Taker - expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount), - ); - expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(cumulativeMakerAssetFillAmount), - ); - expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), - ); - // Fee recipient - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].add(compliantSignedOrder.makerFee).add(makerFeePaid2).add(takerFeePaid), - ); - }); - it('should revert if one maker does not meet the balance threshold', async () => { - // Create order set with one non-compliant maker address - const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - makerAddress: nonCompliantAddress - }); - const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; - // Execute transaction - return expectTransactionFailedAsync( - erc721TakerBalanceThresholdWrapper.marketSellOrdersAsync( - orders, - compliantTakerAddress, - {takerAssetFillAmount} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - it('should revert if taker does not meet the balance threshold', async () => { - const orders = [compliantSignedOrder, compliantSignedOrder2]; - return expectTransactionFailedAsync( - erc721NonCompliantBalanceThresholdWrapper.marketSellOrdersAsync( - orders, - nonCompliantAddress, - {takerAssetFillAmount} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - }); - - describe('marketSellOrdersNoThrow', () => { - beforeEach(async () => { - erc20Balances = await erc20Wrapper.getBalancesAsync(); - compliantSignedOrder = await orderFactory.newSignedOrderAsync(); - compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); - }); - it('should transfer the correct amounts and validate both makers/taker when both makers and taker meet the balance threshold', async () => { - // Execute a valid fill - const orders = [compliantSignedOrder, compliantSignedOrder2]; - const cumulativeTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.plus(takerAssetFillAmount); - const txReceipt = await erc721TakerBalanceThresholdWrapper.marketSellOrdersNoThrowAsync(orders, compliantTakerAddress, {takerAssetFillAmount: cumulativeTakerAssetFillAmount}); - // Assert validated addresses - const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; - assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); - // Check balances - const newBalances = await erc20Wrapper.getBalancesAsync(); - const makerAssetFillAmount2 = takerAssetFillAmount - .times(compliantSignedOrder.makerAssetAmount) - .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); - const makerFeePaid2 = compliantSignedOrder2.makerFee - .times(makerAssetFillAmount2) - .dividedToIntegerBy(compliantSignedOrder2.makerAssetAmount); - const takerFeePaid2 = compliantSignedOrder2.takerFee - .times(makerAssetFillAmount2) - .dividedToIntegerBy(compliantSignedOrder2.makerAssetAmount); - const takerFeePaid = compliantSignedOrder.takerFee.plus(takerFeePaid2); - const cumulativeMakerAssetFillAmount = compliantSignedOrder.makerAssetAmount.plus(makerAssetFillAmount2); - // Maker #1 - expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(compliantSignedOrder.makerAssetAmount), - ); - expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(compliantSignedOrder.takerAssetAmount), - ); - expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][zrxToken.address].minus(compliantSignedOrder.makerFee), - ); - // Maker #2 - expect(newBalances[compliantMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount2), - ); - expect(newBalances[compliantMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress2][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][zrxToken.address].minus(makerFeePaid2), - ); - // Taker - expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount), - ); - expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(cumulativeMakerAssetFillAmount), - ); - expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), - ); - // Fee recipient - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].add(compliantSignedOrder.makerFee).add(makerFeePaid2).add(takerFeePaid), - ); - }); - it('should revert if one maker does not meet the balance threshold', async () => { - // Create order set with one non-compliant maker address - const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - makerAddress: nonCompliantAddress - }); - const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; - // Execute transaction - return expectTransactionFailedAsync( - erc721TakerBalanceThresholdWrapper.marketSellOrdersNoThrowAsync( - orders, - compliantTakerAddress, - {takerAssetFillAmount} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - it('should revert if taker does not meet the balance threshold', async () => { - const orders = [compliantSignedOrder, compliantSignedOrder2]; - return expectTransactionFailedAsync( - erc721NonCompliantBalanceThresholdWrapper.marketSellOrdersNoThrowAsync( - orders, - nonCompliantAddress, - {takerAssetFillAmount} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - }); - - describe('marketBuyOrders', () => { - beforeEach(async () => { - erc20Balances = await erc20Wrapper.getBalancesAsync(); - compliantSignedOrder = await orderFactory.newSignedOrderAsync(); - compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); - }); - it('should transfer the correct amounts and validate both makers/taker when both makers and taker meet the balance threshold', async () => { - // Execute a valid fill - const orders = [compliantSignedOrder, compliantSignedOrder2]; - const cumulativeTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.plus(takerAssetFillAmount); - const makerAssetFillAmount2 = takerAssetFillAmount - .times(compliantSignedOrder.makerAssetAmount) - .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); - const cumulativeMakerAssetFillAmount = compliantSignedOrder.makerAssetAmount.plus(makerAssetFillAmount2); - const txReceipt = await erc721TakerBalanceThresholdWrapper.marketBuyOrdersAsync(orders, compliantTakerAddress, {makerAssetFillAmount: cumulativeMakerAssetFillAmount}); - // Assert validated addresses - const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; - assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); - // Check balances - const newBalances = await erc20Wrapper.getBalancesAsync(); - const makerFeePaid2 = compliantSignedOrder2.makerFee - .times(makerAssetFillAmount2) - .dividedToIntegerBy(compliantSignedOrder2.makerAssetAmount); - const takerFeePaid2 = compliantSignedOrder2.takerFee - .times(makerAssetFillAmount2) - .dividedToIntegerBy(compliantSignedOrder2.makerAssetAmount); - const takerFeePaid = compliantSignedOrder.takerFee.plus(takerFeePaid2); - // Maker #1 - expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(compliantSignedOrder.makerAssetAmount), - ); - expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(compliantSignedOrder.takerAssetAmount), - ); - expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][zrxToken.address].minus(compliantSignedOrder.makerFee), - ); - // Maker #2 - expect(newBalances[compliantMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount2), - ); - expect(newBalances[compliantMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress2][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][zrxToken.address].minus(makerFeePaid2), - ); - // Taker - expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount), - ); - expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(cumulativeMakerAssetFillAmount), - ); - expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), - ); - // Fee recipient - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].add(compliantSignedOrder.makerFee).add(makerFeePaid2).add(takerFeePaid), - ); - }); - it('should revert if one maker does not meet the balance threshold', async () => { - // Create order set with one non-compliant maker address - const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - makerAddress: nonCompliantAddress - }); - const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; - // Execute transaction - const dummyMakerAssetFillAmount = new BigNumber(0); - return expectTransactionFailedAsync( - erc721TakerBalanceThresholdWrapper.marketBuyOrdersAsync( - orders, - compliantTakerAddress, - {makerAssetFillAmount: dummyMakerAssetFillAmount} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - it('should revert if taker does not meet the balance threshold', async () => { - const orders = [compliantSignedOrder, compliantSignedOrder2]; - const dummyMakerAssetFillAmount = new BigNumber(0); - return expectTransactionFailedAsync( - erc721NonCompliantBalanceThresholdWrapper.marketBuyOrdersAsync( - orders, - nonCompliantAddress, - {makerAssetFillAmount: dummyMakerAssetFillAmount} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - }); - - describe('marketBuyOrdersNoThrowAsync', () => { - beforeEach(async () => { - erc20Balances = await erc20Wrapper.getBalancesAsync(); - compliantSignedOrder = await orderFactory.newSignedOrderAsync(); - compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); - }); - it('should transfer the correct amounts and validate both makers/taker when both makers and taker meet the balance threshold', async () => { - // Execute a valid fill - const orders = [compliantSignedOrder, compliantSignedOrder2]; - const cumulativeTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.plus(takerAssetFillAmount); - const makerAssetFillAmount2 = takerAssetFillAmount - .times(compliantSignedOrder.makerAssetAmount) - .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); - const cumulativeMakerAssetFillAmount = compliantSignedOrder.makerAssetAmount.plus(makerAssetFillAmount2); - const txReceipt = await erc721TakerBalanceThresholdWrapper.marketBuyOrdersNoThrowAsync(orders, compliantTakerAddress, {makerAssetFillAmount: cumulativeMakerAssetFillAmount}); - // Assert validated addresses - const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; - assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); - // Check balances - const newBalances = await erc20Wrapper.getBalancesAsync(); - const makerFeePaid2 = compliantSignedOrder2.makerFee - .times(makerAssetFillAmount2) - .dividedToIntegerBy(compliantSignedOrder2.makerAssetAmount); - const takerFeePaid2 = compliantSignedOrder2.takerFee - .times(makerAssetFillAmount2) - .dividedToIntegerBy(compliantSignedOrder2.makerAssetAmount); - const takerFeePaid = compliantSignedOrder.takerFee.plus(takerFeePaid2); - // Maker #1 - expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(compliantSignedOrder.makerAssetAmount), - ); - expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(compliantSignedOrder.takerAssetAmount), - ); - expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][zrxToken.address].minus(compliantSignedOrder.makerFee), - ); - // Maker #2 - expect(newBalances[compliantMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount2), - ); - expect(newBalances[compliantMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress2][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][zrxToken.address].minus(makerFeePaid2), - ); - // Taker - expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount), - ); - expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(cumulativeMakerAssetFillAmount), - ); - expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), - ); - // Fee recipient - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].add(compliantSignedOrder.makerFee).add(makerFeePaid2).add(takerFeePaid), - ); - }); - it('should revert if one maker does not meet the balance threshold', async () => { - // Create order set with one non-compliant maker address - const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - makerAddress: nonCompliantAddress - }); - const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; - // Execute transaction - const dummyMakerAssetFillAmount = new BigNumber(0); - return expectTransactionFailedAsync( - erc721TakerBalanceThresholdWrapper.marketBuyOrdersNoThrowAsync( - orders, - compliantTakerAddress, - {makerAssetFillAmount: dummyMakerAssetFillAmount} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - it('should revert if taker does not meet the balance threshold', async () => { - const orders = [compliantSignedOrder, compliantSignedOrder2]; - const dummyMakerAssetFillAmount = new BigNumber(0); - return expectTransactionFailedAsync( - erc721NonCompliantBalanceThresholdWrapper.marketBuyOrdersNoThrowAsync( - orders, - nonCompliantAddress, - {makerAssetFillAmount: dummyMakerAssetFillAmount} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - }); - - describe('matchOrders', () => { - beforeEach(async () => { - erc20Balances = await erc20Wrapper.getBalancesAsync(); - compliantSignedOrder = await orderFactory.newSignedOrderAsync(); - compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); - }); - it('Should transfer correct amounts when both makers and taker meet the balance threshold', async () => { - // Test values/results taken from Match Orders test: - // 'Should transfer correct amounts when right order is fully filled and values pass isRoundingErrorFloor but fail isRoundingErrorCeil' - // Create orders to match - const signedOrderLeft = await orderFactory.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(17), 0), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(98), 0), - makerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 18), - takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 18), - feeRecipientAddress: feeRecipientAddress, - }); - const signedOrderRight = await orderFactory2.newSignedOrderAsync({ - makerAssetData: assetDataUtils.encodeERC20AssetData(defaultTakerAssetAddress), - takerAssetData: assetDataUtils.encodeERC20AssetData(defaultMakerAssetAddress), - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(75), 0), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(13), 0), - makerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 18), - takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 18), - feeRecipientAddress: feeRecipientAddress, - }); - // Compute expected transfer amounts - const expectedTransferAmounts = { - // Left Maker - amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(13), 0), - amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(75), 0), - feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber('76.4705882352941176'), 16), // 76.47% - // Right Maker - amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(75), 0), - amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(13), 0), - feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - // Taker - amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(0), 0), - feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber('76.5306122448979591'), 16), // 76.53% - feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - }; - const txReceipt = await erc721TakerBalanceThresholdWrapper.matchOrdersAsync(signedOrderLeft, signedOrderRight, compliantTakerAddress); - // Assert validated addresses - const expectedValidatedAddresseses = [signedOrderLeft.makerAddress, signedOrderRight.makerAddress, compliantTakerAddress]; - assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); - // Check balances - const newBalances = await erc20Wrapper.getBalancesAsync(); - expect( - newBalances[signedOrderLeft.makerAddress][defaultMakerAssetAddress], - 'Checking left maker egress ERC20 account balance', - ).to.be.bignumber.equal(erc20Balances[signedOrderLeft.makerAddress][defaultMakerAssetAddress].sub(expectedTransferAmounts.amountSoldByLeftMaker)); - expect( - newBalances[signedOrderRight.makerAddress][defaultTakerAssetAddress], - 'Checking right maker ingress ERC20 account balance', - ).to.be.bignumber.equal(erc20Balances[signedOrderRight.makerAddress][defaultTakerAssetAddress].sub(expectedTransferAmounts.amountSoldByRightMaker)); - expect( - newBalances[compliantTakerAddress][defaultMakerAssetAddress], - 'Checking taker ingress ERC20 account balance', - ).to.be.bignumber.equal(erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(expectedTransferAmounts.amountReceivedByTaker)); - expect( - newBalances[signedOrderLeft.makerAddress][defaultTakerAssetAddress], - 'Checking left maker ingress ERC20 account balance', - ).to.be.bignumber.equal(erc20Balances[signedOrderLeft.makerAddress][defaultTakerAssetAddress].add(expectedTransferAmounts.amountBoughtByLeftMaker)); - expect( - newBalances[signedOrderRight.makerAddress][defaultMakerAssetAddress], - 'Checking right maker egress ERC20 account balance', - ).to.be.bignumber.equal( - erc20Balances[signedOrderRight.makerAddress][defaultMakerAssetAddress].add(expectedTransferAmounts.amountBoughtByRightMaker), - ); - // Paid fees - expect( - newBalances[signedOrderLeft.makerAddress][zrxToken.address], - 'Checking left maker egress ERC20 account fees', - ).to.be.bignumber.equal(erc20Balances[signedOrderLeft.makerAddress][zrxToken.address].minus(expectedTransferAmounts.feePaidByLeftMaker)); - expect( - newBalances[signedOrderRight.makerAddress][zrxToken.address], - 'Checking right maker egress ERC20 account fees', - ).to.be.bignumber.equal(erc20Balances[signedOrderRight.makerAddress][zrxToken.address].minus(expectedTransferAmounts.feePaidByRightMaker)); - expect( - newBalances[compliantTakerAddress][zrxToken.address], - 'Checking taker egress ERC20 account fees', - ).to.be.bignumber.equal(erc20Balances[compliantTakerAddress][zrxToken.address].minus(expectedTransferAmounts.feePaidByTakerLeft).sub(expectedTransferAmounts.feePaidByTakerRight)); - // Received fees - expect( - newBalances[signedOrderLeft.feeRecipientAddress][zrxToken.address], - 'Checking left fee recipient ingress ERC20 account fees', - ).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].add(expectedTransferAmounts.feePaidByLeftMaker).add(expectedTransferAmounts.feePaidByRightMaker).add(expectedTransferAmounts.feePaidByTakerLeft).add(expectedTransferAmounts.feePaidByTakerRight), - ); - }); - it('should revert if left maker does not meet the balance threshold', async () => { - // Create signed order with non-compliant maker address - const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - senderAddress: erc721CompliantForwarderInstance.address, - makerAddress: nonCompliantAddress - }); - // Execute transaction - return expectTransactionFailedAsync( - erc721TakerBalanceThresholdWrapper.matchOrdersAsync( - compliantSignedOrder, - signedOrderWithBadMakerAddress, - compliantTakerAddress, - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - it('should revert if right maker does not meet the balance threshold', async () => { - // Create signed order with non-compliant maker address - const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - senderAddress: erc721CompliantForwarderInstance.address, - makerAddress: nonCompliantAddress - }); - // Execute transaction - return expectTransactionFailedAsync( - erc721TakerBalanceThresholdWrapper.matchOrdersAsync( - signedOrderWithBadMakerAddress, - compliantSignedOrder, - compliantTakerAddress, - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - it('should revert if taker does not meet the balance threshold', async () => { - return expectTransactionFailedAsync( - erc721NonCompliantBalanceThresholdWrapper.matchOrdersAsync( - compliantSignedOrder, - compliantSignedOrder, - nonCompliantAddress, - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - }); - - describe('cancelOrder', () => { - beforeEach(async () => { - erc20Balances = await erc20Wrapper.getBalancesAsync(); - compliantSignedOrder = await orderFactory.newSignedOrderAsync(); - compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); - }); - it('Should successfully cancel order if maker meets balance threshold', async () => { - // Verify order is not cancelled - const orderInfoBeforeCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) - expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); - // Cancel - const txReceipt = await erc721MakerBalanceThresholdWrapper.cancelOrderAsync(compliantSignedOrder, compliantSignedOrder.makerAddress); - // Assert validated addresses - const expectedValidatedAddresseses: string[] = []; - assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); - // Check that order was cancelled - const orderInfoAfterCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) - expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED); - }); - it('Should successfully cancel order if maker does not meet balance threshold', async () => { - // Create order where maker does not meet balance threshold - const signedOrderWithBadMakerAddress = await nonCompliantOrderFactory.newSignedOrderAsync({}); - // Verify order is not cancelled - const orderInfoBeforeCancelling = await erc721NonCompliantBalanceThresholdWrapper.getOrderInfoAsync(signedOrderWithBadMakerAddress) - expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); - // Cancel - const txReceipt = await erc721NonCompliantBalanceThresholdWrapper.cancelOrderAsync(signedOrderWithBadMakerAddress, signedOrderWithBadMakerAddress.makerAddress); - // Assert validated addresses - const expectedValidatedAddresseses: string[] = []; - assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); - // Check that order was cancelled - const orderInfoAfterCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync(signedOrderWithBadMakerAddress) - expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED); - }); - }); - - describe('batchCancelOrders', () => { - beforeEach(async () => { - erc20Balances = await erc20Wrapper.getBalancesAsync(); - }); - it('Should successfully batch cancel orders if maker meets balance threshold', async () => { - // Create orders to cancel - const compliantSignedOrders = [ - await orderFactory.newSignedOrderAsync(), - await orderFactory.newSignedOrderAsync(), - await orderFactory.newSignedOrderAsync(), - ]; - // Verify orders are not cancelled - await _.each(compliantSignedOrders, async (compliantSignedOrder) => { - const orderInfoBeforeCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) - return expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); - }); - // Cancel - const txReceipt = await erc721MakerBalanceThresholdWrapper.batchCancelOrdersAsync(compliantSignedOrders, compliantSignedOrder.makerAddress); - // Assert validated addresses - const expectedValidatedAddresseses: string[] = []; - assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); - // Check that order was cancelled - await _.each(compliantSignedOrders, async (compliantSignedOrder) => { - const orderInfoAfterCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) - return expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED); - }); - }); - it('Should successfully batch cancel order if maker does not meet balance threshold', async () => { - // Create orders to cancel - const nonCompliantSignedOrders = [ - await nonCompliantOrderFactory.newSignedOrderAsync(), - await nonCompliantOrderFactory.newSignedOrderAsync(), - await nonCompliantOrderFactory.newSignedOrderAsync(), - ]; - // Verify orders are not cancelled - await _.each(nonCompliantSignedOrders, async (nonCompliantSignedOrder) => { - const orderInfoBeforeCancelling = await erc721NonCompliantBalanceThresholdWrapper.getOrderInfoAsync(nonCompliantSignedOrder) - return expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); - }); - // Cancel - const txReceipt = await erc721NonCompliantBalanceThresholdWrapper.batchCancelOrdersAsync(nonCompliantSignedOrders, nonCompliantAddress); - // Assert validated addresses - const expectedValidatedAddresseses: string[] = []; - assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); - // Check that order was cancelled - await _.each(nonCompliantSignedOrders, async (nonCompliantSignedOrder) => { - const orderInfoAfterCancelling = await erc721NonCompliantBalanceThresholdWrapper.getOrderInfoAsync(nonCompliantSignedOrder) - return expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED); - }); - }); - }); - - describe('cancelOrdersUpTo', () => { - beforeEach(async () => { - erc20Balances = await erc20Wrapper.getBalancesAsync(); - }); - it('Should successfully batch cancel orders if maker meets balance threshold', async () => { - // Create orders to cancel - const compliantSignedOrders = [ - await orderFactory.newSignedOrderAsync({salt: new BigNumber(0)}), - await orderFactory.newSignedOrderAsync({salt: new BigNumber(1)}), - await orderFactory.newSignedOrderAsync({salt: new BigNumber(2)}), - ]; - // Verify orders are not cancelled - await _.each(compliantSignedOrders, async (compliantSignedOrder) => { - const orderInfoBeforeCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) - return expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); - }); - // Cancel - const cancelOrdersUpToThisSalt = new BigNumber(1); - const txReceipt = await erc721MakerBalanceThresholdWrapper.cancelOrdersUpToAsync(cancelOrdersUpToThisSalt, compliantSignedOrder.makerAddress); - // Assert validated addresses - const expectedValidatedAddresseses: string[] = []; - assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); - // Check that order was cancelled - await _.each(compliantSignedOrders, async (compliantSignedOrder, salt: number) => { - const orderInfoAfterCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) - const saltAsBigNumber = new BigNumber(salt); - if (saltAsBigNumber.lessThanOrEqualTo(cancelOrdersUpToThisSalt)) { - return expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED); - } else { - return expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); - } - }); - }); - it('Should successfully batch cancel order if maker does not meet balance threshold', async () => { - // Create orders to cancel - const nonCompliantSignedOrders = [ - await nonCompliantOrderFactory.newSignedOrderAsync({salt: new BigNumber(0)}), - await nonCompliantOrderFactory.newSignedOrderAsync({salt: new BigNumber(1)}), - await nonCompliantOrderFactory.newSignedOrderAsync({salt: new BigNumber(2)}), - ]; - // Verify orders are not cancelled - await _.each(nonCompliantSignedOrders, async (nonCompliantSignedOrder) => { - const orderInfoBeforeCancelling = await erc721NonCompliantBalanceThresholdWrapper.getOrderInfoAsync(nonCompliantSignedOrder) - return expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); - }); - // Cancel - const cancelOrdersUpToThisSalt = new BigNumber(1); - const txReceipt = await erc721NonCompliantBalanceThresholdWrapper.cancelOrdersUpToAsync(cancelOrdersUpToThisSalt, nonCompliantAddress); - // Assert validated addresses - const expectedValidatedAddresseses: string[] = []; - assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); - // Check that order was cancelled - await _.each(nonCompliantSignedOrders, async (nonCompliantSignedOrder, salt: number) => { - const orderInfoAfterCancelling = await erc721NonCompliantBalanceThresholdWrapper.getOrderInfoAsync(nonCompliantSignedOrder) - const saltAsBigNumber = new BigNumber(salt); - if (saltAsBigNumber.lessThanOrEqualTo(cancelOrdersUpToThisSalt)) { - return expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED); - } else { - return expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); - } - }); - }); - }); -}); -// tslint:disable:max-file-line-count -// tslint:enable:no-unnecessary-type-assertion diff --git a/packages/contracts/test/utils/balance_threshold_wrapper.ts b/packages/contracts/test/utils/balance_threshold_wrapper.ts deleted file mode 100644 index cff40aa52..000000000 --- a/packages/contracts/test/utils/balance_threshold_wrapper.ts +++ /dev/null @@ -1,247 +0,0 @@ -import { SignedOrder } from '@0x/types'; -import { BigNumber } from '@0x/utils'; -import { Web3Wrapper } from '@0x/web3-wrapper'; -import { Provider, TransactionReceiptWithDecodedLogs } from 'ethereum-types'; -import * as _ from 'lodash'; - -import { ExchangeContract } from '../../generated-wrappers/exchange'; -import { BalanceThresholdFilterContract } from '../../generated-wrappers/balance_threshold_filter'; - -import { formatters } from './formatters'; -import { LogDecoder } from './log_decoder'; -import { orderUtils } from './order_utils'; -import { TransactionFactory } from '../utils/transaction_factory'; -import { OrderInfo } from './types'; - -export class BalanceThresholdWrapper { - private readonly _balanceThresholdFilter: BalanceThresholdFilterContract; - private readonly _signerTransactionFactory: TransactionFactory; - private readonly _exchange: ExchangeContract; - private readonly _web3Wrapper: Web3Wrapper; - private readonly _logDecoder: LogDecoder; - constructor(balanceThresholdFilter: BalanceThresholdFilterContract, exchangeContract: ExchangeContract, signerTransactionFactory: TransactionFactory, provider: Provider) { - this._balanceThresholdFilter = balanceThresholdFilter; - this._exchange = exchangeContract; - this._signerTransactionFactory = signerTransactionFactory; - this._web3Wrapper = new Web3Wrapper(provider); - this._logDecoder = new LogDecoder(this._web3Wrapper); - } - public async fillOrderAsync( - signedOrder: SignedOrder, - from: string, - opts: { takerAssetFillAmount?: BigNumber } = {}, - ): Promise { - const params = orderUtils.createFill(signedOrder, opts.takerAssetFillAmount); - const data = this._exchange.fillOrder.getABIEncodedTransactionData( - params.order, - params.takerAssetFillAmount, - params.signature, - ); - const txReceipt = this._executeTransaction(data, from); - return txReceipt; - } - public async fillOrKillOrderAsync( - signedOrder: SignedOrder, - from: string, - opts: { takerAssetFillAmount?: BigNumber } = {}, - ): Promise { - const params = orderUtils.createFill(signedOrder, opts.takerAssetFillAmount); - const data = this._exchange.fillOrKillOrder.getABIEncodedTransactionData( - params.order, - params.takerAssetFillAmount, - params.signature, - ); - const txReceipt = this._executeTransaction(data, from); - return txReceipt; - } - public async fillOrderNoThrowAsync( - signedOrder: SignedOrder, - from: string, - opts: { takerAssetFillAmount?: BigNumber; gas?: number } = {}, - ): Promise { - const params = orderUtils.createFill(signedOrder, opts.takerAssetFillAmount); - const data = this._exchange.fillOrderNoThrow.getABIEncodedTransactionData( - params.order, - params.takerAssetFillAmount, - params.signature, - ); - const txReceipt = this._executeTransaction(data, from, opts.gas); - return txReceipt; - } - public async batchFillOrdersAsync( - orders: SignedOrder[], - from: string, - opts: { takerAssetFillAmounts?: BigNumber[] } = {}, - ): Promise { - const params = formatters.createBatchFill(orders, opts.takerAssetFillAmounts); - const data = this._exchange.batchFillOrders.getABIEncodedTransactionData( - params.orders, - params.takerAssetFillAmounts, - params.signatures, - ); - const txReceipt = this._executeTransaction(data, from); - return txReceipt; - } - public async batchFillOrKillOrdersAsync( - orders: SignedOrder[], - from: string, - opts: { takerAssetFillAmounts?: BigNumber[] } = {}, - ): Promise { - const params = formatters.createBatchFill(orders, opts.takerAssetFillAmounts); - const data = this._exchange.batchFillOrKillOrders.getABIEncodedTransactionData( - params.orders, - params.takerAssetFillAmounts, - params.signatures, - ); - const txReceipt = this._executeTransaction(data, from); - return txReceipt; - } - public async batchFillOrdersNoThrowAsync( - orders: SignedOrder[], - from: string, - opts: { takerAssetFillAmounts?: BigNumber[]; gas?: number } = {}, - ): Promise { - const params = formatters.createBatchFill(orders, opts.takerAssetFillAmounts); - const data = this._exchange.batchFillOrKillOrders.getABIEncodedTransactionData( - params.orders, - params.takerAssetFillAmounts, - params.signatures, - ); - const txReceipt = this._executeTransaction(data, from, opts.gas); - return txReceipt; - } - public async marketSellOrdersAsync( - orders: SignedOrder[], - from: string, - opts: { takerAssetFillAmount: BigNumber }, - ): Promise { - const params = formatters.createMarketSellOrders(orders, opts.takerAssetFillAmount); - const data = this._exchange.marketSellOrders.getABIEncodedTransactionData( - params.orders, - params.takerAssetFillAmount, - params.signatures, - ); - const txReceipt = this._executeTransaction(data, from); - return txReceipt; - } - public async marketSellOrdersNoThrowAsync( - orders: SignedOrder[], - from: string, - opts: { takerAssetFillAmount: BigNumber; gas?: number }, - ): Promise { - const params = formatters.createMarketSellOrders(orders, opts.takerAssetFillAmount); - const data = this._exchange.marketSellOrdersNoThrow.getABIEncodedTransactionData( - params.orders, - params.takerAssetFillAmount, - params.signatures, - ); - const txReceipt = this._executeTransaction(data, from, opts.gas); - return txReceipt; - } - public async marketBuyOrdersAsync( - orders: SignedOrder[], - from: string, - opts: { makerAssetFillAmount: BigNumber }, - ): Promise { - const params = formatters.createMarketBuyOrders(orders, opts.makerAssetFillAmount); - const data = this._exchange.marketBuyOrders.getABIEncodedTransactionData( - params.orders, - params.makerAssetFillAmount, - params.signatures, - ); - const txReceipt = this._executeTransaction(data, from); - return txReceipt; - } - public async marketBuyOrdersNoThrowAsync( - orders: SignedOrder[], - from: string, - opts: { makerAssetFillAmount: BigNumber; gas?: number }, - ): Promise { - const params = formatters.createMarketBuyOrders(orders, opts.makerAssetFillAmount); - const data = this._exchange.marketBuyOrdersNoThrow.getABIEncodedTransactionData( - params.orders, - params.makerAssetFillAmount, - params.signatures, - ); - const txReceipt = this._executeTransaction(data, from, opts.gas); - return txReceipt; - } - public async cancelOrderAsync(signedOrder: SignedOrder, from: string): Promise { - const params = orderUtils.createCancel(signedOrder); - const data = this._exchange.cancelOrder.getABIEncodedTransactionData(params.order); - const txReceipt = this._executeTransaction(data, from); - return txReceipt; - } - public async batchCancelOrdersAsync( - orders: SignedOrder[], - from: string, - ): Promise { - const params = formatters.createBatchCancel(orders); - const data = this._exchange.batchCancelOrders.getABIEncodedTransactionData(params.orders); - const txReceipt = this._executeTransaction(data, from); - return txReceipt; - } - public async cancelOrdersUpToAsync(salt: BigNumber, from: string): Promise { - const data = this._exchange.cancelOrdersUpTo.getABIEncodedTransactionData(salt); - const txReceipt = this._executeTransaction(data, from); - return txReceipt; - } - public async getTakerAssetFilledAmountAsync(orderHashHex: string): Promise { - const filledAmount = await this._exchange.filled.callAsync(orderHashHex); - return filledAmount; - } - public async isCancelledAsync(orderHashHex: string): Promise { - const isCancelled = await this._exchange.cancelled.callAsync(orderHashHex); - return isCancelled; - } - public async getOrderEpochAsync(makerAddress: string, senderAddress: string): Promise { - const orderEpoch = await this._exchange.orderEpoch.callAsync(makerAddress, senderAddress); - return orderEpoch; - } - public async getOrderInfoAsync(signedOrder: SignedOrder): Promise { - const orderInfo = (await this._exchange.getOrderInfo.callAsync(signedOrder)) as OrderInfo; - return orderInfo; - } - public async getOrdersInfoAsync(signedOrders: SignedOrder[]): Promise { - const ordersInfo = (await this._exchange.getOrdersInfo.callAsync(signedOrders)) as OrderInfo[]; - return ordersInfo; - } - public async matchOrdersAsync( - signedOrderLeft: SignedOrder, - signedOrderRight: SignedOrder, - from: string, - ): Promise { - const params = orderUtils.createMatchOrders(signedOrderLeft, signedOrderRight); - const data = await this._exchange.matchOrders.getABIEncodedTransactionData( - params.left, - params.right, - params.leftSignature, - params.rightSignature - ); - const txReceipt = this._executeTransaction(data, from); - return txReceipt; - } - public getBalanceThresholdAddress(): string { - return this._balanceThresholdFilter.address; - } - public getExchangeAddress(): string { - return this._exchange.address; - } - // Exchange functions - //abiEncodeFillOrder - //getFillOrderResultsAsync - // - private async _executeTransaction(abiEncodedExchangeTxData: string, from: string, gas?: number): Promise { - const signedExchangeTx = this._signerTransactionFactory.newSignedTransaction(abiEncodedExchangeTxData); - const txOpts = _.isUndefined(gas) ? {from} : {from, gas}; - const txHash = await this._balanceThresholdFilter.executeTransaction.sendTransactionAsync( - signedExchangeTx.salt, - signedExchangeTx.signerAddress, - signedExchangeTx.data, - signedExchangeTx.signature, - txOpts, - ); - const txReceipt = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); - return txReceipt; - } -} -- cgit From 6a0f5f39eea5fef285a1e2e962be03c2fa8847ab Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 11 Dec 2018 15:55:36 -0800 Subject: Prettier / Linter fixes for TS --- packages/types/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 4d5b6e1f2..4470dd501 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -246,7 +246,7 @@ export enum RevertReason { // Balance Threshold Filter InvalidOrBlockedExchangeSelector = 'INVALID_OR_BLOCKED_EXCHANGE_SELECTOR', BalanceQueryFailed = 'BALANCE_QUERY_FAILED', - AtLeastOneAddressDoesNotMeetBalanceThreshold= 'AT_LEAST_ONE_ADDRESS_DOES_NOT_MEET_BALANCE_THRESHOLD', + AtLeastOneAddressDoesNotMeetBalanceThreshold = 'AT_LEAST_ONE_ADDRESS_DOES_NOT_MEET_BALANCE_THRESHOLD', } export enum StatusCodes { -- cgit From 34ff7fae9cffa7aaba5f0ba0060ed063a855f8e9 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 11 Dec 2018 16:07:19 -0800 Subject: Removed deprecated README + comments --- packages/contracts/contracts/tokens/README.md | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 packages/contracts/contracts/tokens/README.md (limited to 'packages') diff --git a/packages/contracts/contracts/tokens/README.md b/packages/contracts/contracts/tokens/README.md deleted file mode 100644 index b54f0e046..000000000 --- a/packages/contracts/contracts/tokens/README.md +++ /dev/null @@ -1,2 +0,0 @@ -Contracts from https://github.com/sendwyre/yes-compliance-token -Modified to compile in our codebase. -- cgit From f91781a0605f46ee1a40bf979d22ff510f48d464 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 13 Dec 2018 13:52:20 -0800 Subject: Less Assembly. More Solidity. Less Efficiency. More Readability. --- .../utils/ExchangeSelectors/ExchangeSelectors.sol | 151 --------------------- 1 file changed, 151 deletions(-) delete mode 100644 packages/contracts/contracts/utils/ExchangeSelectors/ExchangeSelectors.sol (limited to 'packages') diff --git a/packages/contracts/contracts/utils/ExchangeSelectors/ExchangeSelectors.sol b/packages/contracts/contracts/utils/ExchangeSelectors/ExchangeSelectors.sol deleted file mode 100644 index c361fd075..000000000 --- a/packages/contracts/contracts/utils/ExchangeSelectors/ExchangeSelectors.sol +++ /dev/null @@ -1,151 +0,0 @@ -/* - - Copyright 2018 ZeroEx Intl. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -*/ - -pragma solidity 0.4.24; - - -contract ExchangeSelectors { - - // allowedValidators - bytes4 constant allowedValidatorsSelector = 0x7b8e3514; - bytes4 constant allowedValidatorsSelectorGenerator = bytes4(keccak256('allowedValidators(address,address)')); - - // assetProxies - bytes4 constant assetProxiesSelector = 0x3fd3c997; - bytes4 constant assetProxiesSelectorGenerator = bytes4(keccak256('assetProxies(bytes4)')); - - // batchCancelOrders - bytes4 constant batchCancelOrdersSelector = 0x4ac14782; - bytes4 constant batchCancelOrdersSelectorGenerator = bytes4(keccak256('batchCancelOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[])')); - - // batchFillOrKillOrders - bytes4 constant batchFillOrKillOrdersSelector = 0x4d0ae546; - bytes4 constant batchFillOrKillOrdersSelectorGenerator = bytes4(keccak256('batchFillOrKillOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256[],bytes[])')); - - // batchFillOrders - bytes4 constant batchFillOrdersSelector = 0x297bb70b; - bytes4 constant batchFillOrdersSelectorGenerator = bytes4(keccak256('batchFillOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256[],bytes[])')); - - // batchFillOrdersNoThrow - bytes4 constant batchFillOrdersNoThrowSelector = 0x50dde190; - bytes4 constant batchFillOrdersNoThrowSelectorGenerator = bytes4(keccak256('batchFillOrdersNoThrow((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256[],bytes[])')); - - // cancelOrder - bytes4 constant cancelOrderSelector = 0xd46b02c3; - bytes4 constant cancelOrderSelectorGenerator = bytes4(keccak256('cancelOrder((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes))')); - - // cancelOrdersUpTo - bytes4 constant cancelOrdersUpToSelector = 0x4f9559b1; - bytes4 constant cancelOrdersUpToSelectorGenerator = bytes4(keccak256('cancelOrdersUpTo(uint256)')); - - // cancelled - bytes4 constant cancelledSelector = 0x2ac12622; - bytes4 constant cancelledSelectorGenerator = bytes4(keccak256('cancelled(bytes32)')); - - // currentContextAddress - bytes4 constant currentContextAddressSelector = 0xeea086ba; - bytes4 constant currentContextAddressSelectorGenerator = bytes4(keccak256('currentContextAddress()')); - - // executeTransaction - bytes4 constant executeTransactionSelector = 0xbfc8bfce; - bytes4 constant executeTransactionSelectorGenerator = bytes4(keccak256('executeTransaction(uint256,address,bytes,bytes)')); - - // fillOrKillOrder - bytes4 constant fillOrKillOrderSelector = 0x64a3bc15; - bytes4 constant fillOrKillOrderSelectorGenerator = bytes4(keccak256('fillOrKillOrder((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),uint256,bytes)')); - - // fillOrder - bytes4 constant fillOrderSelector = 0xb4be83d5; - bytes4 constant fillOrderSelectorGenerator = bytes4(keccak256('fillOrder((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),uint256,bytes)')); - - // fillOrderNoThrow - bytes4 constant fillOrderNoThrowSelector = 0x3e228bae; - bytes4 constant fillOrderNoThrowSelectorGenerator = bytes4(keccak256('fillOrderNoThrow((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),uint256,bytes)')); - - // filled - bytes4 constant filledSelector = 0x288cdc91; - bytes4 constant filledSelectorGenerator = bytes4(keccak256('filled(bytes32)')); - - // getAssetProxy - bytes4 constant getAssetProxySelector = 0x60704108; - bytes4 constant getAssetProxySelectorGenerator = bytes4(keccak256('getAssetProxy(bytes4)')); - - // getOrderInfo - bytes4 constant getOrderInfoSelector = 0xc75e0a81; - bytes4 constant getOrderInfoSelectorGenerator = bytes4(keccak256('getOrderInfo((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes))')); - - // getOrdersInfo - bytes4 constant getOrdersInfoSelector = 0x7e9d74dc; - bytes4 constant getOrdersInfoSelectorGenerator = bytes4(keccak256('getOrdersInfo((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[])')); - - // isValidSignature - bytes4 constant isValidSignatureSelector = 0x93634702; - bytes4 constant isValidSignatureSelectorGenerator = bytes4(keccak256('isValidSignature(bytes32,address,bytes)')); - - // marketBuyOrders - bytes4 constant marketBuyOrdersSelector = 0xe5fa431b; - bytes4 constant marketBuyOrdersSelectorGenerator = bytes4(keccak256('marketBuyOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256,bytes[])')); - - // marketBuyOrdersNoThrow - bytes4 constant marketBuyOrdersNoThrowSelector = 0xa3e20380; - bytes4 constant marketBuyOrdersNoThrowSelectorGenerator = bytes4(keccak256('marketBuyOrdersNoThrow((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256,bytes[])')); - - // marketSellOrders - bytes4 constant marketSellOrdersSelector = 0x7e1d9808; - bytes4 constant marketSellOrdersSelectorGenerator = bytes4(keccak256('marketSellOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256,bytes[])')); - - // marketSellOrdersNoThrow - bytes4 constant marketSellOrdersNoThrowSelector = 0xdd1c7d18; - bytes4 constant marketSellOrdersNoThrowSelectorGenerator = bytes4(keccak256('marketSellOrdersNoThrow((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256,bytes[])')); - - // matchOrders - bytes4 constant matchOrdersSelector = 0x3c28d861; - bytes4 constant matchOrdersSelectorGenerator = bytes4(keccak256('matchOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),(address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),bytes,bytes)')); - - // orderEpoch - bytes4 constant orderEpochSelector = 0xd9bfa73e; - bytes4 constant orderEpochSelectorGenerator = bytes4(keccak256('orderEpoch(address,address)')); - - // owner - bytes4 constant ownerSelector = 0x8da5cb5b; - bytes4 constant ownerSelectorGenerator = bytes4(keccak256('owner()')); - - // preSign - bytes4 constant preSignSelector = 0x3683ef8e; - bytes4 constant preSignSelectorGenerator = bytes4(keccak256('preSign(bytes32,address,bytes)')); - - // preSigned - bytes4 constant preSignedSelector = 0x82c174d0; - bytes4 constant preSignedSelectorGenerator = bytes4(keccak256('preSigned(bytes32,address)')); - - // registerAssetProxy - bytes4 constant registerAssetProxySelector = 0xc585bb93; - bytes4 constant registerAssetProxySelectorGenerator = bytes4(keccak256('registerAssetProxy(address)')); - - // setSignatureValidatorApproval - bytes4 constant setSignatureValidatorApprovalSelector = 0x77fcce68; - bytes4 constant setSignatureValidatorApprovalSelectorGenerator = bytes4(keccak256('setSignatureValidatorApproval(address,bool)')); - - // transactions - bytes4 constant transactionsSelector = 0x642f2eaf; - bytes4 constant transactionsSelectorGenerator = bytes4(keccak256('transactions(bytes32)')); - - // transferOwnership - bytes4 constant transferOwnershipSelector = 0xf2fde38b; - bytes4 constant transferOwnershipSelectorGenerator = bytes4(keccak256('transferOwnership(address)')); -} \ No newline at end of file -- cgit From e2510ed28f97feb33404ec0cb773214236620343 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Wed, 19 Dec 2018 10:44:32 +0000 Subject: Add temporary console.log to test failing on CI --- packages/order-watcher/test/order_watcher_web_socket_server_test.ts | 1 + 1 file changed, 1 insertion(+) (limited to 'packages') diff --git a/packages/order-watcher/test/order_watcher_web_socket_server_test.ts b/packages/order-watcher/test/order_watcher_web_socket_server_test.ts index fd388e907..fa64ac305 100644 --- a/packages/order-watcher/test/order_watcher_web_socket_server_test.ts +++ b/packages/order-watcher/test/order_watcher_web_socket_server_test.ts @@ -278,6 +278,7 @@ describe('OrderWatcherWebSocketServer', async () => { for (const client of [wsClient, wsClientTwo]) { const updateMsg = await _onMessageAsync(client); const updateData = JSON.parse(updateMsg.data); + console.log('-------------------------- UPDATE_DATA: ', updateData); const orderState = updateData.result as OrderStateValid; expect(orderState.isValid).to.be.true(); expect(orderState.orderRelevantState.makerFeeProxyAllowance).to.be.eq('0'); -- cgit From 84c8b83694cc16ed42cb01315803f124c582aab7 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Wed, 19 Dec 2018 12:18:53 +0000 Subject: Fix WS tests to remove race-condition and be more specific about the message expected --- .../test/order_watcher_web_socket_server_test.ts | 57 +++++++++++++--------- 1 file changed, 34 insertions(+), 23 deletions(-) (limited to 'packages') diff --git a/packages/order-watcher/test/order_watcher_web_socket_server_test.ts b/packages/order-watcher/test/order_watcher_web_socket_server_test.ts index fa64ac305..8070860e7 100644 --- a/packages/order-watcher/test/order_watcher_web_socket_server_test.ts +++ b/packages/order-watcher/test/order_watcher_web_socket_server_test.ts @@ -26,7 +26,7 @@ interface WsMessage { data: string; } -describe('OrderWatcherWebSocketServer', async () => { +describe.only('OrderWatcherWebSocketServer', async () => { let contractWrappers: ContractWrappers; let wsServer: OrderWatcherWebSocketServer; let wsClient: WebSocket.w3cwebsocket; @@ -49,9 +49,14 @@ describe('OrderWatcherWebSocketServer', async () => { // HACK: createFillableSignedOrderAsync is Promise-based, which forces us // to use Promises instead of the done() callbacks for tests. // onmessage callback must thus be wrapped as a Promise. - const _onMessageAsync = async (client: WebSocket.w3cwebsocket) => + const _getOnMessagePromise = async (client: WebSocket.w3cwebsocket, method: string | null) => new Promise(resolve => { - client.onmessage = (msg: WsMessage) => resolve(msg); + client.onmessage = (msg: WsMessage) => { + const data = JSON.parse(msg.data); + if (data.method === method) { + resolve(msg); + } + }; }); before(async () => { @@ -106,20 +111,20 @@ describe('OrderWatcherWebSocketServer', async () => { isVerbose: true, }; wsServer = new OrderWatcherWebSocketServer(provider, networkId, contractAddresses, orderWatcherConfig); - wsServer.start(); }); after(async () => { await blockchainLifecycle.revertAsync(); - wsServer.stop(); }); beforeEach(async () => { + wsServer.start(); await blockchainLifecycle.startAsync(); wsClient = new WebSocket.w3cwebsocket('ws://127.0.0.1:8080/'); logUtils.log(`${new Date()} [Client] Connected.`); }); afterEach(async () => { - await blockchainLifecycle.revertAsync(); wsClient.close(); + await blockchainLifecycle.revertAsync(); + wsServer.stop(); logUtils.log(`${new Date()} [Client] Closed.`); }); @@ -147,7 +152,7 @@ describe('OrderWatcherWebSocketServer', async () => { method: 'BAD_METHOD', }; wsClient.onopen = () => wsClient.send(JSON.stringify(invalidMethodPayload)); - const errorMsg = await _onMessageAsync(wsClient); + const errorMsg = await _getOnMessagePromise(wsClient, null); const errorData = JSON.parse(errorMsg.data); // tslint:disable-next-line:no-unused-expression expect(errorData.id).to.be.null; @@ -163,7 +168,7 @@ describe('OrderWatcherWebSocketServer', async () => { method: 'GET_STATS', }; wsClient.onopen = () => wsClient.send(JSON.stringify(noJsonRpcPayload)); - const errorMsg = await _onMessageAsync(wsClient); + const errorMsg = await _getOnMessagePromise(wsClient, null); const errorData = JSON.parse(errorMsg.data); // tslint:disable-next-line:no-unused-expression expect(errorData.method).to.be.null; @@ -179,7 +184,7 @@ describe('OrderWatcherWebSocketServer', async () => { orderHash: '0x7337e2f2a9aa2ed6afe26edc2df7ad79c3ffa9cf9b81a964f707ea63f5272355', }; wsClient.onopen = () => wsClient.send(JSON.stringify(noSignedOrderAddOrderPayload)); - const errorMsg = await _onMessageAsync(wsClient); + const errorMsg = await _getOnMessagePromise(wsClient, null); const errorData = JSON.parse(errorMsg.data); // tslint:disable-next-line:no-unused-expression expect(errorData.id).to.be.null; @@ -199,7 +204,7 @@ describe('OrderWatcherWebSocketServer', async () => { }, }; wsClient.onopen = () => wsClient.send(JSON.stringify(invalidAddOrderPayload)); - const errorMsg = await _onMessageAsync(wsClient); + const errorMsg = await _getOnMessagePromise(wsClient, null); const errorData = JSON.parse(errorMsg.data); // tslint:disable-next-line:no-unused-expression expect(errorData.id).to.be.null; @@ -210,15 +215,16 @@ describe('OrderWatcherWebSocketServer', async () => { it('executes addOrder and removeOrder requests correctly', async () => { wsClient.onopen = () => wsClient.send(JSON.stringify(addOrderPayload)); - const addOrderMsg = await _onMessageAsync(wsClient); + const addOrderMsg = await _getOnMessagePromise(wsClient, OrderWatcherMethod.AddOrder); const addOrderData = JSON.parse(addOrderMsg.data); expect(addOrderData.method).to.be.eq('ADD_ORDER'); expect((wsServer as any)._orderWatcher._orderByOrderHash).to.deep.include({ [orderHash]: signedOrder, }); + const clientOnMessagePromise = _getOnMessagePromise(wsClient, OrderWatcherMethod.RemoveOrder); wsClient.send(JSON.stringify(removeOrderPayload)); - const removeOrderMsg = await _onMessageAsync(wsClient); + const removeOrderMsg = await clientOnMessagePromise; const removeOrderData = JSON.parse(removeOrderMsg.data); expect(removeOrderData.method).to.be.eq('REMOVE_ORDER'); expect((wsServer as any)._orderWatcher._orderByOrderHash).to.not.deep.include({ @@ -229,13 +235,13 @@ describe('OrderWatcherWebSocketServer', async () => { it('broadcasts orderStateInvalid message when makerAddress allowance set to 0 for watched order', async () => { // Add the regular order wsClient.onopen = () => wsClient.send(JSON.stringify(addOrderPayload)); - await _onMessageAsync(wsClient); + const clientOnMessagePromise = _getOnMessagePromise(wsClient, OrderWatcherMethod.Update); // Set the allowance to 0 await contractWrappers.erc20Token.setProxyAllowanceAsync(makerTokenAddress, makerAddress, new BigNumber(0)); // Ensure that orderStateInvalid message is received. - const orderWatcherUpdateMsg = await _onMessageAsync(wsClient); + const orderWatcherUpdateMsg = await clientOnMessagePromise; const orderWatcherUpdateData = JSON.parse(orderWatcherUpdateMsg.data); expect(orderWatcherUpdateData.method).to.be.eq('UPDATE'); const invalidOrderState = orderWatcherUpdateData.result as OrderStateInvalid; @@ -269,20 +275,25 @@ describe('OrderWatcherWebSocketServer', async () => { wsClientTwo = new WebSocket.w3cwebsocket('ws://127.0.0.1:8080/'); logUtils.log(`${new Date()} [Client] Connected.`); wsClientTwo.onopen = () => wsClientTwo.send(JSON.stringify(nonZeroMakerFeeOrderPayload)); - await _onMessageAsync(wsClientTwo); + + const clientOneOnMessagePromise = _getOnMessagePromise(wsClient, OrderWatcherMethod.Update); + const clientTwoOnMessagePromise = _getOnMessagePromise(wsClientTwo, OrderWatcherMethod.Update); // Change the allowance await contractWrappers.erc20Token.setProxyAllowanceAsync(zrxTokenAddress, makerAddress, new BigNumber(0)); // Check that both clients receive the emitted event - for (const client of [wsClient, wsClientTwo]) { - const updateMsg = await _onMessageAsync(client); - const updateData = JSON.parse(updateMsg.data); - console.log('-------------------------- UPDATE_DATA: ', updateData); - const orderState = updateData.result as OrderStateValid; - expect(orderState.isValid).to.be.true(); - expect(orderState.orderRelevantState.makerFeeProxyAllowance).to.be.eq('0'); - } + let updateMsg = await clientOneOnMessagePromise; + let updateData = JSON.parse(updateMsg.data); + let orderState = updateData.result as OrderStateValid; + expect(orderState.isValid).to.be.true(); + expect(orderState.orderRelevantState.makerFeeProxyAllowance).to.be.eq('0'); + + updateMsg = await clientTwoOnMessagePromise; + updateData = JSON.parse(updateMsg.data); + orderState = updateData.result as OrderStateValid; + expect(orderState.isValid).to.be.true(); + expect(orderState.orderRelevantState.makerFeeProxyAllowance).to.be.eq('0'); wsClientTwo.close(); logUtils.log(`${new Date()} [Client] Closed.`); -- cgit From 90ee70db23251bfc68c7d4235be1bf1c4c6e6a92 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Wed, 19 Dec 2018 12:49:51 +0000 Subject: Move onMessageAsync outside of tests and add comments --- .../test/order_watcher_web_socket_server_test.ts | 53 ++++++++++++---------- 1 file changed, 30 insertions(+), 23 deletions(-) (limited to 'packages') diff --git a/packages/order-watcher/test/order_watcher_web_socket_server_test.ts b/packages/order-watcher/test/order_watcher_web_socket_server_test.ts index 8070860e7..578e0de61 100644 --- a/packages/order-watcher/test/order_watcher_web_socket_server_test.ts +++ b/packages/order-watcher/test/order_watcher_web_socket_server_test.ts @@ -46,18 +46,6 @@ describe.only('OrderWatcherWebSocketServer', async () => { let removeOrderPayload: RemoveOrderRequest; const decimals = constants.ZRX_DECIMALS; const fillableAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(5), decimals); - // HACK: createFillableSignedOrderAsync is Promise-based, which forces us - // to use Promises instead of the done() callbacks for tests. - // onmessage callback must thus be wrapped as a Promise. - const _getOnMessagePromise = async (client: WebSocket.w3cwebsocket, method: string | null) => - new Promise(resolve => { - client.onmessage = (msg: WsMessage) => { - const data = JSON.parse(msg.data); - if (data.method === method) { - resolve(msg); - } - }; - }); before(async () => { // Set up constants @@ -152,7 +140,7 @@ describe.only('OrderWatcherWebSocketServer', async () => { method: 'BAD_METHOD', }; wsClient.onopen = () => wsClient.send(JSON.stringify(invalidMethodPayload)); - const errorMsg = await _getOnMessagePromise(wsClient, null); + const errorMsg = await onMessageAsync(wsClient, null); const errorData = JSON.parse(errorMsg.data); // tslint:disable-next-line:no-unused-expression expect(errorData.id).to.be.null; @@ -168,7 +156,7 @@ describe.only('OrderWatcherWebSocketServer', async () => { method: 'GET_STATS', }; wsClient.onopen = () => wsClient.send(JSON.stringify(noJsonRpcPayload)); - const errorMsg = await _getOnMessagePromise(wsClient, null); + const errorMsg = await onMessageAsync(wsClient, null); const errorData = JSON.parse(errorMsg.data); // tslint:disable-next-line:no-unused-expression expect(errorData.method).to.be.null; @@ -184,7 +172,7 @@ describe.only('OrderWatcherWebSocketServer', async () => { orderHash: '0x7337e2f2a9aa2ed6afe26edc2df7ad79c3ffa9cf9b81a964f707ea63f5272355', }; wsClient.onopen = () => wsClient.send(JSON.stringify(noSignedOrderAddOrderPayload)); - const errorMsg = await _getOnMessagePromise(wsClient, null); + const errorMsg = await onMessageAsync(wsClient, null); const errorData = JSON.parse(errorMsg.data); // tslint:disable-next-line:no-unused-expression expect(errorData.id).to.be.null; @@ -204,7 +192,7 @@ describe.only('OrderWatcherWebSocketServer', async () => { }, }; wsClient.onopen = () => wsClient.send(JSON.stringify(invalidAddOrderPayload)); - const errorMsg = await _getOnMessagePromise(wsClient, null); + const errorMsg = await onMessageAsync(wsClient, null); const errorData = JSON.parse(errorMsg.data); // tslint:disable-next-line:no-unused-expression expect(errorData.id).to.be.null; @@ -215,14 +203,14 @@ describe.only('OrderWatcherWebSocketServer', async () => { it('executes addOrder and removeOrder requests correctly', async () => { wsClient.onopen = () => wsClient.send(JSON.stringify(addOrderPayload)); - const addOrderMsg = await _getOnMessagePromise(wsClient, OrderWatcherMethod.AddOrder); + const addOrderMsg = await onMessageAsync(wsClient, OrderWatcherMethod.AddOrder); const addOrderData = JSON.parse(addOrderMsg.data); expect(addOrderData.method).to.be.eq('ADD_ORDER'); expect((wsServer as any)._orderWatcher._orderByOrderHash).to.deep.include({ [orderHash]: signedOrder, }); - const clientOnMessagePromise = _getOnMessagePromise(wsClient, OrderWatcherMethod.RemoveOrder); + const clientOnMessagePromise = onMessageAsync(wsClient, OrderWatcherMethod.RemoveOrder); wsClient.send(JSON.stringify(removeOrderPayload)); const removeOrderMsg = await clientOnMessagePromise; const removeOrderData = JSON.parse(removeOrderMsg.data); @@ -235,12 +223,16 @@ describe.only('OrderWatcherWebSocketServer', async () => { it('broadcasts orderStateInvalid message when makerAddress allowance set to 0 for watched order', async () => { // Add the regular order wsClient.onopen = () => wsClient.send(JSON.stringify(addOrderPayload)); - const clientOnMessagePromise = _getOnMessagePromise(wsClient, OrderWatcherMethod.Update); + + // We register the onMessage callback before calling `setProxyAllowanceAsync` which we + // expect will cause a message to be emitted. We do now "await" here, since we want to + // check for messages _after_ calling `setProxyAllowanceAsync` + const clientOnMessagePromise = onMessageAsync(wsClient, OrderWatcherMethod.Update); // Set the allowance to 0 await contractWrappers.erc20Token.setProxyAllowanceAsync(makerTokenAddress, makerAddress, new BigNumber(0)); - // Ensure that orderStateInvalid message is received. + // We now await the `onMessage` promise to check for the message const orderWatcherUpdateMsg = await clientOnMessagePromise; const orderWatcherUpdateData = JSON.parse(orderWatcherUpdateMsg.data); expect(orderWatcherUpdateData.method).to.be.eq('UPDATE'); @@ -276,13 +268,14 @@ describe.only('OrderWatcherWebSocketServer', async () => { logUtils.log(`${new Date()} [Client] Connected.`); wsClientTwo.onopen = () => wsClientTwo.send(JSON.stringify(nonZeroMakerFeeOrderPayload)); - const clientOneOnMessagePromise = _getOnMessagePromise(wsClient, OrderWatcherMethod.Update); - const clientTwoOnMessagePromise = _getOnMessagePromise(wsClientTwo, OrderWatcherMethod.Update); + // Setup the onMessage callbacks, but don't await them yet + const clientOneOnMessagePromise = onMessageAsync(wsClient, OrderWatcherMethod.Update); + const clientTwoOnMessagePromise = onMessageAsync(wsClientTwo, OrderWatcherMethod.Update); // Change the allowance await contractWrappers.erc20Token.setProxyAllowanceAsync(zrxTokenAddress, makerAddress, new BigNumber(0)); - // Check that both clients receive the emitted event + // Check that both clients receive the emitted event by awaiting the onMessageAsync promises let updateMsg = await clientOneOnMessagePromise; let updateData = JSON.parse(updateMsg.data); let orderState = updateData.result as OrderStateValid; @@ -299,3 +292,17 @@ describe.only('OrderWatcherWebSocketServer', async () => { logUtils.log(`${new Date()} [Client] Closed.`); }); }); + +// HACK: createFillableSignedOrderAsync is Promise-based, which forces us +// to use Promises instead of the done() callbacks for tests. +// onmessage callback must thus be wrapped as a Promise. +async function onMessageAsync(client: WebSocket.w3cwebsocket, method: string | null): Promise { + return new Promise(resolve => { + client.onmessage = (msg: WsMessage) => { + const data = JSON.parse(msg.data); + if (data.method === method) { + resolve(msg); + } + }; + }); +} -- cgit From 5c24596d812a80011f9e92a13bf91923fbcc2a64 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Wed, 19 Dec 2018 13:21:06 +0000 Subject: Add missing CHANGELOG entry for OrderWatcher WS interface --- packages/order-watcher/CHANGELOG.json | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'packages') diff --git a/packages/order-watcher/CHANGELOG.json b/packages/order-watcher/CHANGELOG.json index c1fd8d4a9..304dc45fd 100644 --- a/packages/order-watcher/CHANGELOG.json +++ b/packages/order-watcher/CHANGELOG.json @@ -1,4 +1,14 @@ [ + { + "version": "2.3.0", + "changes": [ + { + "note": + "Added a WebSocket interface to OrderWatcher so that it can be used by a client written in any language", + "pr": 1427 + } + ] + }, { "version": "2.2.8", "changes": [ -- cgit From b3bcd726b86331d61919d7dbb4e86b9c3ef296b7 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Wed, 19 Dec 2018 14:19:37 +0000 Subject: fix prettier --- packages/website/ts/pages/documentation/docs_home.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'packages') diff --git a/packages/website/ts/pages/documentation/docs_home.tsx b/packages/website/ts/pages/documentation/docs_home.tsx index 169b2976b..1768f7b10 100644 --- a/packages/website/ts/pages/documentation/docs_home.tsx +++ b/packages/website/ts/pages/documentation/docs_home.tsx @@ -100,8 +100,7 @@ const CATEGORY_TO_PACKAGES: ObjectMap = { }, }, { - description: - 'A Python Standard Relayer API client', + description: 'A Python Standard Relayer API client', link: { title: '0x-sra-client.py', to: 'https://pypi.org/project/0x-sra-client/', -- cgit From c5632490f25efd409472ca4727ee22b5bf0c763a Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Fri, 14 Dec 2018 12:55:50 -0800 Subject: Refactor most of the sol-compiler methods into helper functions in utils and make resolver pluggable into them --- packages/sol-compiler/src/compiler.ts | 205 +++---------------------- packages/sol-compiler/src/utils/compiler.ts | 216 ++++++++++++++++++++++++++- packages/sol-compiler/src/utils/constants.ts | 3 + 3 files changed, 237 insertions(+), 187 deletions(-) (limited to 'packages') diff --git a/packages/sol-compiler/src/compiler.ts b/packages/sol-compiler/src/compiler.ts index 85df8209e..45cbf527b 100644 --- a/packages/sol-compiler/src/compiler.ts +++ b/packages/sol-compiler/src/compiler.ts @@ -8,24 +8,24 @@ import { Resolver, URLResolver, } from '@0x/sol-resolver'; -import { fetchAsync, logUtils } from '@0x/utils'; -import chalk from 'chalk'; +import { logUtils } from '@0x/utils'; import { CompilerOptions, ContractArtifact, ContractVersionData, StandardOutput } from 'ethereum-types'; -import * as ethUtil from 'ethereumjs-util'; import * as fs from 'fs'; import * as _ from 'lodash'; import * as path from 'path'; -import * as requireFromString from 'require-from-string'; import * as semver from 'semver'; import solc = require('solc'); import { compilerOptionsSchema } from './schemas/compiler_options_schema'; import { binPaths } from './solc/bin_paths'; import { + addHexPrefixToContractBytecode, + compile, createDirIfDoesNotExistAsync, getContractArtifactIfExistsAsync, - getNormalizedErrMsg, - parseDependencies, + getSolcAsync, + getSourcesWithDependencies, + getSourceTreeHash, parseSolidityVersionRange, } from './utils/compiler'; import { constants } from './utils/constants'; @@ -35,7 +35,6 @@ import { utils } from './utils/utils'; type TYPE_ALL_FILES_IDENTIFIER = '*'; const ALL_CONTRACTS_IDENTIFIER = '*'; const ALL_FILES_IDENTIFIER = '*'; -const SOLC_BIN_DIR = path.join(__dirname, '..', '..', 'solc_bin'); const DEFAULT_CONTRACTS_DIR = path.resolve('contracts'); const DEFAULT_ARTIFACTS_DIR = path.resolve('artifacts'); // Solc compiler settings cannot be configured from the commandline. @@ -82,49 +81,6 @@ export class Compiler { private readonly _artifactsDir: string; private readonly _solcVersionIfExists: string | undefined; private readonly _specifiedContracts: string[] | TYPE_ALL_FILES_IDENTIFIER; - private static async _getSolcAsync( - solcVersion: string, - ): Promise<{ solcInstance: solc.SolcInstance; fullSolcVersion: string }> { - const fullSolcVersion = binPaths[solcVersion]; - if (_.isUndefined(fullSolcVersion)) { - throw new Error(`${solcVersion} is not a known compiler version`); - } - const compilerBinFilename = path.join(SOLC_BIN_DIR, fullSolcVersion); - let solcjs: string; - if (await fsWrapper.doesFileExistAsync(compilerBinFilename)) { - solcjs = (await fsWrapper.readFileAsync(compilerBinFilename)).toString(); - } else { - logUtils.warn(`Downloading ${fullSolcVersion}...`); - const url = `${constants.BASE_COMPILER_URL}${fullSolcVersion}`; - const response = await fetchAsync(url); - const SUCCESS_STATUS = 200; - if (response.status !== SUCCESS_STATUS) { - throw new Error(`Failed to load ${fullSolcVersion}`); - } - solcjs = await response.text(); - await fsWrapper.writeFileAsync(compilerBinFilename, solcjs); - } - if (solcjs.length === 0) { - throw new Error('No compiler available'); - } - const solcInstance = solc.setupMethods(requireFromString(solcjs, compilerBinFilename)); - return { solcInstance, fullSolcVersion }; - } - private static _addHexPrefixToContractBytecode(compiledContract: solc.StandardContractOutput): void { - if (!_.isUndefined(compiledContract.evm)) { - if (!_.isUndefined(compiledContract.evm.bytecode) && !_.isUndefined(compiledContract.evm.bytecode.object)) { - compiledContract.evm.bytecode.object = ethUtil.addHexPrefix(compiledContract.evm.bytecode.object); - } - if ( - !_.isUndefined(compiledContract.evm.deployedBytecode) && - !_.isUndefined(compiledContract.evm.deployedBytecode.object) - ) { - compiledContract.evm.deployedBytecode.object = ethUtil.addHexPrefix( - compiledContract.evm.deployedBytecode.object, - ); - } - } - } /** * Instantiates a new instance of the Compiler class. * @param opts Optional compiler options @@ -158,7 +114,7 @@ export class Compiler { */ public async compileAsync(): Promise { await createDirIfDoesNotExistAsync(this._artifactsDir); - await createDirIfDoesNotExistAsync(SOLC_BIN_DIR); + await createDirIfDoesNotExistAsync(constants.SOLC_BIN_DIR); await this._compileContractsAsync(this._getContractNamesToCompile(), true); } /** @@ -201,12 +157,14 @@ export class Compiler { for (const contractName of contractNames) { const contractSource = this._resolver.resolve(contractName); + const sourceTreeHashHex = getSourceTreeHash( + this._resolver, + path.join(this._contractsDir, contractSource.path), + ).toString('hex'); const contractData = { contractName, currentArtifactIfExists: await getContractArtifactIfExistsAsync(this._artifactsDir, contractName), - sourceTreeHashHex: `0x${this._getSourceTreeHash( - path.join(this._contractsDir, contractSource.path), - ).toString('hex')}`, + sourceTreeHashHex: `0x${sourceTreeHashHex}`, }; if (!this._shouldCompile(contractData)) { continue; @@ -244,9 +202,8 @@ export class Compiler { }) with Solidity v${solcVersion}...`, ); - const { solcInstance, fullSolcVersion } = await Compiler._getSolcAsync(solcVersion); - - const compilerOutput = this._compile(solcInstance, input.standardInput); + const { solcInstance, fullSolcVersion } = await getSolcAsync(solcVersion); + const compilerOutput = compile(this._resolver, solcInstance, input.standardInput); compilerOutputs.push(compilerOutput); for (const contractPath of input.contractsToCompile) { @@ -259,7 +216,7 @@ export class Compiler { ); } - Compiler._addHexPrefixToContractBytecode(compiledContract); + addHexPrefixToContractBytecode(compiledContract); if (shouldPersist) { await this._persistCompiledContractAsync( @@ -301,7 +258,11 @@ export class Compiler { // contains listings for for every contract compiled during the compiler invocation that compiled the contract // to be persisted, which could include many that are irrelevant to the contract at hand. So, gather up only // the relevant sources: - const { sourceCodes, sources } = this._getSourcesWithDependencies(contractPath, compilerOutput.sources); + const { sourceCodes, sources } = getSourcesWithDependencies( + this._resolver, + contractPath, + compilerOutput.sources, + ); const contractVersion: ContractVersionData = { compilerOutput: compiledContract, @@ -336,130 +297,4 @@ export class Compiler { await fsWrapper.writeFileAsync(currentArtifactPath, artifactString); logUtils.warn(`${contractName} artifact saved!`); } - /** - * For the given @param contractPath, populates JSON objects to be used in the ContractVersionData interface's - * properties `sources` (source code file names mapped to ID numbers) and `sourceCodes` (source code content of - * contracts) for that contract. The source code pointed to by contractPath is read and parsed directly (via - * `this._resolver.resolve().source`), as are its imports, recursively. The ID numbers for @return `sources` are - * taken from the corresponding ID's in @param fullSources, and the content for @return sourceCodes is read from - * disk (via the aforementioned `resolver.source`). - */ - private _getSourcesWithDependencies( - contractPath: string, - fullSources: { [sourceName: string]: { id: number } }, - ): { sourceCodes: { [sourceName: string]: string }; sources: { [sourceName: string]: { id: number } } } { - const sources = { [contractPath]: { id: fullSources[contractPath].id } }; - const sourceCodes = { [contractPath]: this._resolver.resolve(contractPath).source }; - this._recursivelyGatherDependencySources( - contractPath, - sourceCodes[contractPath], - fullSources, - sources, - sourceCodes, - ); - return { sourceCodes, sources }; - } - private _recursivelyGatherDependencySources( - contractPath: string, - contractSource: string, - fullSources: { [sourceName: string]: { id: number } }, - sourcesToAppendTo: { [sourceName: string]: { id: number } }, - sourceCodesToAppendTo: { [sourceName: string]: string }, - ): void { - const importStatementMatches = contractSource.match(/\nimport[^;]*;/g); - if (importStatementMatches === null) { - return; - } - for (const importStatementMatch of importStatementMatches) { - const importPathMatches = importStatementMatch.match(/\"([^\"]*)\"/); - if (importPathMatches === null || importPathMatches.length === 0) { - continue; - } - - let importPath = importPathMatches[1]; - // HACK(ablrow): We have, e.g.: - // - // importPath = "../../utils/LibBytes/LibBytes.sol" - // contractPath = "2.0.0/protocol/AssetProxyOwner/AssetProxyOwner.sol" - // - // Resolver doesn't understand "../" so we want to pass - // "2.0.0/utils/LibBytes/LibBytes.sol" to resolver. - // - // This hack involves using path.resolve. But path.resolve returns - // absolute directories by default. We trick it into thinking that - // contractPath is a root directory by prepending a '/' and then - // removing the '/' the end. - // - // path.resolve("/a/b/c", ""../../d/e") === "/a/d/e" - // - const lastPathSeparatorPos = contractPath.lastIndexOf('/'); - const contractFolder = lastPathSeparatorPos === -1 ? '' : contractPath.slice(0, lastPathSeparatorPos + 1); - if (importPath.startsWith('.')) { - /** - * Some imports path are relative ("../Token.sol", "./Wallet.sol") - * while others are absolute ("Token.sol", "@0x/contracts/Wallet.sol") - * And we need to append the base path for relative imports. - */ - importPath = path.resolve(`/${contractFolder}`, importPath).replace('/', ''); - } - - if (_.isUndefined(sourcesToAppendTo[importPath])) { - sourcesToAppendTo[importPath] = { id: fullSources[importPath].id }; - sourceCodesToAppendTo[importPath] = this._resolver.resolve(importPath).source; - - this._recursivelyGatherDependencySources( - importPath, - this._resolver.resolve(importPath).source, - fullSources, - sourcesToAppendTo, - sourceCodesToAppendTo, - ); - } - } - } - private _compile(solcInstance: solc.SolcInstance, standardInput: solc.StandardInput): solc.StandardOutput { - const compiled: solc.StandardOutput = JSON.parse( - solcInstance.compileStandardWrapper(JSON.stringify(standardInput), importPath => { - const sourceCodeIfExists = this._resolver.resolve(importPath); - return { contents: sourceCodeIfExists.source }; - }), - ); - if (!_.isUndefined(compiled.errors)) { - const SOLIDITY_WARNING = 'warning'; - const errors = _.filter(compiled.errors, entry => entry.severity !== SOLIDITY_WARNING); - const warnings = _.filter(compiled.errors, entry => entry.severity === SOLIDITY_WARNING); - if (!_.isEmpty(errors)) { - errors.forEach(error => { - const normalizedErrMsg = getNormalizedErrMsg(error.formattedMessage || error.message); - logUtils.warn(chalk.red(normalizedErrMsg)); - }); - throw new Error('Compilation errors encountered'); - } else { - warnings.forEach(warning => { - const normalizedWarningMsg = getNormalizedErrMsg(warning.formattedMessage || warning.message); - logUtils.warn(chalk.yellow(normalizedWarningMsg)); - }); - } - } - return compiled; - } - /** - * Gets the source tree hash for a file and its dependencies. - * @param fileName Name of contract file. - */ - private _getSourceTreeHash(importPath: string): Buffer { - const contractSource = this._resolver.resolve(importPath); - const dependencies = parseDependencies(contractSource); - const sourceHash = ethUtil.sha3(contractSource.source); - if (dependencies.length === 0) { - return sourceHash; - } else { - const dependencySourceTreeHashes = _.map(dependencies, (dependency: string) => - this._getSourceTreeHash(dependency), - ); - const sourceTreeHashesBuffer = Buffer.concat([sourceHash, ...dependencySourceTreeHashes]); - const sourceTreeHash = ethUtil.sha3(sourceTreeHashesBuffer); - return sourceTreeHash; - } - } } diff --git a/packages/sol-compiler/src/utils/compiler.ts b/packages/sol-compiler/src/utils/compiler.ts index cda67a414..034f72f8d 100644 --- a/packages/sol-compiler/src/utils/compiler.ts +++ b/packages/sol-compiler/src/utils/compiler.ts @@ -1,9 +1,16 @@ -import { ContractSource } from '@0x/sol-resolver'; -import { logUtils } from '@0x/utils'; +import { ContractSource, Resolver } from '@0x/sol-resolver'; +import { fetchAsync, logUtils } from '@0x/utils'; +import chalk from 'chalk'; import { ContractArtifact } from 'ethereum-types'; +import * as ethUtil from 'ethereumjs-util'; import * as _ from 'lodash'; import * as path from 'path'; +import * as requireFromString from 'require-from-string'; +import * as solc from 'solc'; +import { binPaths } from '../solc/bin_paths'; + +import { constants } from './constants'; import { fsWrapper } from './fs_wrapper'; /** @@ -106,3 +113,208 @@ export function parseDependencies(contractSource: ContractSource): string[] { }); return dependencies; } + +/** + * Compiles the contracts and prints errors/warnings + * @param resolver Resolver + * @param solcInstance Instance of a solc compiler + * @param standardInput Solidity standard JSON input + */ +export function compile( + resolver: Resolver, + solcInstance: solc.SolcInstance, + standardInput: solc.StandardInput, +): solc.StandardOutput { + const standardInputStr = JSON.stringify(standardInput); + const standardOutputStr = solcInstance.compileStandardWrapper(standardInputStr, importPath => { + const sourceCodeIfExists = resolver.resolve(importPath); + return { contents: sourceCodeIfExists.source }; + }); + const compiled: solc.StandardOutput = JSON.parse(standardOutputStr); + if (!_.isUndefined(compiled.errors)) { + printCompilationErrorsAndWarnings(compiled.errors); + } + return compiled; +} +/** + * Separates errors from warnings, formats the messages and prints them. Throws if there is any compilation error (not warning). + * @param solcErrors The errors field of standard JSON output that contains errors and warnings. + */ +function printCompilationErrorsAndWarnings(solcErrors: solc.SolcError[]): void { + const SOLIDITY_WARNING = 'warning'; + const errors = _.filter(solcErrors, entry => entry.severity !== SOLIDITY_WARNING); + const warnings = _.filter(solcErrors, entry => entry.severity === SOLIDITY_WARNING); + if (!_.isEmpty(errors)) { + errors.forEach(error => { + const normalizedErrMsg = getNormalizedErrMsg(error.formattedMessage || error.message); + logUtils.warn(chalk.red(normalizedErrMsg)); + }); + throw new Error('Compilation errors encountered'); + } else { + warnings.forEach(warning => { + const normalizedWarningMsg = getNormalizedErrMsg(warning.formattedMessage || warning.message); + logUtils.warn(chalk.yellow(normalizedWarningMsg)); + }); + } +} + +/** + * Gets the source tree hash for a file and its dependencies. + * @param fileName Name of contract file. + */ +export function getSourceTreeHash(resolver: Resolver, importPath: string): Buffer { + const contractSource = resolver.resolve(importPath); + const dependencies = parseDependencies(contractSource); + const sourceHash = ethUtil.sha3(contractSource.source); + if (dependencies.length === 0) { + return sourceHash; + } else { + const dependencySourceTreeHashes = _.map(dependencies, (dependency: string) => + getSourceTreeHash(resolver, dependency), + ); + const sourceTreeHashesBuffer = Buffer.concat([sourceHash, ...dependencySourceTreeHashes]); + const sourceTreeHash = ethUtil.sha3(sourceTreeHashesBuffer); + return sourceTreeHash; + } +} + +/** + * For the given @param contractPath, populates JSON objects to be used in the ContractVersionData interface's + * properties `sources` (source code file names mapped to ID numbers) and `sourceCodes` (source code content of + * contracts) for that contract. The source code pointed to by contractPath is read and parsed directly (via + * `resolver.resolve().source`), as are its imports, recursively. The ID numbers for @return `sources` are + * taken from the corresponding ID's in @param fullSources, and the content for @return sourceCodes is read from + * disk (via the aforementioned `resolver.source`). + */ +export function getSourcesWithDependencies( + resolver: Resolver, + contractPath: string, + fullSources: { [sourceName: string]: { id: number } }, +): { sourceCodes: { [sourceName: string]: string }; sources: { [sourceName: string]: { id: number } } } { + const sources = { [contractPath]: { id: fullSources[contractPath].id } }; + const sourceCodes = { [contractPath]: resolver.resolve(contractPath).source }; + recursivelyGatherDependencySources( + resolver, + contractPath, + sourceCodes[contractPath], + fullSources, + sources, + sourceCodes, + ); + return { sourceCodes, sources }; +} + +function recursivelyGatherDependencySources( + resolver: Resolver, + contractPath: string, + contractSource: string, + fullSources: { [sourceName: string]: { id: number } }, + sourcesToAppendTo: { [sourceName: string]: { id: number } }, + sourceCodesToAppendTo: { [sourceName: string]: string }, +): void { + const importStatementMatches = contractSource.match(/\nimport[^;]*;/g); + if (importStatementMatches === null) { + return; + } + for (const importStatementMatch of importStatementMatches) { + const importPathMatches = importStatementMatch.match(/\"([^\"]*)\"/); + if (importPathMatches === null || importPathMatches.length === 0) { + continue; + } + + let importPath = importPathMatches[1]; + // HACK(ablrow): We have, e.g.: + // + // importPath = "../../utils/LibBytes/LibBytes.sol" + // contractPath = "2.0.0/protocol/AssetProxyOwner/AssetProxyOwner.sol" + // + // Resolver doesn't understand "../" so we want to pass + // "2.0.0/utils/LibBytes/LibBytes.sol" to resolver. + // + // This hack involves using path.resolve. But path.resolve returns + // absolute directories by default. We trick it into thinking that + // contractPath is a root directory by prepending a '/' and then + // removing the '/' the end. + // + // path.resolve("/a/b/c", ""../../d/e") === "/a/d/e" + // + const lastPathSeparatorPos = contractPath.lastIndexOf('/'); + const contractFolder = lastPathSeparatorPos === -1 ? '' : contractPath.slice(0, lastPathSeparatorPos + 1); + if (importPath.startsWith('.')) { + /** + * Some imports path are relative ("../Token.sol", "./Wallet.sol") + * while others are absolute ("Token.sol", "@0x/contracts/Wallet.sol") + * And we need to append the base path for relative imports. + */ + importPath = path.resolve(`/${contractFolder}`, importPath).replace('/', ''); + } + + if (_.isUndefined(sourcesToAppendTo[importPath])) { + sourcesToAppendTo[importPath] = { id: fullSources[importPath].id }; + sourceCodesToAppendTo[importPath] = resolver.resolve(importPath).source; + + recursivelyGatherDependencySources( + resolver, + importPath, + resolver.resolve(importPath).source, + fullSources, + sourcesToAppendTo, + sourceCodesToAppendTo, + ); + } + } +} + +/** + * Gets the solidity compiler instance and full version name. If the compiler is already cached - gets it from FS, + * otherwise - fetches it and caches it. + * @param solcVersion The compiler version. e.g. 0.5.0 + */ +export async function getSolcAsync( + solcVersion: string, +): Promise<{ solcInstance: solc.SolcInstance; fullSolcVersion: string }> { + const fullSolcVersion = binPaths[solcVersion]; + if (_.isUndefined(fullSolcVersion)) { + throw new Error(`${solcVersion} is not a known compiler version`); + } + const compilerBinFilename = path.join(constants.SOLC_BIN_DIR, fullSolcVersion); + let solcjs: string; + if (await fsWrapper.doesFileExistAsync(compilerBinFilename)) { + solcjs = (await fsWrapper.readFileAsync(compilerBinFilename)).toString(); + } else { + logUtils.warn(`Downloading ${fullSolcVersion}...`); + const url = `${constants.BASE_COMPILER_URL}${fullSolcVersion}`; + const response = await fetchAsync(url); + const SUCCESS_STATUS = 200; + if (response.status !== SUCCESS_STATUS) { + throw new Error(`Failed to load ${fullSolcVersion}`); + } + solcjs = await response.text(); + await fsWrapper.writeFileAsync(compilerBinFilename, solcjs); + } + if (solcjs.length === 0) { + throw new Error('No compiler available'); + } + const solcInstance = solc.setupMethods(requireFromString(solcjs, compilerBinFilename)); + return { solcInstance, fullSolcVersion }; +} + +/** + * Solidity compiler emits the bytecode without a 0x prefix for a hex. This function fixes it if bytecode is present. + * @param compiledContract The standard JSON output section for a contract. Geth modified in place. + */ +export function addHexPrefixToContractBytecode(compiledContract: solc.StandardContractOutput): void { + if (!_.isUndefined(compiledContract.evm)) { + if (!_.isUndefined(compiledContract.evm.bytecode) && !_.isUndefined(compiledContract.evm.bytecode.object)) { + compiledContract.evm.bytecode.object = ethUtil.addHexPrefix(compiledContract.evm.bytecode.object); + } + if ( + !_.isUndefined(compiledContract.evm.deployedBytecode) && + !_.isUndefined(compiledContract.evm.deployedBytecode.object) + ) { + compiledContract.evm.deployedBytecode.object = ethUtil.addHexPrefix( + compiledContract.evm.deployedBytecode.object, + ); + } + } +} diff --git a/packages/sol-compiler/src/utils/constants.ts b/packages/sol-compiler/src/utils/constants.ts index df2ddb3b2..433897f8a 100644 --- a/packages/sol-compiler/src/utils/constants.ts +++ b/packages/sol-compiler/src/utils/constants.ts @@ -1,5 +1,8 @@ +import * as path from 'path'; + export const constants = { SOLIDITY_FILE_EXTENSION: '.sol', BASE_COMPILER_URL: 'https://ethereum.github.io/solc-bin/bin/', LATEST_ARTIFACT_VERSION: '2.0.0', + SOLC_BIN_DIR: path.join(__dirname, '..', '..', 'solc_bin'), }; -- cgit From bc27ee0debe39b1095bfbdf685a22e634c214d2a Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Wed, 19 Dec 2018 13:28:43 +0100 Subject: Add includes section to typescript-typings package.json --- packages/typescript-typings/tsconfig.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/typescript-typings/tsconfig.json b/packages/typescript-typings/tsconfig.json index 7f0fe2f7a..8ea3bfb0c 100644 --- a/packages/typescript-typings/tsconfig.json +++ b/packages/typescript-typings/tsconfig.json @@ -3,5 +3,6 @@ "compilerOptions": { "outDir": "lib", "rootDir": "." - } + }, + "include": ["types"] } -- cgit From c8eaa63cce99e6666d6d863e8a47007e4cbcbbbb Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Wed, 19 Dec 2018 14:06:58 +0100 Subject: Add to type in sol-resolver --- packages/sol-resolver/CHANGELOG.json | 13 +++++++++++++ packages/sol-resolver/src/resolvers/fs_resolver.ts | 5 +---- packages/sol-resolver/src/resolvers/name_resolver.ts | 10 ++-------- packages/sol-resolver/src/resolvers/npm_resolver.ts | 5 +---- packages/sol-resolver/src/resolvers/relative_fs_resolver.ts | 7 ++----- packages/sol-resolver/src/resolvers/url_resolver.ts | 5 +---- packages/sol-resolver/src/types.ts | 1 + 7 files changed, 21 insertions(+), 25 deletions(-) (limited to 'packages') diff --git a/packages/sol-resolver/CHANGELOG.json b/packages/sol-resolver/CHANGELOG.json index 85398e624..b898a422f 100644 --- a/packages/sol-resolver/CHANGELOG.json +++ b/packages/sol-resolver/CHANGELOG.json @@ -1,4 +1,17 @@ [ + { + "version": "1.2.1", + "changes": [ + { + "note": "Add `absolutePath` to `ContractSource` type", + "pr": "TODO" + }, + { + "note": "Add `SpyResolver` that records all resolved contracts data", + "pr": "TODO" + } + ] + }, { "version": "1.1.1", "changes": [ diff --git a/packages/sol-resolver/src/resolvers/fs_resolver.ts b/packages/sol-resolver/src/resolvers/fs_resolver.ts index 63fc3448e..86128023d 100644 --- a/packages/sol-resolver/src/resolvers/fs_resolver.ts +++ b/packages/sol-resolver/src/resolvers/fs_resolver.ts @@ -9,10 +9,7 @@ export class FSResolver extends Resolver { public resolveIfExists(importPath: string): ContractSource | undefined { if (fs.existsSync(importPath) && fs.lstatSync(importPath).isFile()) { const fileContent = fs.readFileSync(importPath).toString(); - return { - source: fileContent, - path: importPath, - }; + return { source: fileContent, path: importPath, absolutePath: importPath }; } return undefined; } diff --git a/packages/sol-resolver/src/resolvers/name_resolver.ts b/packages/sol-resolver/src/resolvers/name_resolver.ts index d6ac6a499..aee326fb7 100644 --- a/packages/sol-resolver/src/resolvers/name_resolver.ts +++ b/packages/sol-resolver/src/resolvers/name_resolver.ts @@ -20,10 +20,7 @@ export class NameResolver extends EnumerableResolver { if (contractName === lookupContractName) { const absoluteContractPath = path.join(this._contractsDir, filePath); const source = fs.readFileSync(absoluteContractPath).toString(); - contractSource = { - source, - path: filePath, - }; + contractSource = { source, path: filePath, absolutePath: absoluteContractPath }; return true; } return undefined; @@ -36,10 +33,7 @@ export class NameResolver extends EnumerableResolver { const onFile = (filePath: string) => { const absoluteContractPath = path.join(this._contractsDir, filePath); const source = fs.readFileSync(absoluteContractPath).toString(); - const contractSource = { - source, - path: filePath, - }; + const contractSource = { source, path: filePath, absolutePath: absoluteContractPath }; contractSources.push(contractSource); }; this._traverseContractsDir(this._contractsDir, onFile); diff --git a/packages/sol-resolver/src/resolvers/npm_resolver.ts b/packages/sol-resolver/src/resolvers/npm_resolver.ts index eeb2b5493..3c1d09557 100644 --- a/packages/sol-resolver/src/resolvers/npm_resolver.ts +++ b/packages/sol-resolver/src/resolvers/npm_resolver.ts @@ -32,10 +32,7 @@ export class NPMResolver extends Resolver { const lookupPath = path.join(currentPath, 'node_modules', packagePath, pathWithinPackage); if (fs.existsSync(lookupPath) && fs.lstatSync(lookupPath).isFile()) { const fileContent = fs.readFileSync(lookupPath).toString(); - return { - source: fileContent, - path: lookupPath, - }; + return { source: fileContent, path: importPath, absolutePath: lookupPath }; } currentPath = path.dirname(currentPath); } diff --git a/packages/sol-resolver/src/resolvers/relative_fs_resolver.ts b/packages/sol-resolver/src/resolvers/relative_fs_resolver.ts index ed96040d3..cfff145f9 100644 --- a/packages/sol-resolver/src/resolvers/relative_fs_resolver.ts +++ b/packages/sol-resolver/src/resolvers/relative_fs_resolver.ts @@ -13,13 +13,10 @@ export class RelativeFSResolver extends Resolver { } // tslint:disable-next-line:prefer-function-over-method public resolveIfExists(importPath: string): ContractSource | undefined { - const filePath = path.join(this._contractsDir, importPath); + const filePath = path.resolve(path.join(this._contractsDir, importPath)); if (fs.existsSync(filePath) && !fs.lstatSync(filePath).isDirectory()) { const fileContent = fs.readFileSync(filePath).toString(); - return { - source: fileContent, - path: importPath, - }; + return { source: fileContent, path: importPath, absolutePath: filePath }; } return undefined; } diff --git a/packages/sol-resolver/src/resolvers/url_resolver.ts b/packages/sol-resolver/src/resolvers/url_resolver.ts index 180b0c9f6..ef300e6db 100644 --- a/packages/sol-resolver/src/resolvers/url_resolver.ts +++ b/packages/sol-resolver/src/resolvers/url_resolver.ts @@ -11,10 +11,7 @@ export class URLResolver extends Resolver { if (importPath.startsWith(FILE_URL_PREXIF)) { const filePath = importPath.substr(FILE_URL_PREXIF.length); const fileContent = fs.readFileSync(filePath).toString(); - return { - source: fileContent, - path: importPath, - }; + return { source: fileContent, path: importPath, absolutePath: filePath }; } return undefined; } diff --git a/packages/sol-resolver/src/types.ts b/packages/sol-resolver/src/types.ts index 41492622d..b4ba164c8 100644 --- a/packages/sol-resolver/src/types.ts +++ b/packages/sol-resolver/src/types.ts @@ -1,6 +1,7 @@ export interface ContractSource { source: string; path: string; + absolutePath: string; } export interface ContractSources { -- cgit From 8ddf925a8feb41e240871fe0d57e0f40b9bb4896 Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Wed, 19 Dec 2018 14:07:20 +0100 Subject: Add SpyResolver --- packages/sol-resolver/src/index.ts | 1 + .../sol-resolver/src/resolvers/spy_resolver.ts | 23 ++++++++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 packages/sol-resolver/src/resolvers/spy_resolver.ts (limited to 'packages') diff --git a/packages/sol-resolver/src/index.ts b/packages/sol-resolver/src/index.ts index a86053259..f55aca070 100644 --- a/packages/sol-resolver/src/index.ts +++ b/packages/sol-resolver/src/index.ts @@ -5,5 +5,6 @@ export { NPMResolver } from './resolvers/npm_resolver'; export { FSResolver } from './resolvers/fs_resolver'; export { RelativeFSResolver } from './resolvers/relative_fs_resolver'; export { NameResolver } from './resolvers/name_resolver'; +export { SpyResolver } from './resolvers/spy_resolver'; export { EnumerableResolver } from './resolvers/enumerable_resolver'; export { Resolver } from './resolvers/resolver'; diff --git a/packages/sol-resolver/src/resolvers/spy_resolver.ts b/packages/sol-resolver/src/resolvers/spy_resolver.ts new file mode 100644 index 000000000..df56c8f9e --- /dev/null +++ b/packages/sol-resolver/src/resolvers/spy_resolver.ts @@ -0,0 +1,23 @@ +import * as fs from 'fs'; +import * as _ from 'lodash'; +import * as path from 'path'; + +import { ContractSource } from '../types'; + +import { Resolver } from './resolver'; + +export class SpyResolver extends Resolver { + public resolvedContractSources: ContractSource[] = []; + private _resolver: Resolver; + constructor(resolver: Resolver) { + super(); + this._resolver = resolver; + } + public resolveIfExists(importPath: string): ContractSource | undefined { + const contractSourceIfExists = this._resolver.resolveIfExists(importPath); + if (!_.isUndefined(contractSourceIfExists)) { + this.resolvedContractSources.push(contractSourceIfExists); + } + return contractSourceIfExists; + } +} -- cgit From 657b698e1eba7fde2322bb81547eba26491c0af4 Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Wed, 19 Dec 2018 14:11:08 +0100 Subject: Add sol-compiler watch mode --- packages/sol-compiler/CHANGELOG.json | 13 +++++++ packages/sol-compiler/package.json | 4 ++ packages/sol-compiler/src/cli.ts | 10 ++++- packages/sol-compiler/src/compiler.ts | 46 +++++++++++++++++++++++ packages/sol-compiler/src/utils/compiler.ts | 7 ++-- packages/sol-compiler/src/utils/types.ts | 9 +++++ packages/sol-compiler/test/compiler_utils_test.ts | 6 +-- 7 files changed, 88 insertions(+), 7 deletions(-) (limited to 'packages') diff --git a/packages/sol-compiler/CHANGELOG.json b/packages/sol-compiler/CHANGELOG.json index 0a757f519..17b3f9edd 100644 --- a/packages/sol-compiler/CHANGELOG.json +++ b/packages/sol-compiler/CHANGELOG.json @@ -1,4 +1,17 @@ [ + { + "version": "2.0.0", + "changes": [ + { + "note": "Add sol-compiler watch mode with -w flag", + "pr": "TODO" + }, + { + "note": "Make error and warning colouring more visually pleasant and consistent with other compilers", + "pr": "TODO" + } + ] + }, { "version": "1.1.16", "changes": [ diff --git a/packages/sol-compiler/package.json b/packages/sol-compiler/package.json index 0ad620b1f..86167a603 100644 --- a/packages/sol-compiler/package.json +++ b/packages/sol-compiler/package.json @@ -44,7 +44,9 @@ "devDependencies": { "@0x/dev-utils": "^1.0.21", "@0x/tslint-config": "^2.0.0", + "@types/chokidar": "^1.7.5", "@types/mkdirp": "^0.5.2", + "@types/pluralize": "^0.0.29", "@types/require-from-string": "^1.2.0", "@types/semver": "^5.5.0", "chai": "^4.0.1", @@ -74,10 +76,12 @@ "@0x/web3-wrapper": "^3.2.1", "@types/yargs": "^11.0.0", "chalk": "^2.3.0", + "chokidar": "^2.0.4", "ethereum-types": "^1.1.4", "ethereumjs-util": "^5.1.1", "lodash": "^4.17.5", "mkdirp": "^0.5.1", + "pluralize": "^7.0.0", "require-from-string": "^2.0.1", "semver": "5.5.0", "solc": "^0.4.23", diff --git a/packages/sol-compiler/src/cli.ts b/packages/sol-compiler/src/cli.ts index 0a9db6e05..18cc68aaf 100644 --- a/packages/sol-compiler/src/cli.ts +++ b/packages/sol-compiler/src/cli.ts @@ -25,6 +25,10 @@ const SEPARATOR = ','; type: 'string', description: 'comma separated list of contracts to compile', }) + .option('watch', { + alias: 'w', + default: false, + }) .help().argv; const contracts = _.isUndefined(argv.contracts) ? undefined @@ -37,7 +41,11 @@ const SEPARATOR = ','; contracts, }; const compiler = new Compiler(opts); - await compiler.compileAsync(); + if (argv.watch) { + await compiler.watchAsync(); + } else { + await compiler.compileAsync(); + } })().catch(err => { logUtils.log(err); process.exit(1); diff --git a/packages/sol-compiler/src/compiler.ts b/packages/sol-compiler/src/compiler.ts index 45cbf527b..17a1ce563 100644 --- a/packages/sol-compiler/src/compiler.ts +++ b/packages/sol-compiler/src/compiler.ts @@ -6,13 +6,17 @@ import { NPMResolver, RelativeFSResolver, Resolver, + SpyResolver, URLResolver, } from '@0x/sol-resolver'; import { logUtils } from '@0x/utils'; +import chalk from 'chalk'; +import * as chokidar from 'chokidar'; import { CompilerOptions, ContractArtifact, ContractVersionData, StandardOutput } from 'ethereum-types'; import * as fs from 'fs'; import * as _ from 'lodash'; import * as path from 'path'; +import * as pluralize from 'pluralize'; import * as semver from 'semver'; import solc = require('solc'); @@ -30,6 +34,7 @@ import { } from './utils/compiler'; import { constants } from './utils/constants'; import { fsWrapper } from './utils/fs_wrapper'; +import { CompilationError } from './utils/types'; import { utils } from './utils/utils'; type TYPE_ALL_FILES_IDENTIFIER = '*'; @@ -129,6 +134,43 @@ export class Compiler { const promisedOutputs = this._compileContractsAsync(this._getContractNamesToCompile(), false); return promisedOutputs; } + public async watchAsync(): Promise { + console.clear(); // tslint:disable-line:no-console + logWithTime('Starting compilation in watch mode...'); + const watcher = chokidar.watch('^$', { ignored: /(^|[\/\\])\../ }); + const onFileChangedAsync = async () => { + watcher.unwatch('*'); // Stop watching + try { + await this.compileAsync(); + logWithTime('Found 0 errors. Watching for file changes.'); + } catch (err) { + if (err.typeName === 'CompilationError') { + logWithTime(`Found ${err.errorsCount} ${pluralize('error', err.errorsCount)}. Watching for file changes.`); + } else { + logWithTime('Found errors. Watching for file changes.'); + } + } + + const pathsToWatch = this._getPathsToWatch(); + watcher.add(pathsToWatch); + }; + await onFileChangedAsync(); + watcher.on('change', (changedFilePath: string) => { + console.clear(); // tslint:disable-line:no-console + logWithTime('File change detected. Starting incremental compilation...'); + onFileChangedAsync(); + }); + } + private _getPathsToWatch(): string[] { + const contractNames = this._getContractNamesToCompile(); + const spyResolver = new SpyResolver(this._resolver); + for (const contractName of contractNames) { + const contractSource = spyResolver.resolve(contractName); + getSourceTreeHash(spyResolver, contractSource.path); + } + const pathsToWatch = _.uniq(spyResolver.resolvedContractSources.map(cs => cs.absolutePath)); + return pathsToWatch; + } private _getContractNamesToCompile(): string[] { let contractNamesToCompile; if (this._specifiedContracts === ALL_CONTRACTS_IDENTIFIER) { @@ -298,3 +340,7 @@ export class Compiler { logUtils.warn(`${contractName} artifact saved!`); } } + +function logWithTime(arg: string): void { + logUtils.log(`[${chalk.gray(new Date().toLocaleTimeString())}] ${arg}`); +} diff --git a/packages/sol-compiler/src/utils/compiler.ts b/packages/sol-compiler/src/utils/compiler.ts index 034f72f8d..486d8bedd 100644 --- a/packages/sol-compiler/src/utils/compiler.ts +++ b/packages/sol-compiler/src/utils/compiler.ts @@ -12,6 +12,7 @@ import { binPaths } from '../solc/bin_paths'; import { constants } from './constants'; import { fsWrapper } from './fs_wrapper'; +import { CompilationError } from './types'; /** * Gets contract data on network or returns if an artifact does not exist. @@ -147,13 +148,13 @@ function printCompilationErrorsAndWarnings(solcErrors: solc.SolcError[]): void { if (!_.isEmpty(errors)) { errors.forEach(error => { const normalizedErrMsg = getNormalizedErrMsg(error.formattedMessage || error.message); - logUtils.warn(chalk.red(normalizedErrMsg)); + logUtils.log(chalk.red('error'), normalizedErrMsg); }); - throw new Error('Compilation errors encountered'); + throw new CompilationError(errors.length); } else { warnings.forEach(warning => { const normalizedWarningMsg = getNormalizedErrMsg(warning.formattedMessage || warning.message); - logUtils.warn(chalk.yellow(normalizedWarningMsg)); + logUtils.log(chalk.yellow('warning'), normalizedWarningMsg); }); } } diff --git a/packages/sol-compiler/src/utils/types.ts b/packages/sol-compiler/src/utils/types.ts index b211cfcbc..64328899d 100644 --- a/packages/sol-compiler/src/utils/types.ts +++ b/packages/sol-compiler/src/utils/types.ts @@ -29,3 +29,12 @@ export interface Token { } export type DoneCallback = (err?: Error) => void; + +export class CompilationError extends Error { + public errorsCount: number; + public typeName = 'CompilationError'; + constructor(errorsCount: number) { + super('Compilation errors encountered'); + this.errorsCount = errorsCount; + } +} diff --git a/packages/sol-compiler/test/compiler_utils_test.ts b/packages/sol-compiler/test/compiler_utils_test.ts index 4fe7b994e..b8c18110c 100644 --- a/packages/sol-compiler/test/compiler_utils_test.ts +++ b/packages/sol-compiler/test/compiler_utils_test.ts @@ -52,7 +52,7 @@ describe('Compiler utils', () => { const source = await fsWrapper.readFileAsync(path, { encoding: 'utf8', }); - const dependencies = parseDependencies({ source, path }); + const dependencies = parseDependencies({ source, path, absolutePath: path }); const expectedDependencies = [ 'zeppelin-solidity/contracts/token/ERC20/ERC20.sol', 'packages/sol-compiler/lib/test/fixtures/contracts/TokenTransferProxy.sol', @@ -68,7 +68,7 @@ describe('Compiler utils', () => { const source = await fsWrapper.readFileAsync(path, { encoding: 'utf8', }); - expect(parseDependencies({ source, path })).to.be.deep.equal([ + expect(parseDependencies({ source, path, absolutePath: path })).to.be.deep.equal([ 'zeppelin-solidity/contracts/ownership/Ownable.sol', 'zeppelin-solidity/contracts/token/ERC20/ERC20.sol', ]); @@ -77,7 +77,7 @@ describe('Compiler utils', () => { it.skip('correctly parses commented out dependencies', async () => { const path = ''; const source = `// import "./TokenTransferProxy.sol";`; - expect(parseDependencies({ path, source })).to.be.deep.equal([]); + expect(parseDependencies({ path, source, absolutePath: path })).to.be.deep.equal([]); }); }); }); -- cgit From 56d48758d308f9450aaac2e986dd09efd8d479c0 Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Wed, 19 Dec 2018 14:19:35 +0100 Subject: Add PR numbers --- packages/sol-compiler/CHANGELOG.json | 4 ++-- packages/sol-resolver/CHANGELOG.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'packages') diff --git a/packages/sol-compiler/CHANGELOG.json b/packages/sol-compiler/CHANGELOG.json index 17b3f9edd..8548fd73f 100644 --- a/packages/sol-compiler/CHANGELOG.json +++ b/packages/sol-compiler/CHANGELOG.json @@ -4,11 +4,11 @@ "changes": [ { "note": "Add sol-compiler watch mode with -w flag", - "pr": "TODO" + "pr": 1461 }, { "note": "Make error and warning colouring more visually pleasant and consistent with other compilers", - "pr": "TODO" + "pr": 1461 } ] }, diff --git a/packages/sol-resolver/CHANGELOG.json b/packages/sol-resolver/CHANGELOG.json index b898a422f..74c4d39c5 100644 --- a/packages/sol-resolver/CHANGELOG.json +++ b/packages/sol-resolver/CHANGELOG.json @@ -4,11 +4,11 @@ "changes": [ { "note": "Add `absolutePath` to `ContractSource` type", - "pr": "TODO" + "pr": 1461 }, { "note": "Add `SpyResolver` that records all resolved contracts data", - "pr": "TODO" + "pr": 1461 } ] }, -- cgit From 87d157b805465d76a8eb25cda84fb91dc064faec Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Wed, 19 Dec 2018 14:32:59 +0100 Subject: Move logWithTime function to utils --- packages/sol-compiler/src/compiler.ts | 15 +++++---------- packages/sol-compiler/src/utils/utils.ts | 6 ++++++ 2 files changed, 11 insertions(+), 10 deletions(-) (limited to 'packages') diff --git a/packages/sol-compiler/src/compiler.ts b/packages/sol-compiler/src/compiler.ts index 17a1ce563..986999254 100644 --- a/packages/sol-compiler/src/compiler.ts +++ b/packages/sol-compiler/src/compiler.ts @@ -10,7 +10,6 @@ import { URLResolver, } from '@0x/sol-resolver'; import { logUtils } from '@0x/utils'; -import chalk from 'chalk'; import * as chokidar from 'chokidar'; import { CompilerOptions, ContractArtifact, ContractVersionData, StandardOutput } from 'ethereum-types'; import * as fs from 'fs'; @@ -136,18 +135,18 @@ export class Compiler { } public async watchAsync(): Promise { console.clear(); // tslint:disable-line:no-console - logWithTime('Starting compilation in watch mode...'); + utils.logWithTime('Starting compilation in watch mode...'); const watcher = chokidar.watch('^$', { ignored: /(^|[\/\\])\../ }); const onFileChangedAsync = async () => { watcher.unwatch('*'); // Stop watching try { await this.compileAsync(); - logWithTime('Found 0 errors. Watching for file changes.'); + utils.logWithTime('Found 0 errors. Watching for file changes.'); } catch (err) { if (err.typeName === 'CompilationError') { - logWithTime(`Found ${err.errorsCount} ${pluralize('error', err.errorsCount)}. Watching for file changes.`); + utils.logWithTime(`Found ${err.errorsCount} ${pluralize('error', err.errorsCount)}. Watching for file changes.`); } else { - logWithTime('Found errors. Watching for file changes.'); + utils.logWithTime('Found errors. Watching for file changes.'); } } @@ -157,7 +156,7 @@ export class Compiler { await onFileChangedAsync(); watcher.on('change', (changedFilePath: string) => { console.clear(); // tslint:disable-line:no-console - logWithTime('File change detected. Starting incremental compilation...'); + utils.logWithTime('File change detected. Starting incremental compilation...'); onFileChangedAsync(); }); } @@ -340,7 +339,3 @@ export class Compiler { logUtils.warn(`${contractName} artifact saved!`); } } - -function logWithTime(arg: string): void { - logUtils.log(`[${chalk.gray(new Date().toLocaleTimeString())}] ${arg}`); -} diff --git a/packages/sol-compiler/src/utils/utils.ts b/packages/sol-compiler/src/utils/utils.ts index 4f2de2caa..538efc61a 100644 --- a/packages/sol-compiler/src/utils/utils.ts +++ b/packages/sol-compiler/src/utils/utils.ts @@ -1,6 +1,12 @@ +import { logUtils } from '@0x/utils'; +import chalk from 'chalk'; + export const utils = { stringifyWithFormatting(obj: any): string { const stringifiedObj = JSON.stringify(obj, null, '\t'); return stringifiedObj; }, + logWithTime(arg: string): void { + logUtils.log(`[${chalk.gray(new Date().toLocaleTimeString())}] ${arg}`); + }, }; -- cgit From e2535027e92206356d9dde80978edf64f00a50d4 Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Wed, 19 Dec 2018 14:44:05 +0100 Subject: Remove unused variables --- packages/sol-resolver/src/resolvers/spy_resolver.ts | 2 -- 1 file changed, 2 deletions(-) (limited to 'packages') diff --git a/packages/sol-resolver/src/resolvers/spy_resolver.ts b/packages/sol-resolver/src/resolvers/spy_resolver.ts index df56c8f9e..3fd62985b 100644 --- a/packages/sol-resolver/src/resolvers/spy_resolver.ts +++ b/packages/sol-resolver/src/resolvers/spy_resolver.ts @@ -1,6 +1,4 @@ -import * as fs from 'fs'; import * as _ from 'lodash'; -import * as path from 'path'; import { ContractSource } from '../types'; -- cgit From 85a8c6160f046ad5b5e33d30fc7a71abea3ecf33 Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Wed, 19 Dec 2018 14:44:40 +0100 Subject: Mark the field as readonly --- packages/sol-resolver/src/resolvers/spy_resolver.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/sol-resolver/src/resolvers/spy_resolver.ts b/packages/sol-resolver/src/resolvers/spy_resolver.ts index 3fd62985b..b31ecfe1d 100644 --- a/packages/sol-resolver/src/resolvers/spy_resolver.ts +++ b/packages/sol-resolver/src/resolvers/spy_resolver.ts @@ -6,7 +6,7 @@ import { Resolver } from './resolver'; export class SpyResolver extends Resolver { public resolvedContractSources: ContractSource[] = []; - private _resolver: Resolver; + private readonly _resolver: Resolver; constructor(resolver: Resolver) { super(); this._resolver = resolver; -- cgit From 237014e823ad4fba7bea50bdf8ec9b5a5cc25918 Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Wed, 19 Dec 2018 15:06:11 +0100 Subject: Disable linter no a hanging promise with a comment --- packages/sol-compiler/src/compiler.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'packages') diff --git a/packages/sol-compiler/src/compiler.ts b/packages/sol-compiler/src/compiler.ts index 986999254..b3531ab65 100644 --- a/packages/sol-compiler/src/compiler.ts +++ b/packages/sol-compiler/src/compiler.ts @@ -33,7 +33,6 @@ import { } from './utils/compiler'; import { constants } from './utils/constants'; import { fsWrapper } from './utils/fs_wrapper'; -import { CompilationError } from './utils/types'; import { utils } from './utils/utils'; type TYPE_ALL_FILES_IDENTIFIER = '*'; @@ -157,7 +156,9 @@ export class Compiler { watcher.on('change', (changedFilePath: string) => { console.clear(); // tslint:disable-line:no-console utils.logWithTime('File change detected. Starting incremental compilation...'); - onFileChangedAsync(); + // NOTE: We can't await it here because that's a callback. + // Instead we stop watching inside of it and start it again when we're finished. + onFileChangedAsync(); // tslint:disable-line no-floating-promises }); } private _getPathsToWatch(): string[] { -- cgit From 69de1d05efcadc1a6e70e98a0e77f32e8976560e Mon Sep 17 00:00:00 2001 From: Fabio B Date: Wed, 19 Dec 2018 15:15:15 +0100 Subject: Update packages/sol-compiler/src/compiler.ts Co-Authored-By: LogvinovLeon --- packages/sol-compiler/src/compiler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/sol-compiler/src/compiler.ts b/packages/sol-compiler/src/compiler.ts index b3531ab65..519712e20 100644 --- a/packages/sol-compiler/src/compiler.ts +++ b/packages/sol-compiler/src/compiler.ts @@ -297,7 +297,7 @@ export class Compiler { const compiledContract = compilerOutput.contracts[contractPath][contractName]; // need to gather sourceCodes for this artifact, but compilerOutput.sources (the list of contract modules) - // contains listings for for every contract compiled during the compiler invocation that compiled the contract + // contains listings for every contract compiled during the compiler invocation that compiled the contract // to be persisted, which could include many that are irrelevant to the contract at hand. So, gather up only // the relevant sources: const { sourceCodes, sources } = getSourcesWithDependencies( -- cgit From 85be2fbf19ef937709f747b0c9169a2cf3e6697e Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Wed, 19 Dec 2018 15:32:04 +0100 Subject: Move logWithTime to logUtils --- packages/sol-compiler/src/compiler.ts | 10 +++++----- packages/sol-compiler/src/utils/utils.ts | 6 ------ packages/utils/CHANGELOG.json | 9 +++++++++ packages/utils/package.json | 1 + packages/utils/src/log_utils.ts | 5 +++++ 5 files changed, 20 insertions(+), 11 deletions(-) (limited to 'packages') diff --git a/packages/sol-compiler/src/compiler.ts b/packages/sol-compiler/src/compiler.ts index 519712e20..eca887ce9 100644 --- a/packages/sol-compiler/src/compiler.ts +++ b/packages/sol-compiler/src/compiler.ts @@ -134,18 +134,18 @@ export class Compiler { } public async watchAsync(): Promise { console.clear(); // tslint:disable-line:no-console - utils.logWithTime('Starting compilation in watch mode...'); + logUtils.logWithTime('Starting compilation in watch mode...'); const watcher = chokidar.watch('^$', { ignored: /(^|[\/\\])\../ }); const onFileChangedAsync = async () => { watcher.unwatch('*'); // Stop watching try { await this.compileAsync(); - utils.logWithTime('Found 0 errors. Watching for file changes.'); + logUtils.logWithTime('Found 0 errors. Watching for file changes.'); } catch (err) { if (err.typeName === 'CompilationError') { - utils.logWithTime(`Found ${err.errorsCount} ${pluralize('error', err.errorsCount)}. Watching for file changes.`); + logUtils.logWithTime(`Found ${err.errorsCount} ${pluralize('error', err.errorsCount)}. Watching for file changes.`); } else { - utils.logWithTime('Found errors. Watching for file changes.'); + logUtils.logWithTime('Found errors. Watching for file changes.'); } } @@ -155,7 +155,7 @@ export class Compiler { await onFileChangedAsync(); watcher.on('change', (changedFilePath: string) => { console.clear(); // tslint:disable-line:no-console - utils.logWithTime('File change detected. Starting incremental compilation...'); + logUtils.logWithTime('File change detected. Starting incremental compilation...'); // NOTE: We can't await it here because that's a callback. // Instead we stop watching inside of it and start it again when we're finished. onFileChangedAsync(); // tslint:disable-line no-floating-promises diff --git a/packages/sol-compiler/src/utils/utils.ts b/packages/sol-compiler/src/utils/utils.ts index 538efc61a..4f2de2caa 100644 --- a/packages/sol-compiler/src/utils/utils.ts +++ b/packages/sol-compiler/src/utils/utils.ts @@ -1,12 +1,6 @@ -import { logUtils } from '@0x/utils'; -import chalk from 'chalk'; - export const utils = { stringifyWithFormatting(obj: any): string { const stringifiedObj = JSON.stringify(obj, null, '\t'); return stringifiedObj; }, - logWithTime(arg: string): void { - logUtils.log(`[${chalk.gray(new Date().toLocaleTimeString())}] ${arg}`); - }, }; diff --git a/packages/utils/CHANGELOG.json b/packages/utils/CHANGELOG.json index fe66d3f31..605151fb6 100644 --- a/packages/utils/CHANGELOG.json +++ b/packages/utils/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "version": "2.1.0", + "changes": [ + { + "note": "Add `logWithTime` to `logUtils`", + "pr": 1461 + } + ] + }, { "version": "2.0.8", "changes": [ diff --git a/packages/utils/package.json b/packages/utils/package.json index a25dc9cff..5ffec049a 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -49,6 +49,7 @@ "@types/node": "*", "abortcontroller-polyfill": "^1.1.9", "bignumber.js": "~4.1.0", + "chalk": "^2.4.1", "detect-node": "2.0.3", "ethereum-types": "^1.1.4", "ethereumjs-util": "^5.1.1", diff --git a/packages/utils/src/log_utils.ts b/packages/utils/src/log_utils.ts index 87f8479b5..6d9996c67 100644 --- a/packages/utils/src/log_utils.ts +++ b/packages/utils/src/log_utils.ts @@ -1,3 +1,5 @@ +import chalk from 'chalk'; + export const logUtils = { log(...args: any[]): void { console.log(...args); // tslint:disable-line:no-console @@ -5,4 +7,7 @@ export const logUtils = { warn(...args: any[]): void { console.warn(...args); // tslint:disable-line:no-console }, + logWithTime(arg: string): void { + logUtils.log(`[${chalk.gray(new Date().toLocaleTimeString())}] ${arg}`); + }, }; -- cgit From 0abf1c71f64e90aba3e3bac55c8c24dc984bf80d Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Wed, 19 Dec 2018 15:33:25 +0100 Subject: Add a comment for SpyResolver --- packages/sol-resolver/src/resolvers/spy_resolver.ts | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'packages') diff --git a/packages/sol-resolver/src/resolvers/spy_resolver.ts b/packages/sol-resolver/src/resolvers/spy_resolver.ts index b31ecfe1d..5582d771a 100644 --- a/packages/sol-resolver/src/resolvers/spy_resolver.ts +++ b/packages/sol-resolver/src/resolvers/spy_resolver.ts @@ -4,6 +4,10 @@ import { ContractSource } from '../types'; import { Resolver } from './resolver'; +/** + * This resolver is a passthrough proxy to any resolver that records all the resolved contracts sources. + * You can access them later using the `resolvedContractSources` public field. + */ export class SpyResolver extends Resolver { public resolvedContractSources: ContractSource[] = []; private readonly _resolver: Resolver; -- cgit From 5c4a992b87aa4341fc768eb4865179f2d36abad2 Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Wed, 19 Dec 2018 15:36:26 +0100 Subject: Add a NOTE comment --- packages/sol-compiler/src/compiler.ts | 3 +++ 1 file changed, 3 insertions(+) (limited to 'packages') diff --git a/packages/sol-compiler/src/compiler.ts b/packages/sol-compiler/src/compiler.ts index eca887ce9..793ab4f93 100644 --- a/packages/sol-compiler/src/compiler.ts +++ b/packages/sol-compiler/src/compiler.ts @@ -166,6 +166,9 @@ export class Compiler { const spyResolver = new SpyResolver(this._resolver); for (const contractName of contractNames) { const contractSource = spyResolver.resolve(contractName); + // NOTE: We ignore the return value here. We don't want to compute the source tree hash. + // We just want to call a SpyResolver on each contracts and it's dependencies and + // this is a convinient way to reuse the existing code that does that. getSourceTreeHash(spyResolver, contractSource.path); } const pathsToWatch = _.uniq(spyResolver.resolvedContractSources.map(cs => cs.absolutePath)); -- cgit From 5656605355f201e36667cd054d0eed0a3ba0dbfe Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Wed, 19 Dec 2018 15:39:43 +0100 Subject: Describe regexes --- packages/sol-compiler/src/compiler.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/sol-compiler/src/compiler.ts b/packages/sol-compiler/src/compiler.ts index 793ab4f93..6b77a464d 100644 --- a/packages/sol-compiler/src/compiler.ts +++ b/packages/sol-compiler/src/compiler.ts @@ -135,7 +135,10 @@ export class Compiler { public async watchAsync(): Promise { console.clear(); // tslint:disable-line:no-console logUtils.logWithTime('Starting compilation in watch mode...'); - const watcher = chokidar.watch('^$', { ignored: /(^|[\/\\])\../ }); + const MATCH_NOTHING_REGEX = '^$'; + const IGNORE_DOT_FILES_REGEX = /(^|[\/\\])\../; + // Initially we watch nothing. We'll add the paths later. + const watcher = chokidar.watch(MATCH_NOTHING_REGEX, { ignored: IGNORE_DOT_FILES_REGEX }); const onFileChangedAsync = async () => { watcher.unwatch('*'); // Stop watching try { -- cgit From 86a9375d047f78981ffba74d24543184b8ea089a Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Wed, 19 Dec 2018 15:40:27 +0100 Subject: Run prettier --- packages/sol-compiler/src/compiler.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/sol-compiler/src/compiler.ts b/packages/sol-compiler/src/compiler.ts index 6b77a464d..08ab97ba8 100644 --- a/packages/sol-compiler/src/compiler.ts +++ b/packages/sol-compiler/src/compiler.ts @@ -146,7 +146,9 @@ export class Compiler { logUtils.logWithTime('Found 0 errors. Watching for file changes.'); } catch (err) { if (err.typeName === 'CompilationError') { - logUtils.logWithTime(`Found ${err.errorsCount} ${pluralize('error', err.errorsCount)}. Watching for file changes.`); + logUtils.logWithTime( + `Found ${err.errorsCount} ${pluralize('error', err.errorsCount)}. Watching for file changes.`, + ); } else { logUtils.logWithTime('Found errors. Watching for file changes.'); } -- cgit From e886ef8c4b3d5f97fca6eb6decf3273fb401222d Mon Sep 17 00:00:00 2001 From: Fabio B Date: Wed, 19 Dec 2018 15:57:19 +0100 Subject: Update packages/sol-compiler/src/utils/compiler.ts Co-Authored-By: LogvinovLeon --- packages/sol-compiler/src/utils/compiler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/sol-compiler/src/utils/compiler.ts b/packages/sol-compiler/src/utils/compiler.ts index 486d8bedd..db308f2b5 100644 --- a/packages/sol-compiler/src/utils/compiler.ts +++ b/packages/sol-compiler/src/utils/compiler.ts @@ -224,7 +224,7 @@ function recursivelyGatherDependencySources( } let importPath = importPathMatches[1]; - // HACK(ablrow): We have, e.g.: + // HACK(albrow): We have, e.g.: // // importPath = "../../utils/LibBytes/LibBytes.sol" // contractPath = "2.0.0/protocol/AssetProxyOwner/AssetProxyOwner.sol" -- cgit From d456710441f5d126c63ae31003ddfffb2654047a Mon Sep 17 00:00:00 2001 From: Fabio B Date: Wed, 19 Dec 2018 15:57:30 +0100 Subject: Update packages/sol-compiler/src/compiler.ts Co-Authored-By: LogvinovLeon --- packages/sol-compiler/src/compiler.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/sol-compiler/src/compiler.ts b/packages/sol-compiler/src/compiler.ts index 08ab97ba8..d38ccbf39 100644 --- a/packages/sol-compiler/src/compiler.ts +++ b/packages/sol-compiler/src/compiler.ts @@ -173,7 +173,8 @@ export class Compiler { const contractSource = spyResolver.resolve(contractName); // NOTE: We ignore the return value here. We don't want to compute the source tree hash. // We just want to call a SpyResolver on each contracts and it's dependencies and - // this is a convinient way to reuse the existing code that does that. + // this is a convenient way to reuse the existing code that does that. + // We can then get all the relevant paths from the `spyResolver` below. getSourceTreeHash(spyResolver, contractSource.path); } const pathsToWatch = _.uniq(spyResolver.resolvedContractSources.map(cs => cs.absolutePath)); -- cgit