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 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 v1.2.3 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 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 v1.2.3 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(-) 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 v1.2.3 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 --------------------- yarn.lock | 39 +-- 7 files changed, 483 insertions(+), 509 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 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.`); - }); -}); diff --git a/yarn.lock b/yarn.lock index 6522c9979..f391c8f97 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2008,10 +2008,6 @@ aes-js@^0.2.3: version "0.2.4" resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-0.2.4.tgz#94b881ab717286d015fa219e08fb66709dda5a3d" -aes-js@^3.1.1: - version "3.1.2" - resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-3.1.2.tgz#db9aabde85d5caabbfc0d4f2a4446960f627146a" - agent-base@4, agent-base@^4.1.0, agent-base@~4.2.0: version "4.2.1" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9" @@ -3470,7 +3466,7 @@ bs-logger@0.x: dependencies: fast-json-stable-stringify "^2.0.0" -bs58@=4.0.1, bs58@^4.0.0: +bs58@=4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a" dependencies: @@ -3493,14 +3489,6 @@ bs58check@^1.0.8: bs58 "^3.1.0" create-hash "^1.1.0" -bs58check@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/bs58check/-/bs58check-2.1.2.tgz#53b018291228d82a5aa08e7d796fdafda54aebfc" - dependencies: - bs58 "^4.0.0" - create-hash "^1.1.0" - safe-buffer "^5.1.2" - bser@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/bser/-/bser-2.0.0.tgz#9ac78d3ed5d915804fd87acb158bc797147a1719" @@ -6175,19 +6163,6 @@ ethereumjs-wallet@0.6.0: utf8 "^2.1.1" uuid "^2.0.1" -ethereumjs-wallet@~0.6.0: - version "0.6.2" - resolved "https://registry.yarnpkg.com/ethereumjs-wallet/-/ethereumjs-wallet-0.6.2.tgz#67244b6af3e8113b53d709124b25477b64aeccda" - dependencies: - aes-js "^3.1.1" - bs58check "^2.1.2" - ethereumjs-util "^5.2.0" - hdkey "^1.0.0" - safe-buffer "^5.1.2" - scrypt.js "^0.2.0" - utf8 "^3.0.0" - uuid "^3.3.2" - ethers@~4.0.4: version "4.0.4" resolved "https://registry.yarnpkg.com/ethers/-/ethers-4.0.4.tgz#d3f85e8b27f4b59537e06526439b0fb15b44dc65" @@ -7752,14 +7727,6 @@ hdkey@^0.7.0, hdkey@^0.7.1: coinstring "^2.0.0" secp256k1 "^3.0.1" -hdkey@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/hdkey/-/hdkey-1.1.0.tgz#e74e7b01d2c47f797fa65d1d839adb7a44639f29" - dependencies: - coinstring "^2.0.0" - safe-buffer "^5.1.1" - secp256k1 "^3.0.1" - he@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" @@ -16177,10 +16144,6 @@ utf8@^2.1.1: version "2.1.2" resolved "https://registry.yarnpkg.com/utf8/-/utf8-2.1.2.tgz#1fa0d9270e9be850d9b05027f63519bf46457d96" -utf8@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/utf8/-/utf8-3.0.0.tgz#f052eed1364d696e769ef058b183df88c87f69d1" - util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" -- cgit v1.2.3 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(-) 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 v1.2.3 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(-) 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 v1.2.3 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 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 v1.2.3 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(-) 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 v1.2.3 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(-) 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 v1.2.3 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(-) 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 v1.2.3 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 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 v1.2.3 From 18e55830b5816c342d8e9d9f64ae035f9284ea80 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Tue, 18 Dec 2018 15:31:58 +0000 Subject: Fix version picker so it doesn't overflow onto two lines --- .../ts/components/documentation/sidebar_header.tsx | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/website/ts/components/documentation/sidebar_header.tsx b/packages/website/ts/components/documentation/sidebar_header.tsx index 9ced52c74..0ab24ab5e 100644 --- a/packages/website/ts/components/documentation/sidebar_header.tsx +++ b/packages/website/ts/components/documentation/sidebar_header.tsx @@ -24,7 +24,7 @@ export const SidebarHeader: React.StatelessComponent = ({ return ( - + = ({ {!_.isUndefined(docsVersion) && !_.isUndefined(availableDocVersions) && !_.isUndefined(onVersionSelected) && ( -
- +
+ + +
)} -- cgit v1.2.3 From 67df5a433d68a2af1a3a03a8bf431629a534dc97 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Tue, 18 Dec 2018 15:32:11 +0000 Subject: Fix OrderWatcher title to fix sidebar top --- packages/website/ts/containers/order_watcher_documentation.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/website/ts/containers/order_watcher_documentation.ts b/packages/website/ts/containers/order_watcher_documentation.ts index ac92e6a22..683e1fe9f 100644 --- a/packages/website/ts/containers/order_watcher_documentation.ts +++ b/packages/website/ts/containers/order_watcher_documentation.ts @@ -24,7 +24,7 @@ const docsInfoConfig: DocsInfoConfig = { id: DocPackages.OrderWatcher, packageName: '@0x/order-watcher', type: SupportedDocJson.TypeDoc, - displayName: 'OrderWatcher', + displayName: 'Order Watcher', packageUrl: 'https://github.com/0xProject/0x-monorepo', markdownMenu: { 'getting-started': [markdownSections.introduction, markdownSections.installation], -- cgit v1.2.3 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 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 v1.2.3 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 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 v1.2.3 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 --- contracts/test-utils/src/types.ts | 1 + .../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 ++++++ 8 files changed, 1580 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 diff --git a/contracts/test-utils/src/types.ts b/contracts/test-utils/src/types.ts index d738fcd4e..04f95e1a8 100644 --- a/contracts/test-utils/src/types.ts +++ b/contracts/test-utils/src/types.ts @@ -99,6 +99,7 @@ export enum ContractName { ERC721Proxy = 'ERC721Proxy', DummyERC721Receiver = 'DummyERC721Receiver', DummyERC721Token = 'DummyERC721Token', + DummyYesComplianceToken = 'DummyYesComplianceToken', TestLibBytes = 'TestLibBytes', TestWallet = 'TestWallet', Authorizable = 'Authorizable', 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 v1.2.3 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 --- contracts/test-utils/src/types.ts | 2 +- 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 ++- 7 files changed, 22 insertions(+), 38 deletions(-) create mode 100644 packages/contracts/contracts/tokens/README.md diff --git a/contracts/test-utils/src/types.ts b/contracts/test-utils/src/types.ts index 04f95e1a8..f0830deb4 100644 --- a/contracts/test-utils/src/types.ts +++ b/contracts/test-utils/src/types.ts @@ -84,6 +84,7 @@ export enum ContractName { MultiSigWalletWithTimeLock = 'MultiSigWalletWithTimeLock', Exchange = 'Exchange', ZRXToken = 'ZRXToken', + YesComplianceToken = 'YesComplianceToken', DummyERC20Token = 'DummyERC20Token', EtherToken = 'WETH9', DutchAuction = 'DutchAuction', @@ -99,7 +100,6 @@ export enum ContractName { ERC721Proxy = 'ERC721Proxy', DummyERC721Receiver = 'DummyERC721Receiver', DummyERC721Token = 'DummyERC721Token', - DummyYesComplianceToken = 'DummyYesComplianceToken', TestLibBytes = 'TestLibBytes', TestWallet = 'TestWallet', Authorizable = 'Authorizable', 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 v1.2.3 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/test-utils/src/types.ts | 1 + .../contracts/extensions/CompliantForwarder/CompliantForwarder.sol | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/contracts/test-utils/src/types.ts b/contracts/test-utils/src/types.ts index f0830deb4..46b7ad941 100644 --- a/contracts/test-utils/src/types.ts +++ b/contracts/test-utils/src/types.ts @@ -105,6 +105,7 @@ export enum ContractName { Authorizable = 'Authorizable', Whitelist = 'Whitelist', Forwarder = 'Forwarder', + CompliantForwarder = 'CompliantForwarder', } export interface SignedTransaction { 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 v1.2.3 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 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 v1.2.3 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 --- contracts/extensions/test/extensions/forwarder.ts | 1 + .../test/extensions/compliant_forwarder.ts | 172 +++++++++++++++------ 2 files changed, 126 insertions(+), 47 deletions(-) diff --git a/contracts/extensions/test/extensions/forwarder.ts b/contracts/extensions/test/extensions/forwarder.ts index 4027f493d..a4afa283e 100644 --- a/contracts/extensions/test/extensions/forwarder.ts +++ b/contracts/extensions/test/extensions/forwarder.ts @@ -61,6 +61,7 @@ describe(ContractName.Forwarder, () => { let wethContract: WETH9Contract; let forwarderWrapper: ForwarderWrapper; let exchangeWrapper: ExchangeWrapper; + let takerPrivateKey: Buffer; let orderWithoutFee: SignedOrder; let orderWithFee: SignedOrder; 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 v1.2.3 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(-) 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 v1.2.3 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(-) 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 v1.2.3 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(-) 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 v1.2.3 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(-) 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 v1.2.3 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(-) 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 v1.2.3 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(-) 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 v1.2.3 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(-) 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 v1.2.3 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(-) 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 v1.2.3 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(-) 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 v1.2.3 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 --- contracts/protocol/test/utils/exchange_wrapper.ts | 1 + .../CompliantForwarder/CompliantForwarder.sol | 140 +++++++++++---------- .../test/extensions/compliant_forwarder.ts | 35 +++--- 3 files changed, 99 insertions(+), 77 deletions(-) diff --git a/contracts/protocol/test/utils/exchange_wrapper.ts b/contracts/protocol/test/utils/exchange_wrapper.ts index cb6dce901..6106e78ca 100644 --- a/contracts/protocol/test/utils/exchange_wrapper.ts +++ b/contracts/protocol/test/utils/exchange_wrapper.ts @@ -214,6 +214,7 @@ export class ExchangeWrapper { { from }, ); const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); + console.log(JSON.stringify(tx)); return tx; } public async getTakerAssetFilledAmountAsync(orderHashHex: string): Promise { 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 v1.2.3 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 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 v1.2.3 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(-) 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 v1.2.3 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(-) 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 v1.2.3 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(-) 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 v1.2.3 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(-) 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 v1.2.3 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(-) 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 v1.2.3 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(-) 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 v1.2.3 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(-) 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 v1.2.3 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(-) 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 v1.2.3 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(-) 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 v1.2.3 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(-) 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 v1.2.3 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(-) 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 v1.2.3 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(-) 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 v1.2.3 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(-) 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 v1.2.3 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(-) 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 v1.2.3 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 --- contracts/test-utils/src/types.ts | 2 +- .../BalanceThresholdFilter.sol | 323 ++++++++++++++++++ .../interfaces/IThresholdAsset.sol | 30 ++ .../CompliantForwarder/CompliantForwarder.sol | 323 ------------------ .../test/extensions/balance_threshold_filter.ts | 366 +++++++++++++++++++++ .../test/extensions/compliant_forwarder.ts | 366 --------------------- 6 files changed, 720 insertions(+), 690 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 diff --git a/contracts/test-utils/src/types.ts b/contracts/test-utils/src/types.ts index 46b7ad941..cbdd513eb 100644 --- a/contracts/test-utils/src/types.ts +++ b/contracts/test-utils/src/types.ts @@ -105,7 +105,7 @@ export enum ContractName { Authorizable = 'Authorizable', Whitelist = 'Whitelist', Forwarder = 'Forwarder', - CompliantForwarder = 'CompliantForwarder', + BalanceThresholdFilter = 'BalanceThresholdFilter', } export interface SignedTransaction { 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 v1.2.3 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 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 v1.2.3 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(-) 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 v1.2.3 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(-) 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 v1.2.3 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(-) 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 v1.2.3 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(-) 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 v1.2.3 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 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 v1.2.3 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(-) 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 v1.2.3 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(-) 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 v1.2.3 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(-) 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 v1.2.3 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(-) 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 v1.2.3 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(-) 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 v1.2.3 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(-) 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 v1.2.3 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(+) 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 v1.2.3 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(-) 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 v1.2.3 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(-) 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 v1.2.3 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(-) 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 v1.2.3 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(-) 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 v1.2.3 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 --- contracts/test-utils/src/types.ts | 1 - .../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 +- 8 files changed, 12 insertions(+), 1454 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 diff --git a/contracts/test-utils/src/types.ts b/contracts/test-utils/src/types.ts index cbdd513eb..1630eab0d 100644 --- a/contracts/test-utils/src/types.ts +++ b/contracts/test-utils/src/types.ts @@ -84,7 +84,6 @@ export enum ContractName { MultiSigWalletWithTimeLock = 'MultiSigWalletWithTimeLock', Exchange = 'Exchange', ZRXToken = 'ZRXToken', - YesComplianceToken = 'YesComplianceToken', DummyERC20Token = 'DummyERC20Token', EtherToken = 'WETH9', DutchAuction = 'DutchAuction', 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 v1.2.3 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(-) 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 v1.2.3 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(-) 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 v1.2.3 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 --- contracts/extensions/compiler.json | 2 +- .../BalanceThresholdFilter.sol | 44 + .../MixinBalanceThresholdFilterCore.sol | 319 ++++ .../interfaces/IThresholdAsset.sol | 30 + .../mixins/MBalanceThresholdFilterCore.sol | 81 ++ contracts/extensions/package.json | 2 +- contracts/extensions/src/artifacts/index.ts | 2 + contracts/extensions/src/wrappers/index.ts | 1 + .../test/extensions/balance_threshold_filter.ts | 1530 ++++++++++++++++++++ .../test/utils/balance_threshold_wrapper.ts | 246 ++++ contracts/extensions/tsconfig.json | 2 +- .../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 ---- 17 files changed, 2256 insertions(+), 2245 deletions(-) create mode 100644 contracts/extensions/contracts/BalanceThresholdFilter/BalanceThresholdFilter.sol create mode 100644 contracts/extensions/contracts/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol create mode 100644 contracts/extensions/contracts/BalanceThresholdFilter/interfaces/IThresholdAsset.sol create mode 100644 contracts/extensions/contracts/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol create mode 100644 contracts/extensions/test/extensions/balance_threshold_filter.ts create mode 100644 contracts/extensions/test/utils/balance_threshold_wrapper.ts 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 diff --git a/contracts/extensions/compiler.json b/contracts/extensions/compiler.json index 69d607b3e..e6ed0c215 100644 --- a/contracts/extensions/compiler.json +++ b/contracts/extensions/compiler.json @@ -18,5 +18,5 @@ } } }, - "contracts": ["DutchAuction", "Forwarder"] + "contracts": ["BalanceThresholdFilter", "DutchAuction", "Forwarder"] } diff --git a/contracts/extensions/contracts/BalanceThresholdFilter/BalanceThresholdFilter.sol b/contracts/extensions/contracts/BalanceThresholdFilter/BalanceThresholdFilter.sol new file mode 100644 index 000000000..a68f6805d --- /dev/null +++ b/contracts/extensions/contracts/BalanceThresholdFilter/BalanceThresholdFilter.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 "@0x/contracts-interfaces/contracts/protocol/Exchange/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; + } +} diff --git a/contracts/extensions/contracts/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol b/contracts/extensions/contracts/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol new file mode 100644 index 000000000..0ad8ccddf --- /dev/null +++ b/contracts/extensions/contracts/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol @@ -0,0 +1,319 @@ +/* + + 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/contracts/extensions/contracts/BalanceThresholdFilter/interfaces/IThresholdAsset.sol b/contracts/extensions/contracts/BalanceThresholdFilter/interfaces/IThresholdAsset.sol new file mode 100644 index 000000000..61acaba0a --- /dev/null +++ b/contracts/extensions/contracts/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/contracts/extensions/contracts/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol b/contracts/extensions/contracts/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol new file mode 100644 index 000000000..ff0042f58 --- /dev/null +++ b/contracts/extensions/contracts/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol @@ -0,0 +1,81 @@ +/* + + 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 "@0x/contracts-interfaces/contracts/protocol/Exchange/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/contracts/extensions/package.json b/contracts/extensions/package.json index 938e1138c..2d9ed4dcd 100644 --- a/contracts/extensions/package.json +++ b/contracts/extensions/package.json @@ -31,7 +31,7 @@ "lint-contracts": "solhint -c ../.solhint.json contracts/**/**/**/**/*.sol" }, "config": { - "abis": "generated-artifacts/@(DutchAuction|Forwarder).json" + "abis": "generated-artifacts/@(BalanceThresholdFilter|DutchAuction|Forwarder).json" }, "repository": { "type": "git", diff --git a/contracts/extensions/src/artifacts/index.ts b/contracts/extensions/src/artifacts/index.ts index 7588178f0..ebf0b8050 100644 --- a/contracts/extensions/src/artifacts/index.ts +++ b/contracts/extensions/src/artifacts/index.ts @@ -1,9 +1,11 @@ import { ContractArtifact } from 'ethereum-types'; +import * as BalanceThresholdFilter from '../../generated-artifacts/BalanceThresholdFilter.json'; import * as DutchAuction from '../../generated-artifacts/DutchAuction.json'; import * as Forwarder from '../../generated-artifacts/Forwarder.json'; export const artifacts = { + BalanceThresholdFilter: BalanceThresholdFilter as ContractArtifact, DutchAuction: DutchAuction as ContractArtifact, Forwarder: Forwarder as ContractArtifact, }; diff --git a/contracts/extensions/src/wrappers/index.ts b/contracts/extensions/src/wrappers/index.ts index 90880e37f..8a8122caa 100644 --- a/contracts/extensions/src/wrappers/index.ts +++ b/contracts/extensions/src/wrappers/index.ts @@ -1,2 +1,3 @@ +export * from '../../generated-wrappers/balance_threshold_filter'; export * from '../../generated-wrappers/dutch_auction'; export * from '../../generated-wrappers/forwarder'; diff --git a/contracts/extensions/test/extensions/balance_threshold_filter.ts b/contracts/extensions/test/extensions/balance_threshold_filter.ts new file mode 100644 index 000000000..34339564c --- /dev/null +++ b/contracts/extensions/test/extensions/balance_threshold_filter.ts @@ -0,0 +1,1530 @@ +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 { + artifacts as protocolArtifacts, + ERC20Wrapper, + ERC721Wrapper, + ExchangeContract, + ExchangeWrapper, + TestExchangeInternalsContract, +} from '@0x/contracts-protocol'; +import { + chaiSetup, + constants, + ContractName, + ERC20BalancesByOwner, + expectTransactionFailedAsync, + expectTransactionFailedWithoutReasonAsync, + getLatestBlockTimestampAsync, + LogDecoder, + OrderFactory, + OrderStatus, + orderUtils, + provider, + SignedTransaction, + txDefaults, + TransactionFactory, + web3Wrapper, +} from '@0x/contracts-test-utils'; +import { + artifacts as tokensArtifacts, + DummyERC20TokenContract, + DummyERC721TokenContract, + WETH9Contract, +} from '@0x/contracts-tokens'; + +import { artifacts } from '../../src/artifacts'; +import { BalanceThresholdFilterContract } from '../../generated-wrappers/balance_threshold_filter'; +import { BalanceThresholdWrapper } from '../utils/balance_threshold_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( + protocolArtifacts.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, {... artifacts, ... protocolArtifacts, ... tokensArtifacts}); + 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( + protocolArtifacts.TestExchangeInternals, + provider, + txDefaults, + ); + }); + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + }); + + describe('General Sanity Checks', () => { + beforeEach(async () => { + erc20Balances = await erc20Wrapper.getBalancesAsync(); + compliantSignedOrder = await orderFactory.newSignedOrderAsync(); + compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); + }); + it('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/contracts/extensions/test/utils/balance_threshold_wrapper.ts b/contracts/extensions/test/utils/balance_threshold_wrapper.ts new file mode 100644 index 000000000..63128686c --- /dev/null +++ b/contracts/extensions/test/utils/balance_threshold_wrapper.ts @@ -0,0 +1,246 @@ +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 '@0x/contracts-protocol'; +import { BalanceThresholdFilterContract } from '../../generated-wrappers/balance_threshold_filter'; + +import { formatters, LogDecoder, orderUtils, OrderInfo, TransactionFactory } from '@0x/contracts-test-utils'; +import { artifacts } from '../../src/artifacts'; +import {artifacts as protocolArtifacts} from '@0x/contracts-protocol'; +import { artifacts as tokensArtifacts } from '@0x/contracts-tokens'; + +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, {... artifacts, ... tokensArtifacts, ... protocolArtifacts} ); + } + 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; + } +} diff --git a/contracts/extensions/tsconfig.json b/contracts/extensions/tsconfig.json index a4ce1e002..5d3ebfe74 100644 --- a/contracts/extensions/tsconfig.json +++ b/contracts/extensions/tsconfig.json @@ -6,6 +6,6 @@ "resolveJsonModule": true }, "include": ["./src/**/*", "./test/**/*", "./generated-wrappers/**/*"], - "files": ["./generated-artifacts/DutchAuction.json", "./generated-artifacts/Forwarder.json"], + "files": ["./generated-artifacts/BalanceThresholdFilter.json", "./generated-artifacts/DutchAuction.json", "./generated-artifacts/Forwarder.json"], "exclude": ["./deploy/solc/solc_bin"] } 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 v1.2.3 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 --- .../test/extensions/balance_threshold_filter.ts | 779 +++++++++++++-------- contracts/extensions/test/extensions/forwarder.ts | 1 - .../test/utils/balance_threshold_wrapper.ts | 95 ++- contracts/extensions/tsconfig.json | 6 +- packages/types/src/index.ts | 2 +- 5 files changed, 544 insertions(+), 339 deletions(-) diff --git a/contracts/extensions/test/extensions/balance_threshold_filter.ts b/contracts/extensions/test/extensions/balance_threshold_filter.ts index 34339564c..e4eda3354 100644 --- a/contracts/extensions/test/extensions/balance_threshold_filter.ts +++ b/contracts/extensions/test/extensions/balance_threshold_filter.ts @@ -1,12 +1,12 @@ import { BlockchainLifecycle } from '@0x/dev-utils'; import { assetDataUtils } from '@0x/order-utils'; -import { RevertReason, SignedOrder, Order } from '@0x/types'; +import { Order, 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 * as ethUtil from 'ethereumjs-util'; import * as _ from 'lodash'; -import { TransactionReceiptWithDecodedLogs } from 'ethereum-types'; import { artifacts as protocolArtifacts, @@ -14,7 +14,6 @@ import { ERC721Wrapper, ExchangeContract, ExchangeWrapper, - TestExchangeInternalsContract, } from '@0x/contracts-protocol'; import { chaiSetup, @@ -23,26 +22,19 @@ import { ERC20BalancesByOwner, expectTransactionFailedAsync, expectTransactionFailedWithoutReasonAsync, - getLatestBlockTimestampAsync, - LogDecoder, OrderFactory, OrderStatus, orderUtils, provider, SignedTransaction, - txDefaults, TransactionFactory, + txDefaults, web3Wrapper, } from '@0x/contracts-test-utils'; -import { - artifacts as tokensArtifacts, - DummyERC20TokenContract, - DummyERC721TokenContract, - WETH9Contract, -} from '@0x/contracts-tokens'; +import { DummyERC20TokenContract } from '@0x/contracts-tokens'; -import { artifacts } from '../../src/artifacts'; import { BalanceThresholdFilterContract } from '../../generated-wrappers/balance_threshold_filter'; +import { artifacts } from '../../src/artifacts'; import { BalanceThresholdWrapper } from '../utils/balance_threshold_wrapper'; chaiSetup.configure(); @@ -51,7 +43,7 @@ const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); const DECIMALS_DEFAULT = 18; interface ValidatedAddressesLog { - args: {addresses: string[]} + args: { addresses: string[] }; } describe.only(ContractName.BalanceThresholdFilter, () => { @@ -79,14 +71,10 @@ describe.only(ContractName.BalanceThresholdFilter, () => { 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); @@ -96,14 +84,17 @@ describe.only(ContractName.BalanceThresholdFilter, () => { let erc721CompliantForwarderInstance: BalanceThresholdFilterContract; let erc20CompliantForwarderInstance: BalanceThresholdFilterContract; - const assertValidatedAddressesLog = async (txReceipt: TransactionReceiptWithDecodedLogs, expectedValidatedAddresses: string[]) => { + 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 validatedAddressesNormalized: string[] = []; + _.each(validatedAddresses, address => { const normalizedAddress = _.toLower(address); validatedAddressesNormalized.push(normalizedAddress); }); @@ -124,7 +115,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { ] = accounts); // Create wrappers erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner); - let compliantAddresses = _.cloneDeepWith(usedAddresses); + const compliantAddresses = _.cloneDeepWith(usedAddresses); _.remove(compliantAddresses, (address: string) => { return address === nonCompliantAddress; }); @@ -168,7 +159,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { txDefaults, exchangeInstance.address, erc721BalanceThresholdAsset.address, - erc721alanceThreshold + erc721alanceThreshold, ); const erc20BalanceThreshold = Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 10); erc20CompliantForwarderInstance = await BalanceThresholdFilterContract.deployFrom0xArtifactAsync( @@ -177,7 +168,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { txDefaults, exchangeInstance.address, erc20BalanceThresholdAsset.address, - erc20BalanceThreshold + erc20BalanceThreshold, ); // Default order parameters @@ -194,25 +185,22 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }; const defaultOrderParams1 = { makerAddress: compliantMakerAddress, - ... - defaultOrderParams, - } + ...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, - } + ...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, + ...defaultOrderParams, }; nonCompliantOrderFactory = new OrderFactory(nonCompliantPrivateKey, defaultNonCompliantOrderParams); // Create Valid/Invalid orders @@ -233,17 +221,29 @@ describe.only(ContractName.BalanceThresholdFilter, () => { compliantSignedOrderWithoutExchangeAddressData, ); - logDecoder = new LogDecoder(web3Wrapper, {... artifacts, ... protocolArtifacts, ... tokensArtifacts}); - 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( - protocolArtifacts.TestExchangeInternals, + 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, - txDefaults, ); }); beforeEach(async () => { @@ -261,16 +261,22 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); it('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, + ...defaultOrderParams, makerAddress: compliantMakerAddress, senderAddress: erc20TakerBalanceThresholdWrapper.getBalanceThresholdAddress(), }); // Execute a valid fill - const txReceipt = await erc20TakerBalanceThresholdWrapper.fillOrderAsync(compliantSignedOrderERC20Sender, compliantTakerAddress, {takerAssetFillAmount}); + const txReceipt = await erc20TakerBalanceThresholdWrapper.fillOrderAsync( + compliantSignedOrderERC20Sender, + compliantTakerAddress, + { takerAssetFillAmount }, + ); // Assert validated addresses - const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedFillOrderTx.signerAddress]; - assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); + const expectedValidatedAddresseses = [ + compliantSignedOrder.makerAddress, + compliantSignedFillOrderTx.signerAddress, + ]; + await assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); // Check balances const newBalances = await erc20Wrapper.getBalancesAsync(); const makerAssetFillAmount = takerAssetFillAmount @@ -314,12 +320,14 @@ describe.only(ContractName.BalanceThresholdFilter, () => { 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, - )); + 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 @@ -339,12 +347,14 @@ describe.only(ContractName.BalanceThresholdFilter, () => { signedOrderWithoutExchangeAddressData, ); // Call compliant forwarder - return expectTransactionFailedWithoutReasonAsync(erc721CompliantForwarderInstance.executeTransaction.sendTransactionAsync( - signedFillOrderTx.salt, - signedFillOrderTx.signerAddress, - signedFillOrderTx.data, - signedFillOrderTx.signature, - )); + return expectTransactionFailedWithoutReasonAsync( + erc721CompliantForwarderInstance.executeTransaction.sendTransactionAsync( + signedFillOrderTx.salt, + signedFillOrderTx.signerAddress, + signedFillOrderTx.data, + signedFillOrderTx.signature, + ), + ); }); // @TODO - greater than 1 balance }); @@ -359,10 +369,18 @@ describe.only(ContractName.BalanceThresholdFilter, () => { // Execute a valid fill const orders = [compliantSignedOrder, compliantSignedOrder2]; const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; - const txReceipt = await erc721TakerBalanceThresholdWrapper.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); + const expectedValidatedAddresseses = [ + compliantSignedOrder.makerAddress, + compliantSignedOrder2.makerAddress, + compliantSignedFillOrderTx.signerAddress, + ]; + await assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); // Check balances const newBalances = await erc20Wrapper.getBalancesAsync(); const cumulativeTakerAssetFillAmount = takerAssetFillAmount.times(2); @@ -396,11 +414,11 @@ describe.only(ContractName.BalanceThresholdFilter, () => { expect(newBalances[compliantMakerAddress2][zrxToken.address]).to.be.bignumber.equal( erc20Balances[compliantMakerAddress2][zrxToken.address].minus(makerFeePaid), ); - // Taker + // 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)), ); @@ -416,29 +434,25 @@ describe.only(ContractName.BalanceThresholdFilter, () => { // Create order set with one non-compliant maker address const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - makerAddress: nonCompliantAddress + makerAddress: nonCompliantAddress, }); const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; // Execute transaction return expectTransactionFailedAsync( - erc721TakerBalanceThresholdWrapper.batchFillOrdersAsync( - orders, - compliantTakerAddress, - {takerAssetFillAmounts} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + 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 + erc721NonCompliantBalanceThresholdWrapper.batchFillOrdersAsync(orders, nonCompliantAddress, { + takerAssetFillAmounts, + }), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold, ); }); }); @@ -453,10 +467,18 @@ describe.only(ContractName.BalanceThresholdFilter, () => { // Execute a valid fill const orders = [compliantSignedOrder, compliantSignedOrder2]; const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; - const txReceipt = await erc721TakerBalanceThresholdWrapper.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); + const expectedValidatedAddresseses = [ + compliantSignedOrder.makerAddress, + compliantSignedOrder2.makerAddress, + compliantSignedFillOrderTx.signerAddress, + ]; + await assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); // Check balances const newBalances = await erc20Wrapper.getBalancesAsync(); const cumulativeTakerAssetFillAmount = takerAssetFillAmount.times(2); @@ -490,11 +512,11 @@ describe.only(ContractName.BalanceThresholdFilter, () => { expect(newBalances[compliantMakerAddress2][zrxToken.address]).to.be.bignumber.equal( erc20Balances[compliantMakerAddress2][zrxToken.address].minus(makerFeePaid), ); - // Taker + // 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)), ); @@ -510,29 +532,25 @@ describe.only(ContractName.BalanceThresholdFilter, () => { // Create order set with one non-compliant maker address const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - makerAddress: nonCompliantAddress + makerAddress: nonCompliantAddress, }); const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; // Execute transaction return expectTransactionFailedAsync( - erc721TakerBalanceThresholdWrapper.batchFillOrdersNoThrowAsync( - orders, - compliantTakerAddress, - {takerAssetFillAmounts} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + 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 + erc721NonCompliantBalanceThresholdWrapper.batchFillOrdersNoThrowAsync(orders, nonCompliantAddress, { + takerAssetFillAmounts, + }), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold, ); }); }); @@ -547,10 +565,18 @@ describe.only(ContractName.BalanceThresholdFilter, () => { // Execute a valid fill const orders = [compliantSignedOrder, compliantSignedOrder2]; const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; - const txReceipt = await erc721TakerBalanceThresholdWrapper.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); + const expectedValidatedAddresseses = [ + compliantSignedOrder.makerAddress, + compliantSignedOrder2.makerAddress, + compliantSignedFillOrderTx.signerAddress, + ]; + await assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); // Check balances const newBalances = await erc20Wrapper.getBalancesAsync(); const cumulativeTakerAssetFillAmount = takerAssetFillAmount.times(2); @@ -584,11 +610,11 @@ describe.only(ContractName.BalanceThresholdFilter, () => { expect(newBalances[compliantMakerAddress2][zrxToken.address]).to.be.bignumber.equal( erc20Balances[compliantMakerAddress2][zrxToken.address].minus(makerFeePaid), ); - // Taker + // 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)), ); @@ -604,29 +630,25 @@ describe.only(ContractName.BalanceThresholdFilter, () => { // Create order set with one non-compliant maker address const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - makerAddress: nonCompliantAddress + makerAddress: nonCompliantAddress, }); const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; // Execute transaction return expectTransactionFailedAsync( - erc721TakerBalanceThresholdWrapper.batchFillOrKillOrdersAsync( - orders, - compliantTakerAddress, - {takerAssetFillAmounts} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + 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 + erc721NonCompliantBalanceThresholdWrapper.batchFillOrKillOrdersAsync(orders, nonCompliantAddress, { + takerAssetFillAmounts, + }), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold, ); }); it('should revert if one takerAssetFillAmount is not fully filled', async () => { @@ -634,12 +656,10 @@ describe.only(ContractName.BalanceThresholdFilter, () => { const orders = [compliantSignedOrder, compliantSignedOrder2]; const takerAssetFillAmounts = [takerAssetFillAmount, tooBigTakerAssetFillAmount]; return expectTransactionFailedAsync( - erc721TakerBalanceThresholdWrapper.batchFillOrKillOrdersAsync( - orders, - compliantTakerAddress, - {takerAssetFillAmounts} - ), - RevertReason.FailedExecution + erc721TakerBalanceThresholdWrapper.batchFillOrKillOrdersAsync(orders, compliantTakerAddress, { + takerAssetFillAmounts, + }), + RevertReason.FailedExecution, ); }); }); @@ -651,10 +671,17 @@ 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 erc721TakerBalanceThresholdWrapper.fillOrderAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount}); + const txReceipt = await erc721TakerBalanceThresholdWrapper.fillOrderAsync( + compliantSignedOrder, + compliantTakerAddress, + { takerAssetFillAmount }, + ); // Assert validated addresses - const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedFillOrderTx.signerAddress]; - assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); + const expectedValidatedAddresseses = [ + compliantSignedOrder.makerAddress, + compliantSignedFillOrderTx.signerAddress, + ]; + await assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); // Check balances const newBalances = await erc20Wrapper.getBalancesAsync(); const makerAssetFillAmount = takerAssetFillAmount @@ -692,26 +719,24 @@ describe.only(ContractName.BalanceThresholdFilter, () => { // Create signed order with non-compliant maker address const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ senderAddress: erc721CompliantForwarderInstance.address, - makerAddress: nonCompliantAddress + makerAddress: nonCompliantAddress, }); // Execute transaction return expectTransactionFailedAsync( erc721TakerBalanceThresholdWrapper.fillOrderAsync( signedOrderWithBadMakerAddress, - compliantTakerAddress, - {takerAssetFillAmount} + compliantTakerAddress, + { takerAssetFillAmount }, ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold, ); }); it('should revert if taker does not meet the balance threshold', async () => { return expectTransactionFailedAsync( - erc721NonCompliantBalanceThresholdWrapper.fillOrderAsync( - compliantSignedOrder, - nonCompliantAddress, - {takerAssetFillAmount} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + erc721NonCompliantBalanceThresholdWrapper.fillOrderAsync(compliantSignedOrder, nonCompliantAddress, { + takerAssetFillAmount, + }), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold, ); }); }); @@ -723,10 +748,17 @@ 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 erc721TakerBalanceThresholdWrapper.fillOrderNoThrowAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount}); + const txReceipt = await erc721TakerBalanceThresholdWrapper.fillOrderNoThrowAsync( + compliantSignedOrder, + compliantTakerAddress, + { takerAssetFillAmount }, + ); // Assert validated addresses - const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedFillOrderTx.signerAddress]; - assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); + const expectedValidatedAddresseses = [ + compliantSignedOrder.makerAddress, + compliantSignedFillOrderTx.signerAddress, + ]; + await assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); // Check balances const newBalances = await erc20Wrapper.getBalancesAsync(); const makerAssetFillAmount = takerAssetFillAmount @@ -764,26 +796,26 @@ describe.only(ContractName.BalanceThresholdFilter, () => { // Create signed order with non-compliant maker address const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ senderAddress: erc721CompliantForwarderInstance.address, - makerAddress: nonCompliantAddress + makerAddress: nonCompliantAddress, }); // Execute transaction return expectTransactionFailedAsync( erc721TakerBalanceThresholdWrapper.fillOrderNoThrowAsync( signedOrderWithBadMakerAddress, - compliantTakerAddress, - {takerAssetFillAmount} + compliantTakerAddress, + { takerAssetFillAmount }, ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold, ); }); it('should revert if taker does not meet the balance threshold', async () => { return expectTransactionFailedAsync( erc721NonCompliantBalanceThresholdWrapper.fillOrderNoThrowAsync( compliantSignedOrder, - nonCompliantAddress, - {takerAssetFillAmount} + nonCompliantAddress, + { takerAssetFillAmount }, ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold, ); }); }); @@ -796,10 +828,17 @@ 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 erc721TakerBalanceThresholdWrapper.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); + const expectedValidatedAddresseses = [ + compliantSignedOrder.makerAddress, + compliantSignedFillOrderTx.signerAddress, + ]; + await assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); // Check balances const newBalances = await erc20Wrapper.getBalancesAsync(); const makerAssetFillAmount = takerAssetFillAmount_ @@ -837,37 +876,35 @@ describe.only(ContractName.BalanceThresholdFilter, () => { // Create signed order with non-compliant maker address const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ senderAddress: erc721CompliantForwarderInstance.address, - makerAddress: nonCompliantAddress + makerAddress: nonCompliantAddress, }); // Execute transaction return expectTransactionFailedAsync( erc721TakerBalanceThresholdWrapper.fillOrKillOrderAsync( signedOrderWithBadMakerAddress, - compliantTakerAddress, - {takerAssetFillAmount} + compliantTakerAddress, + { takerAssetFillAmount }, ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold, ); }); it('should revert if taker does not meet the balance threshold', async () => { return expectTransactionFailedAsync( erc721NonCompliantBalanceThresholdWrapper.fillOrKillOrderAsync( compliantSignedOrder, - nonCompliantAddress, - {takerAssetFillAmount} + nonCompliantAddress, + { takerAssetFillAmount }, ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + 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 + erc721TakerBalanceThresholdWrapper.fillOrKillOrderAsync(compliantSignedOrder, compliantTakerAddress, { + takerAssetFillAmount: tooBigTakerAssetFillAmount, + }), + RevertReason.FailedExecution, ); }); }); @@ -882,10 +919,18 @@ describe.only(ContractName.BalanceThresholdFilter, () => { // Execute a valid fill const orders = [compliantSignedOrder, compliantSignedOrder2]; const cumulativeTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.plus(takerAssetFillAmount); - const txReceipt = await erc721TakerBalanceThresholdWrapper.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); + const expectedValidatedAddresseses = [ + compliantSignedOrder.makerAddress, + compliantSignedOrder2.makerAddress, + compliantSignedFillOrderTx.signerAddress, + ]; + await assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); // Check balances const newBalances = await erc20Wrapper.getBalancesAsync(); const makerAssetFillAmount2 = takerAssetFillAmount @@ -901,10 +946,14 @@ describe.only(ContractName.BalanceThresholdFilter, () => { const cumulativeMakerAssetFillAmount = compliantSignedOrder.makerAssetAmount.plus(makerAssetFillAmount2); // Maker #1 expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(compliantSignedOrder.makerAssetAmount), + erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus( + compliantSignedOrder.makerAssetAmount, + ), ); expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(compliantSignedOrder.takerAssetAmount), + erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add( + compliantSignedOrder.takerAssetAmount, + ), ); expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( erc20Balances[compliantMakerAddress][zrxToken.address].minus(compliantSignedOrder.makerFee), @@ -919,7 +968,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { expect(newBalances[compliantMakerAddress2][zrxToken.address]).to.be.bignumber.equal( erc20Balances[compliantMakerAddress2][zrxToken.address].minus(makerFeePaid2), ); - // Taker + // Taker expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount), ); @@ -931,34 +980,33 @@ describe.only(ContractName.BalanceThresholdFilter, () => { ); // Fee recipient expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].add(compliantSignedOrder.makerFee).add(makerFeePaid2).add(takerFeePaid), + 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 + makerAddress: nonCompliantAddress, }); const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; // Execute transaction return expectTransactionFailedAsync( - erc721TakerBalanceThresholdWrapper.marketSellOrdersAsync( - orders, - compliantTakerAddress, - {takerAssetFillAmount} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + 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 + erc721NonCompliantBalanceThresholdWrapper.marketSellOrdersAsync(orders, nonCompliantAddress, { + takerAssetFillAmount, + }), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold, ); }); }); @@ -973,10 +1021,18 @@ describe.only(ContractName.BalanceThresholdFilter, () => { // Execute a valid fill const orders = [compliantSignedOrder, compliantSignedOrder2]; const cumulativeTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.plus(takerAssetFillAmount); - const txReceipt = await erc721TakerBalanceThresholdWrapper.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); + const expectedValidatedAddresseses = [ + compliantSignedOrder.makerAddress, + compliantSignedOrder2.makerAddress, + compliantSignedFillOrderTx.signerAddress, + ]; + await assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); // Check balances const newBalances = await erc20Wrapper.getBalancesAsync(); const makerAssetFillAmount2 = takerAssetFillAmount @@ -992,10 +1048,14 @@ describe.only(ContractName.BalanceThresholdFilter, () => { const cumulativeMakerAssetFillAmount = compliantSignedOrder.makerAssetAmount.plus(makerAssetFillAmount2); // Maker #1 expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(compliantSignedOrder.makerAssetAmount), + erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus( + compliantSignedOrder.makerAssetAmount, + ), ); expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(compliantSignedOrder.takerAssetAmount), + erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add( + compliantSignedOrder.takerAssetAmount, + ), ); expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( erc20Balances[compliantMakerAddress][zrxToken.address].minus(compliantSignedOrder.makerFee), @@ -1010,7 +1070,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { expect(newBalances[compliantMakerAddress2][zrxToken.address]).to.be.bignumber.equal( erc20Balances[compliantMakerAddress2][zrxToken.address].minus(makerFeePaid2), ); - // Taker + // Taker expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount), ); @@ -1022,34 +1082,33 @@ describe.only(ContractName.BalanceThresholdFilter, () => { ); // Fee recipient expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].add(compliantSignedOrder.makerFee).add(makerFeePaid2).add(takerFeePaid), + 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 + makerAddress: nonCompliantAddress, }); const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; // Execute transaction return expectTransactionFailedAsync( - erc721TakerBalanceThresholdWrapper.marketSellOrdersNoThrowAsync( - orders, - compliantTakerAddress, - {takerAssetFillAmount} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + 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 + erc721NonCompliantBalanceThresholdWrapper.marketSellOrdersNoThrowAsync(orders, nonCompliantAddress, { + takerAssetFillAmount, + }), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold, ); }); }); @@ -1065,13 +1124,21 @@ describe.only(ContractName.BalanceThresholdFilter, () => { const orders = [compliantSignedOrder, compliantSignedOrder2]; const cumulativeTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.plus(takerAssetFillAmount); const makerAssetFillAmount2 = takerAssetFillAmount - .times(compliantSignedOrder.makerAssetAmount) - .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); + .times(compliantSignedOrder.makerAssetAmount) + .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); const cumulativeMakerAssetFillAmount = compliantSignedOrder.makerAssetAmount.plus(makerAssetFillAmount2); - const txReceipt = await erc721TakerBalanceThresholdWrapper.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); + const expectedValidatedAddresseses = [ + compliantSignedOrder.makerAddress, + compliantSignedOrder2.makerAddress, + compliantSignedFillOrderTx.signerAddress, + ]; + await assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); // Check balances const newBalances = await erc20Wrapper.getBalancesAsync(); const makerFeePaid2 = compliantSignedOrder2.makerFee @@ -1083,10 +1150,14 @@ describe.only(ContractName.BalanceThresholdFilter, () => { const takerFeePaid = compliantSignedOrder.takerFee.plus(takerFeePaid2); // Maker #1 expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(compliantSignedOrder.makerAssetAmount), + erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus( + compliantSignedOrder.makerAssetAmount, + ), ); expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(compliantSignedOrder.takerAssetAmount), + erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add( + compliantSignedOrder.takerAssetAmount, + ), ); expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( erc20Balances[compliantMakerAddress][zrxToken.address].minus(compliantSignedOrder.makerFee), @@ -1101,7 +1172,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { expect(newBalances[compliantMakerAddress2][zrxToken.address]).to.be.bignumber.equal( erc20Balances[compliantMakerAddress2][zrxToken.address].minus(makerFeePaid2), ); - // Taker + // Taker expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount), ); @@ -1113,36 +1184,35 @@ describe.only(ContractName.BalanceThresholdFilter, () => { ); // Fee recipient expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].add(compliantSignedOrder.makerFee).add(makerFeePaid2).add(takerFeePaid), + 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 + makerAddress: nonCompliantAddress, }); const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; // Execute transaction const dummyMakerAssetFillAmount = new BigNumber(0); return expectTransactionFailedAsync( - erc721TakerBalanceThresholdWrapper.marketBuyOrdersAsync( - orders, - compliantTakerAddress, - {makerAssetFillAmount: dummyMakerAssetFillAmount} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + 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 + erc721NonCompliantBalanceThresholdWrapper.marketBuyOrdersAsync(orders, nonCompliantAddress, { + makerAssetFillAmount: dummyMakerAssetFillAmount, + }), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold, ); }); }); @@ -1158,13 +1228,21 @@ describe.only(ContractName.BalanceThresholdFilter, () => { const orders = [compliantSignedOrder, compliantSignedOrder2]; const cumulativeTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.plus(takerAssetFillAmount); const makerAssetFillAmount2 = takerAssetFillAmount - .times(compliantSignedOrder.makerAssetAmount) - .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); + .times(compliantSignedOrder.makerAssetAmount) + .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); const cumulativeMakerAssetFillAmount = compliantSignedOrder.makerAssetAmount.plus(makerAssetFillAmount2); - const txReceipt = await erc721TakerBalanceThresholdWrapper.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); + const expectedValidatedAddresseses = [ + compliantSignedOrder.makerAddress, + compliantSignedOrder2.makerAddress, + compliantSignedFillOrderTx.signerAddress, + ]; + await assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); // Check balances const newBalances = await erc20Wrapper.getBalancesAsync(); const makerFeePaid2 = compliantSignedOrder2.makerFee @@ -1176,10 +1254,14 @@ describe.only(ContractName.BalanceThresholdFilter, () => { const takerFeePaid = compliantSignedOrder.takerFee.plus(takerFeePaid2); // Maker #1 expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(compliantSignedOrder.makerAssetAmount), + erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus( + compliantSignedOrder.makerAssetAmount, + ), ); expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(compliantSignedOrder.takerAssetAmount), + erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add( + compliantSignedOrder.takerAssetAmount, + ), ); expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( erc20Balances[compliantMakerAddress][zrxToken.address].minus(compliantSignedOrder.makerFee), @@ -1194,7 +1276,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { expect(newBalances[compliantMakerAddress2][zrxToken.address]).to.be.bignumber.equal( erc20Balances[compliantMakerAddress2][zrxToken.address].minus(makerFeePaid2), ); - // Taker + // Taker expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount), ); @@ -1206,40 +1288,39 @@ describe.only(ContractName.BalanceThresholdFilter, () => { ); // Fee recipient expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].add(compliantSignedOrder.makerFee).add(makerFeePaid2).add(takerFeePaid), + 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 + makerAddress: nonCompliantAddress, }); const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; // Execute transaction const dummyMakerAssetFillAmount = new BigNumber(0); return expectTransactionFailedAsync( - erc721TakerBalanceThresholdWrapper.marketBuyOrdersNoThrowAsync( - orders, - compliantTakerAddress, - {makerAssetFillAmount: dummyMakerAssetFillAmount} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + 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 + erc721NonCompliantBalanceThresholdWrapper.marketBuyOrdersNoThrowAsync(orders, nonCompliantAddress, { + makerAssetFillAmount: dummyMakerAssetFillAmount, + }), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold, ); }); }); - + describe('matchOrders', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); @@ -1255,7 +1336,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(98), 0), makerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 18), takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 18), - feeRecipientAddress: feeRecipientAddress, + feeRecipientAddress, }); const signedOrderRight = await orderFactory2.newSignedOrderAsync({ makerAssetData: assetDataUtils.encodeERC20AssetData(defaultTakerAssetAddress), @@ -1264,7 +1345,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(13), 0), makerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 18), takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 18), - feeRecipientAddress: feeRecipientAddress, + feeRecipientAddress, }); // Compute expected transfer amounts const expectedTransferAmounts = { @@ -1281,85 +1362,127 @@ 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 erc721TakerBalanceThresholdWrapper.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); + const expectedValidatedAddresseses = [ + signedOrderLeft.makerAddress, + signedOrderRight.makerAddress, + compliantTakerAddress, + ]; + await 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)); + ).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)); + ).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)); + ).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)); + ).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), + 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)); + ).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)); + ).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)); + ).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), + 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 + makerAddress: nonCompliantAddress, }); // Execute transaction return expectTransactionFailedAsync( erc721TakerBalanceThresholdWrapper.matchOrdersAsync( compliantSignedOrder, signedOrderWithBadMakerAddress, - compliantTakerAddress, + compliantTakerAddress, ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + 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 + makerAddress: nonCompliantAddress, }); // Execute transaction return expectTransactionFailedAsync( erc721TakerBalanceThresholdWrapper.matchOrdersAsync( signedOrderWithBadMakerAddress, compliantSignedOrder, - compliantTakerAddress, + compliantTakerAddress, ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold, ); }); it('should revert if taker does not meet the balance threshold', async () => { @@ -1367,9 +1490,9 @@ describe.only(ContractName.BalanceThresholdFilter, () => { erc721NonCompliantBalanceThresholdWrapper.matchOrdersAsync( compliantSignedOrder, compliantSignedOrder, - nonCompliantAddress, + nonCompliantAddress, ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold, ); }); }); @@ -1382,30 +1505,44 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); it('Should successfully cancel order if maker meets balance threshold', async () => { // Verify order is not cancelled - const orderInfoBeforeCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) + const orderInfoBeforeCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync( + compliantSignedOrder, + ); expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); // Cancel - const txReceipt = await erc721MakerBalanceThresholdWrapper.cancelOrderAsync(compliantSignedOrder, compliantSignedOrder.makerAddress); + const txReceipt = await erc721MakerBalanceThresholdWrapper.cancelOrderAsync( + compliantSignedOrder, + compliantSignedOrder.makerAddress, + ); // Assert validated addresses const expectedValidatedAddresseses: string[] = []; - assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); + await assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); // Check that order was cancelled - const orderInfoAfterCancelling = await erc721MakerBalanceThresholdWrapper.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 erc721NonCompliantBalanceThresholdWrapper.getOrderInfoAsync(signedOrderWithBadMakerAddress) + const orderInfoBeforeCancelling = await erc721NonCompliantBalanceThresholdWrapper.getOrderInfoAsync( + signedOrderWithBadMakerAddress, + ); expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); // Cancel - const txReceipt = await erc721NonCompliantBalanceThresholdWrapper.cancelOrderAsync(signedOrderWithBadMakerAddress, signedOrderWithBadMakerAddress.makerAddress); + const txReceipt = await erc721NonCompliantBalanceThresholdWrapper.cancelOrderAsync( + signedOrderWithBadMakerAddress, + signedOrderWithBadMakerAddress.makerAddress, + ); // Assert validated addresses const expectedValidatedAddresseses: string[] = []; - assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); + await assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); // Check that order was cancelled - const orderInfoAfterCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync(signedOrderWithBadMakerAddress) + const orderInfoAfterCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync( + signedOrderWithBadMakerAddress, + ); expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED); }); }); @@ -1420,20 +1557,27 @@ describe.only(ContractName.BalanceThresholdFilter, () => { 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) + _.each(compliantSignedOrders, async signedOrder => { + const orderInfoBeforeCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync( + signedOrder, + ); return expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); }); // Cancel - const txReceipt = await erc721MakerBalanceThresholdWrapper.batchCancelOrdersAsync(compliantSignedOrders, compliantSignedOrder.makerAddress); + const txReceipt = await erc721MakerBalanceThresholdWrapper.batchCancelOrdersAsync( + compliantSignedOrders, + compliantSignedOrder.makerAddress, + ); // Assert validated addresses const expectedValidatedAddresseses: string[] = []; - assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); + await assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); // Check that order was cancelled - await _.each(compliantSignedOrders, async (compliantSignedOrder) => { - const orderInfoAfterCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) + _.each(compliantSignedOrders, async signedOrder => { + const orderInfoAfterCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync( + signedOrder, + ); return expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED); }); }); @@ -1443,20 +1587,27 @@ describe.only(ContractName.BalanceThresholdFilter, () => { 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) + _.each(nonCompliantSignedOrders, async signedOrder => { + const orderInfoBeforeCancelling = await erc721NonCompliantBalanceThresholdWrapper.getOrderInfoAsync( + signedOrder, + ); return expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); }); // Cancel - const txReceipt = await erc721NonCompliantBalanceThresholdWrapper.batchCancelOrdersAsync(nonCompliantSignedOrders, nonCompliantAddress); + const txReceipt = await erc721NonCompliantBalanceThresholdWrapper.batchCancelOrdersAsync( + nonCompliantSignedOrders, + nonCompliantAddress, + ); // Assert validated addresses const expectedValidatedAddresseses: string[] = []; - assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); + await assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); // Check that order was cancelled - await _.each(nonCompliantSignedOrders, async (nonCompliantSignedOrder) => { - const orderInfoAfterCancelling = await erc721NonCompliantBalanceThresholdWrapper.getOrderInfoAsync(nonCompliantSignedOrder) + _.each(nonCompliantSignedOrders, async signedOrder => { + const orderInfoAfterCancelling = await erc721NonCompliantBalanceThresholdWrapper.getOrderInfoAsync( + signedOrder, + ); return expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED); }); }); @@ -1469,24 +1620,31 @@ describe.only(ContractName.BalanceThresholdFilter, () => { 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)}), - ]; + 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) + _.each(compliantSignedOrders, async signedOrder => { + const orderInfoBeforeCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync( + signedOrder, + ); return expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); }); // Cancel const cancelOrdersUpToThisSalt = new BigNumber(1); - const txReceipt = await erc721MakerBalanceThresholdWrapper.cancelOrdersUpToAsync(cancelOrdersUpToThisSalt, compliantSignedOrder.makerAddress); + const txReceipt = await erc721MakerBalanceThresholdWrapper.cancelOrdersUpToAsync( + cancelOrdersUpToThisSalt, + compliantSignedOrder.makerAddress, + ); // Assert validated addresses const expectedValidatedAddresseses: string[] = []; - assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); + await assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); // Check that order was cancelled - await _.each(compliantSignedOrders, async (compliantSignedOrder, salt: number) => { - const orderInfoAfterCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) + _.each(compliantSignedOrders, async (signedOrder, salt: number) => { + const orderInfoAfterCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync( + signedOrder, + ); const saltAsBigNumber = new BigNumber(salt); if (saltAsBigNumber.lessThanOrEqualTo(cancelOrdersUpToThisSalt)) { return expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED); @@ -1498,24 +1656,31 @@ describe.only(ContractName.BalanceThresholdFilter, () => { 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)}), - ]; + 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) + _.each(nonCompliantSignedOrders, async signedOrder => { + const orderInfoBeforeCancelling = await erc721NonCompliantBalanceThresholdWrapper.getOrderInfoAsync( + signedOrder, + ); return expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); }); // Cancel const cancelOrdersUpToThisSalt = new BigNumber(1); - const txReceipt = await erc721NonCompliantBalanceThresholdWrapper.cancelOrdersUpToAsync(cancelOrdersUpToThisSalt, nonCompliantAddress); + const txReceipt = await erc721NonCompliantBalanceThresholdWrapper.cancelOrdersUpToAsync( + cancelOrdersUpToThisSalt, + nonCompliantAddress, + ); // Assert validated addresses const expectedValidatedAddresseses: string[] = []; - assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); + await assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); // Check that order was cancelled - await _.each(nonCompliantSignedOrders, async (nonCompliantSignedOrder, salt: number) => { - const orderInfoAfterCancelling = await erc721NonCompliantBalanceThresholdWrapper.getOrderInfoAsync(nonCompliantSignedOrder) + _.each(nonCompliantSignedOrders, async (signedOrder, salt: number) => { + const orderInfoAfterCancelling = await erc721NonCompliantBalanceThresholdWrapper.getOrderInfoAsync( + signedOrder, + ); const saltAsBigNumber = new BigNumber(salt); if (saltAsBigNumber.lessThanOrEqualTo(cancelOrdersUpToThisSalt)) { return expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED); diff --git a/contracts/extensions/test/extensions/forwarder.ts b/contracts/extensions/test/extensions/forwarder.ts index a4afa283e..4027f493d 100644 --- a/contracts/extensions/test/extensions/forwarder.ts +++ b/contracts/extensions/test/extensions/forwarder.ts @@ -61,7 +61,6 @@ describe(ContractName.Forwarder, () => { let wethContract: WETH9Contract; let forwarderWrapper: ForwarderWrapper; let exchangeWrapper: ExchangeWrapper; - let takerPrivateKey: Buffer; let orderWithoutFee: SignedOrder; let orderWithFee: SignedOrder; diff --git a/contracts/extensions/test/utils/balance_threshold_wrapper.ts b/contracts/extensions/test/utils/balance_threshold_wrapper.ts index 63128686c..28a4ef011 100644 --- a/contracts/extensions/test/utils/balance_threshold_wrapper.ts +++ b/contracts/extensions/test/utils/balance_threshold_wrapper.ts @@ -1,16 +1,21 @@ +import { artifacts as protocolArtifacts, ExchangeContract } from '@0x/contracts-protocol'; +import { + FillResults, + formatters, + LogDecoder, + OrderInfo, + orderUtils, + TransactionFactory, +} from '@0x/contracts-test-utils'; +import { artifacts as tokensArtifacts } from '@0x/contracts-tokens'; 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 '@0x/contracts-protocol'; import { BalanceThresholdFilterContract } from '../../generated-wrappers/balance_threshold_filter'; - -import { formatters, LogDecoder, orderUtils, OrderInfo, TransactionFactory } from '@0x/contracts-test-utils'; import { artifacts } from '../../src/artifacts'; -import {artifacts as protocolArtifacts} from '@0x/contracts-protocol'; -import { artifacts as tokensArtifacts } from '@0x/contracts-tokens'; export class BalanceThresholdWrapper { private readonly _balanceThresholdFilter: BalanceThresholdFilterContract; @@ -18,12 +23,21 @@ export class BalanceThresholdWrapper { private readonly _exchange: ExchangeContract; private readonly _web3Wrapper: Web3Wrapper; private readonly _logDecoder: LogDecoder; - constructor(balanceThresholdFilter: BalanceThresholdFilterContract, exchangeContract: ExchangeContract, signerTransactionFactory: TransactionFactory, provider: Provider) { + 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, {... artifacts, ... tokensArtifacts, ... protocolArtifacts} ); + this._logDecoder = new LogDecoder(this._web3Wrapper, { + ...artifacts, + ...tokensArtifacts, + ...protocolArtifacts, + }); } public async fillOrderAsync( signedOrder: SignedOrder, @@ -36,7 +50,7 @@ export class BalanceThresholdWrapper { params.takerAssetFillAmount, params.signature, ); - const txReceipt = this._executeTransaction(data, from); + const txReceipt = this._executeTransactionAsync(data, from); return txReceipt; } public async fillOrKillOrderAsync( @@ -50,7 +64,7 @@ export class BalanceThresholdWrapper { params.takerAssetFillAmount, params.signature, ); - const txReceipt = this._executeTransaction(data, from); + const txReceipt = this._executeTransactionAsync(data, from); return txReceipt; } public async fillOrderNoThrowAsync( @@ -64,7 +78,7 @@ export class BalanceThresholdWrapper { params.takerAssetFillAmount, params.signature, ); - const txReceipt = this._executeTransaction(data, from, opts.gas); + const txReceipt = this._executeTransactionAsync(data, from, opts.gas); return txReceipt; } public async batchFillOrdersAsync( @@ -78,7 +92,7 @@ export class BalanceThresholdWrapper { params.takerAssetFillAmounts, params.signatures, ); - const txReceipt = this._executeTransaction(data, from); + const txReceipt = this._executeTransactionAsync(data, from); return txReceipt; } public async batchFillOrKillOrdersAsync( @@ -92,7 +106,7 @@ export class BalanceThresholdWrapper { params.takerAssetFillAmounts, params.signatures, ); - const txReceipt = this._executeTransaction(data, from); + const txReceipt = this._executeTransactionAsync(data, from); return txReceipt; } public async batchFillOrdersNoThrowAsync( @@ -106,7 +120,7 @@ export class BalanceThresholdWrapper { params.takerAssetFillAmounts, params.signatures, ); - const txReceipt = this._executeTransaction(data, from, opts.gas); + const txReceipt = this._executeTransactionAsync(data, from, opts.gas); return txReceipt; } public async marketSellOrdersAsync( @@ -120,7 +134,7 @@ export class BalanceThresholdWrapper { params.takerAssetFillAmount, params.signatures, ); - const txReceipt = this._executeTransaction(data, from); + const txReceipt = this._executeTransactionAsync(data, from); return txReceipt; } public async marketSellOrdersNoThrowAsync( @@ -134,7 +148,7 @@ export class BalanceThresholdWrapper { params.takerAssetFillAmount, params.signatures, ); - const txReceipt = this._executeTransaction(data, from, opts.gas); + const txReceipt = this._executeTransactionAsync(data, from, opts.gas); return txReceipt; } public async marketBuyOrdersAsync( @@ -148,7 +162,7 @@ export class BalanceThresholdWrapper { params.makerAssetFillAmount, params.signatures, ); - const txReceipt = this._executeTransaction(data, from); + const txReceipt = this._executeTransactionAsync(data, from); return txReceipt; } public async marketBuyOrdersNoThrowAsync( @@ -162,13 +176,13 @@ export class BalanceThresholdWrapper { params.makerAssetFillAmount, params.signatures, ); - const txReceipt = this._executeTransaction(data, from, opts.gas); + const txReceipt = this._executeTransactionAsync(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); + const txReceipt = this._executeTransactionAsync(data, from); return txReceipt; } public async batchCancelOrdersAsync( @@ -177,12 +191,12 @@ export class BalanceThresholdWrapper { ): Promise { const params = formatters.createBatchCancel(orders); const data = this._exchange.batchCancelOrders.getABIEncodedTransactionData(params.orders); - const txReceipt = this._executeTransaction(data, from); + const txReceipt = this._executeTransactionAsync(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); + const txReceipt = this._executeTransactionAsync(data, from); return txReceipt; } public async getTakerAssetFilledAmountAsync(orderHashHex: string): Promise { @@ -198,7 +212,7 @@ export class BalanceThresholdWrapper { return orderEpoch; } public async getOrderInfoAsync(signedOrder: SignedOrder): Promise { - const orderInfo = (await this._exchange.getOrderInfo.callAsync(signedOrder)) as OrderInfo; + const orderInfo = await this._exchange.getOrderInfo.callAsync(signedOrder); return orderInfo; } public async getOrdersInfoAsync(signedOrders: SignedOrder[]): Promise { @@ -215,24 +229,47 @@ export class BalanceThresholdWrapper { params.left, params.right, params.leftSignature, - params.rightSignature + params.rightSignature, ); - const txReceipt = this._executeTransaction(data, from); + const txReceipt = this._executeTransactionAsync(data, from); return txReceipt; } + public async getFillOrderResultsAsync( + signedOrder: SignedOrder, + from: string, + opts: { takerAssetFillAmount?: BigNumber } = {}, + ): Promise { + const params = orderUtils.createFill(signedOrder, opts.takerAssetFillAmount); + const fillResults = await this._exchange.fillOrder.callAsync( + params.order, + params.takerAssetFillAmount, + params.signature, + { from }, + ); + return fillResults; + } + public abiEncodeFillOrder(signedOrder: SignedOrder, opts: { takerAssetFillAmount?: BigNumber } = {}): string { + const params = orderUtils.createFill(signedOrder, opts.takerAssetFillAmount); + const data = this._exchange.fillOrder.getABIEncodedTransactionData( + params.order, + params.takerAssetFillAmount, + params.signature, + ); + return data; + } 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 { + private async _executeTransactionAsync( + abiEncodedExchangeTxData: string, + from: string, + gas?: number, + ): Promise { const signedExchangeTx = this._signerTransactionFactory.newSignedTransaction(abiEncodedExchangeTxData); - const txOpts = _.isUndefined(gas) ? {from} : {from, gas}; + const txOpts = _.isUndefined(gas) ? { from } : { from, gas }; const txHash = await this._balanceThresholdFilter.executeTransaction.sendTransactionAsync( signedExchangeTx.salt, signedExchangeTx.signerAddress, diff --git a/contracts/extensions/tsconfig.json b/contracts/extensions/tsconfig.json index 5d3ebfe74..a303e3f5c 100644 --- a/contracts/extensions/tsconfig.json +++ b/contracts/extensions/tsconfig.json @@ -6,6 +6,10 @@ "resolveJsonModule": true }, "include": ["./src/**/*", "./test/**/*", "./generated-wrappers/**/*"], - "files": ["./generated-artifacts/BalanceThresholdFilter.json", "./generated-artifacts/DutchAuction.json", "./generated-artifacts/Forwarder.json"], + "files": [ + "./generated-artifacts/BalanceThresholdFilter.json", + "./generated-artifacts/DutchAuction.json", + "./generated-artifacts/Forwarder.json" + ], "exclude": ["./deploy/solc/solc_bin"] } 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 v1.2.3 From fc684ad063151f160a5bc584482aabb56df71e00 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 11 Dec 2018 16:01:06 -0800 Subject: solhint fixes --- .../BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol | 8 ++++++-- .../BalanceThresholdFilter/interfaces/IThresholdAsset.sol | 3 ++- .../BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol | 4 +++- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/contracts/extensions/contracts/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol b/contracts/extensions/contracts/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol index 0ad8ccddf..51dbae8f3 100644 --- a/contracts/extensions/contracts/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol +++ b/contracts/extensions/contracts/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol @@ -53,7 +53,7 @@ contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { bytes signedExchangeTransaction, bytes signature ) - external + external { // Validate addresses. validateBalanceThresholdsOrRevert(); @@ -216,6 +216,7 @@ contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { // Step 2/2 Extract addresses to validate based on this selector. // See ../../utils/ExchangeSelectors/ExchangeSelectors.sol for selectors + // solhint-disable no-empty-blocks switch exchangeFunctionSelector case 0x297bb70b00000000000000000000000000000000000000000000000000000000 { recordAddressesForBatchFillVariant() } // batchFillOrders case 0x50dde19000000000000000000000000000000000000000000000000000000000 { recordAddressesForBatchFillVariant() } // batchFillOrdersNoThrow @@ -245,6 +246,7 @@ contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { // 64 -- strlen(INVALID_OR_BLOCKED_EXCHANGE_SELECTOR) rounded up to nearest 32-byte word. revert(0, 132) } + // solhint-enable no-empty-blocks ///// Validate Recorded Addresses ///// @@ -262,7 +264,9 @@ contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { // Validate addresses let thresholdAssetAddress := sload(THRESHOLD_ASSET_slot) let thresholdBalance := sload(THRESHOLD_BALANCE_slot) + // solhint-disable max-line-length for {let addressToValidate := addressesToValidateElementPtr} lt(addressToValidate, addressesToValidateElementEndPtr) {addressToValidate := add(addressToValidate, 0x20)} { + // solhint-enable max-line-length // Construct calldata for `THRESHOLD_ASSET.balanceOf` mstore(freeMemPtr, 0x70a0823100000000000000000000000000000000000000000000000000000000) mstore(add(freeMemPtr, 0x04), mload(addressToValidate)) @@ -316,4 +320,4 @@ contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { ///// If we hit this point then all addresses are valid ///// emit ValidatedAddresses(validatedAddresses); } -} \ No newline at end of file +} diff --git a/contracts/extensions/contracts/BalanceThresholdFilter/interfaces/IThresholdAsset.sol b/contracts/extensions/contracts/BalanceThresholdFilter/interfaces/IThresholdAsset.sol index 61acaba0a..3e424b9f4 100644 --- a/contracts/extensions/contracts/BalanceThresholdFilter/interfaces/IThresholdAsset.sol +++ b/contracts/extensions/contracts/BalanceThresholdFilter/interfaces/IThresholdAsset.sol @@ -18,6 +18,7 @@ pragma solidity 0.4.24; + contract IThresholdAsset { /// @param _owner The address from which the balance will be retrieved @@ -27,4 +28,4 @@ contract IThresholdAsset { view returns (uint256); -} \ No newline at end of file +} diff --git a/contracts/extensions/contracts/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol b/contracts/extensions/contracts/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol index ff0042f58..af7a9453b 100644 --- a/contracts/extensions/contracts/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol +++ b/contracts/extensions/contracts/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol @@ -26,6 +26,7 @@ import "../interfaces/IThresholdAsset.sol"; contract MBalanceThresholdFilterCore { // Points to 0x exchange contract + // solhint-disable var-name-mixedcase IExchange internal EXCHANGE; // The asset that must be held by makers/takers @@ -33,6 +34,7 @@ contract MBalanceThresholdFilterCore { // The minimum balance of `THRESHOLD_ASSET` that must be held by makers/takers uint256 internal THRESHOLD_BALANCE; + // solhint-enable var-name-mixedcase // Addresses that hold at least `THRESHOLD_BALANCE` of `THRESHOLD_ASSET` event ValidatedAddresses ( @@ -78,4 +80,4 @@ contract MBalanceThresholdFilterCore { /// 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 v1.2.3 From dccca95c2c7a3bfcbf88db5f9182919f0e5ebf10 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 11 Dec 2018 16:02:45 -0800 Subject: removed `only` from tests --- contracts/extensions/test/extensions/balance_threshold_filter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/extensions/test/extensions/balance_threshold_filter.ts b/contracts/extensions/test/extensions/balance_threshold_filter.ts index e4eda3354..72227090b 100644 --- a/contracts/extensions/test/extensions/balance_threshold_filter.ts +++ b/contracts/extensions/test/extensions/balance_threshold_filter.ts @@ -46,7 +46,7 @@ interface ValidatedAddressesLog { args: { addresses: string[] }; } -describe.only(ContractName.BalanceThresholdFilter, () => { +describe(ContractName.BalanceThresholdFilter, () => { let compliantMakerAddress: string; let compliantMakerAddress2: string; let owner: string; -- cgit v1.2.3 From d45c13bfa8a85e69bbf4476b22ad48443ae320e7 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 11 Dec 2018 16:03:48 -0800 Subject: removed unnecessary log --- contracts/protocol/test/utils/exchange_wrapper.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/contracts/protocol/test/utils/exchange_wrapper.ts b/contracts/protocol/test/utils/exchange_wrapper.ts index 6106e78ca..cb6dce901 100644 --- a/contracts/protocol/test/utils/exchange_wrapper.ts +++ b/contracts/protocol/test/utils/exchange_wrapper.ts @@ -214,7 +214,6 @@ export class ExchangeWrapper { { from }, ); const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); - console.log(JSON.stringify(tx)); return tx; } public async getTakerAssetFilledAmountAsync(orderHashHex: string): Promise { -- cgit v1.2.3 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 --- contracts/extensions/test/extensions/balance_threshold_filter.ts | 3 +-- packages/contracts/contracts/tokens/README.md | 2 -- 2 files changed, 1 insertion(+), 4 deletions(-) delete mode 100644 packages/contracts/contracts/tokens/README.md diff --git a/contracts/extensions/test/extensions/balance_threshold_filter.ts b/contracts/extensions/test/extensions/balance_threshold_filter.ts index 72227090b..63cb9de31 100644 --- a/contracts/extensions/test/extensions/balance_threshold_filter.ts +++ b/contracts/extensions/test/extensions/balance_threshold_filter.ts @@ -135,7 +135,7 @@ describe(ContractName.BalanceThresholdFilter, () => { // Create proxies const erc20Proxy = await erc20Wrapper.deployProxyAsync(); await erc20Wrapper.setBalancesAndAllowancesAsync(); - // Deploy Exchange congtract + // Deploy Exchange contract exchangeInstance = await ExchangeContract.deployFrom0xArtifactAsync( protocolArtifacts.Exchange, provider, @@ -356,7 +356,6 @@ describe(ContractName.BalanceThresholdFilter, () => { ), ); }); - // @TODO - greater than 1 balance }); describe('batchFillOrders', () => { 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 v1.2.3 From a1266a3341cdc899a9090c90d6cf30b0ceb1db94 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 11 Dec 2018 16:36:56 -0800 Subject: Cleaned up tests for balance threshold filter --- .../test/extensions/balance_threshold_filter.ts | 1097 ++++++++++---------- 1 file changed, 534 insertions(+), 563 deletions(-) diff --git a/contracts/extensions/test/extensions/balance_threshold_filter.ts b/contracts/extensions/test/extensions/balance_threshold_filter.ts index 63cb9de31..ced9d6e25 100644 --- a/contracts/extensions/test/extensions/balance_threshold_filter.ts +++ b/contracts/extensions/test/extensions/balance_threshold_filter.ts @@ -46,13 +46,17 @@ interface ValidatedAddressesLog { args: { addresses: string[] }; } -describe(ContractName.BalanceThresholdFilter, () => { - let compliantMakerAddress: string; - let compliantMakerAddress2: string; +describe.only(ContractName.BalanceThresholdFilter, () => { + 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 validMakerAddress: string; + let validMakerAddress2: string; let owner: string; - let compliantTakerAddress: string; + let validTakerAddress: string; let feeRecipientAddress: string; - let nonCompliantAddress: string; + let invalidAddress: string; let defaultMakerAssetAddress: string; let defaultTakerAssetAddress: string; let zrxAssetData: string; @@ -62,27 +66,21 @@ describe(ContractName.BalanceThresholdFilter, () => { let orderFactory: OrderFactory; let orderFactory2: OrderFactory; - let nonCompliantOrderFactory: OrderFactory; + let invalidOrderFactory: OrderFactory; let erc20Wrapper: ERC20Wrapper; let erc20Balances: ERC20BalancesByOwner; let erc20TakerBalanceThresholdWrapper: BalanceThresholdWrapper; let erc721TakerBalanceThresholdWrapper: BalanceThresholdWrapper; let erc721MakerBalanceThresholdWrapper: BalanceThresholdWrapper; - let erc721NonCompliantBalanceThresholdWrapper: BalanceThresholdWrapper; - - let takerTransactionFactory: TransactionFactory; - let compliantSignedOrder: SignedOrder; - let compliantSignedOrder2: SignedOrder; - let compliantSignedFillOrderTx: SignedTransaction; + let erc721NonValidBalanceThresholdWrapper: BalanceThresholdWrapper; let defaultOrderParams: Partial; + let validSignedOrder: SignedOrder; + let validSignedOrder2: SignedOrder; + let validSignedFillOrderTx: 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 erc721CompliantForwarderInstance: BalanceThresholdFilterContract; - let erc20CompliantForwarderInstance: BalanceThresholdFilterContract; + let erc721BalanceThresholdFilterInstance: BalanceThresholdFilterContract; + let erc20BalanceThresholdFilterInstance: BalanceThresholdFilterContract; const assertValidatedAddressesLog = async ( txReceipt: TransactionReceiptWithDecodedLogs, @@ -107,19 +105,23 @@ describe(ContractName.BalanceThresholdFilter, () => { const accounts = await web3Wrapper.getAvailableAddressesAsync(); const usedAddresses = ([ owner, - compliantMakerAddress, - compliantMakerAddress2, - compliantTakerAddress, + validMakerAddress, + validMakerAddress2, + validTakerAddress, feeRecipientAddress, - nonCompliantAddress, + invalidAddress, ] = accounts); + const takerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(validTakerAddress)]; + const makerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(validMakerAddress)]; + const secondMakerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(validMakerAddress2)]; + const invalidAddressPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(invalidAddress)]; // Create wrappers erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner); - const compliantAddresses = _.cloneDeepWith(usedAddresses); - _.remove(compliantAddresses, (address: string) => { - return address === nonCompliantAddress; + const validAddresses = _.cloneDeepWith(usedAddresses); + _.remove(validAddresses, (address: string) => { + return address === invalidAddress; }); - const erc721Wrapper = new ERC721Wrapper(provider, compliantAddresses, owner); + const erc721Wrapper = new ERC721Wrapper(provider, validAddresses, owner); // Deploy ERC20 tokens const numDummyErc20ToDeploy = 4; let erc20TokenA: DummyERC20TokenContract; @@ -148,12 +150,13 @@ describe(ContractName.BalanceThresholdFilter, () => { await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(exchangeInstance.address, { from: owner, }); - // Deploy Compliant Forwarder + // Deploy Balance Threshold Filters + // One uses an ERC721 token as its balance threshold asset; the other uses an ERC20 const erc721alanceThreshold = new BigNumber(1); await erc721Wrapper.deployProxyAsync(); const [erc721BalanceThresholdAsset] = await erc721Wrapper.deployDummyTokensAsync(); await erc721Wrapper.setBalancesAndAllowancesAsync(); - erc721CompliantForwarderInstance = await BalanceThresholdFilterContract.deployFrom0xArtifactAsync( + erc721BalanceThresholdFilterInstance = await BalanceThresholdFilterContract.deployFrom0xArtifactAsync( artifacts.BalanceThresholdFilter, provider, txDefaults, @@ -162,7 +165,7 @@ describe(ContractName.BalanceThresholdFilter, () => { erc721alanceThreshold, ); const erc20BalanceThreshold = Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 10); - erc20CompliantForwarderInstance = await BalanceThresholdFilterContract.deployFrom0xArtifactAsync( + erc20BalanceThresholdFilterInstance = await BalanceThresholdFilterContract.deployFrom0xArtifactAsync( artifacts.BalanceThresholdFilter, provider, txDefaults, @@ -170,7 +173,6 @@ describe(ContractName.BalanceThresholdFilter, () => { erc20BalanceThresholdAsset.address, erc20BalanceThreshold, ); - // Default order parameters defaultOrderParams = { exchangeAddress: exchangeInstance.address, @@ -181,68 +183,51 @@ describe(ContractName.BalanceThresholdFilter, () => { takerAssetAmount, makerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), DECIMALS_DEFAULT), takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(150), DECIMALS_DEFAULT), - senderAddress: erc721CompliantForwarderInstance.address, + senderAddress: erc721BalanceThresholdFilterInstance.address, }; + // Create two order factories with valid makers (who meet the threshold balance), and + // one factory for an invalid address (that does not meet the threshold balance) + // Valid order factory #1 const defaultOrderParams1 = { - makerAddress: compliantMakerAddress, + makerAddress: validMakerAddress, ...defaultOrderParams, }; - const makerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantMakerAddress)]; - takerTransactionFactory = new TransactionFactory(makerPrivateKey, exchangeInstance.address); orderFactory = new OrderFactory(makerPrivateKey, defaultOrderParams1); + // Valid order factory #2 const defaultOrderParams2 = { - makerAddress: compliantMakerAddress2, + makerAddress: validMakerAddress2, ...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, + // Invalid order factory + const defaultNonValidOrderParams = { + makerAddress: invalidAddress, ...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, - ); - + invalidOrderFactory = new OrderFactory(invalidAddressPrivateKey, defaultNonValidOrderParams); + // Create Balance Thresold Wrappers erc20TakerBalanceThresholdWrapper = new BalanceThresholdWrapper( - erc20CompliantForwarderInstance, + erc20BalanceThresholdFilterInstance, exchangeInstance, new TransactionFactory(takerPrivateKey, exchangeInstance.address), provider, ); erc721TakerBalanceThresholdWrapper = new BalanceThresholdWrapper( - erc721CompliantForwarderInstance, + erc721BalanceThresholdFilterInstance, exchangeInstance, new TransactionFactory(takerPrivateKey, exchangeInstance.address), provider, ); erc721MakerBalanceThresholdWrapper = new BalanceThresholdWrapper( - erc721CompliantForwarderInstance, + erc721BalanceThresholdFilterInstance, exchangeInstance, new TransactionFactory(makerPrivateKey, exchangeInstance.address), provider, ); - erc721NonCompliantBalanceThresholdWrapper = new BalanceThresholdWrapper( - erc721CompliantForwarderInstance, + erc721NonValidBalanceThresholdWrapper = new BalanceThresholdWrapper( + erc721BalanceThresholdFilterInstance, exchangeInstance, - new TransactionFactory(nonCompliantPrivateKey, exchangeInstance.address), + new TransactionFactory(invalidAddressPrivateKey, exchangeInstance.address), provider, ); }); @@ -256,104 +241,90 @@ describe(ContractName.BalanceThresholdFilter, () => { describe('General Sanity Checks', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); - compliantSignedOrder = await orderFactory.newSignedOrderAsync(); - compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); + validSignedOrder = await orderFactory.newSignedOrderAsync(); + validSignedOrder2 = await orderFactory2.newSignedOrderAsync(); }); it('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({ + const validSignedOrderERC20Sender = await orderFactory.newSignedOrderAsync({ ...defaultOrderParams, - makerAddress: compliantMakerAddress, + makerAddress: validMakerAddress, senderAddress: erc20TakerBalanceThresholdWrapper.getBalanceThresholdAddress(), }); // Execute a valid fill const txReceipt = await erc20TakerBalanceThresholdWrapper.fillOrderAsync( - compliantSignedOrderERC20Sender, - compliantTakerAddress, + validSignedOrderERC20Sender, + validTakerAddress, { takerAssetFillAmount }, ); // Assert validated addresses const expectedValidatedAddresseses = [ - compliantSignedOrder.makerAddress, - compliantSignedFillOrderTx.signerAddress, + validSignedOrder.makerAddress, + validTakerAddress, ]; await assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); // Check balances const newBalances = await erc20Wrapper.getBalancesAsync(); const makerAssetFillAmount = takerAssetFillAmount - .times(compliantSignedOrder.makerAssetAmount) - .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); - const makerFeePaid = compliantSignedOrder.makerFee + .times(validSignedOrder.makerAssetAmount) + .dividedToIntegerBy(validSignedOrder.takerAssetAmount); + const makerFeePaid = validSignedOrder.makerFee .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); - const takerFeePaid = compliantSignedOrder.takerFee + .dividedToIntegerBy(validSignedOrder.makerAssetAmount); + const takerFeePaid = validSignedOrder.takerFee .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); - expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), + .dividedToIntegerBy(validSignedOrder.makerAssetAmount); + expect(newBalances[validMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[validMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), ); - expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount), + expect(newBalances[validMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[validMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount), ); - expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), + expect(newBalances[validMakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[validMakerAddress][zrxToken.address].minus(makerFeePaid), ); - expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount), + expect(newBalances[validTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[validTakerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount), ); - expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount), + expect(newBalances[validTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[validTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount), ); - expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), + expect(newBalances[validTakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[validTakerAddress][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 () => { + it('should revert if the Exchange transaction function is not 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, + const salt = new BigNumber(0); + const badSelectorHex = '0x00000000'; + const signatureHex = '0x'; + // Call valid forwarder + return expectTransactionFailedAsync( + erc721BalanceThresholdFilterInstance.executeTransaction.sendTransactionAsync( + salt, + validTakerAddress, + badSelectorHex, + signatureHex, ), + RevertReason.InvalidOrBlockedExchangeSelector ); }); - it('should revert if senderAddress is not set to the compliant forwarding contract', async () => { + it('should revert if senderAddress is not set to the valid 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, + // Call valid forwarder + return expectTransactionFailedAsync( + erc721TakerBalanceThresholdWrapper.fillOrderAsync( + signedOrderWithBadSenderAddress, + validTakerAddress, + { takerAssetFillAmount }, ), + RevertReason.FailedExecution ); }); }); @@ -361,68 +332,68 @@ describe(ContractName.BalanceThresholdFilter, () => { describe('batchFillOrders', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); - compliantSignedOrder = await orderFactory.newSignedOrderAsync(); - compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); + validSignedOrder = await orderFactory.newSignedOrderAsync(); + validSignedOrder2 = 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 orders = [validSignedOrder, validSignedOrder2]; const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; const txReceipt = await erc721TakerBalanceThresholdWrapper.batchFillOrdersAsync( orders, - compliantTakerAddress, + validTakerAddress, { takerAssetFillAmounts }, ); // Assert validated addresses const expectedValidatedAddresseses = [ - compliantSignedOrder.makerAddress, - compliantSignedOrder2.makerAddress, - compliantSignedFillOrderTx.signerAddress, + validSignedOrder.makerAddress, + validSignedOrder2.makerAddress, + validTakerAddress, ]; await 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(validSignedOrder.makerAssetAmount) + .dividedToIntegerBy(validSignedOrder.takerAssetAmount); + const makerFeePaid = validSignedOrder.makerFee .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); - const takerFeePaid = compliantSignedOrder.takerFee + .dividedToIntegerBy(validSignedOrder.makerAssetAmount); + const takerFeePaid = validSignedOrder.takerFee .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount) + .dividedToIntegerBy(validSignedOrder.makerAssetAmount) .times(2); // Maker #1 - expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), + expect(newBalances[validMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[validMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), ); - expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount), + expect(newBalances[validMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[validMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount), ); - expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), + expect(newBalances[validMakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[validMakerAddress][zrxToken.address].minus(makerFeePaid), ); // Maker #2 - expect(newBalances[compliantMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount), + expect(newBalances[validMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[validMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount), ); - expect(newBalances[compliantMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount), + expect(newBalances[validMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[validMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount), ); - expect(newBalances[compliantMakerAddress2][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][zrxToken.address].minus(makerFeePaid), + expect(newBalances[validMakerAddress2][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[validMakerAddress2][zrxToken.address].minus(makerFeePaid), ); // Taker - expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount), + expect(newBalances[validTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[validTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount), ); - expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount.times(2)), + expect(newBalances[validTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[validTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount.times(2)), ); - expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), + expect(newBalances[validTakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[validTakerAddress][zrxToken.address].minus(takerFeePaid), ); // Fee recipient expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( @@ -430,25 +401,25 @@ describe(ContractName.BalanceThresholdFilter, () => { ); }); it('should revert if one maker does not meet the balance threshold', async () => { - // Create order set with one non-compliant maker address + // Create order set with one non-valid maker address const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - makerAddress: nonCompliantAddress, + makerAddress: invalidAddress, }); - const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; + const orders = [validSignedOrder, signedOrderWithBadMakerAddress]; // Execute transaction return expectTransactionFailedAsync( - erc721TakerBalanceThresholdWrapper.batchFillOrdersAsync(orders, compliantTakerAddress, { + erc721TakerBalanceThresholdWrapper.batchFillOrdersAsync(orders, validTakerAddress, { takerAssetFillAmounts, }), RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold, ); }); it('should revert if taker does not meet the balance threshold', async () => { - const orders = [compliantSignedOrder, compliantSignedOrder2]; + const orders = [validSignedOrder, validSignedOrder2]; const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; return expectTransactionFailedAsync( - erc721NonCompliantBalanceThresholdWrapper.batchFillOrdersAsync(orders, nonCompliantAddress, { + erc721NonValidBalanceThresholdWrapper.batchFillOrdersAsync(orders, invalidAddress, { takerAssetFillAmounts, }), RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold, @@ -459,68 +430,68 @@ describe(ContractName.BalanceThresholdFilter, () => { describe('batchFillOrdersNoThrow', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); - compliantSignedOrder = await orderFactory.newSignedOrderAsync(); - compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); + validSignedOrder = await orderFactory.newSignedOrderAsync(); + validSignedOrder2 = 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 orders = [validSignedOrder, validSignedOrder2]; const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; const txReceipt = await erc721TakerBalanceThresholdWrapper.batchFillOrdersNoThrowAsync( orders, - compliantTakerAddress, + validTakerAddress, { takerAssetFillAmounts }, ); // Assert validated addresses const expectedValidatedAddresseses = [ - compliantSignedOrder.makerAddress, - compliantSignedOrder2.makerAddress, - compliantSignedFillOrderTx.signerAddress, + validSignedOrder.makerAddress, + validSignedOrder2.makerAddress, + validTakerAddress, ]; await 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(validSignedOrder.makerAssetAmount) + .dividedToIntegerBy(validSignedOrder.takerAssetAmount); + const makerFeePaid = validSignedOrder.makerFee .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); - const takerFeePaid = compliantSignedOrder.takerFee + .dividedToIntegerBy(validSignedOrder.makerAssetAmount); + const takerFeePaid = validSignedOrder.takerFee .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount) + .dividedToIntegerBy(validSignedOrder.makerAssetAmount) .times(2); // Maker #1 - expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), + expect(newBalances[validMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[validMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), ); - expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount), + expect(newBalances[validMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[validMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount), ); - expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), + expect(newBalances[validMakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[validMakerAddress][zrxToken.address].minus(makerFeePaid), ); // Maker #2 - expect(newBalances[compliantMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount), + expect(newBalances[validMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[validMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount), ); - expect(newBalances[compliantMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount), + expect(newBalances[validMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[validMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount), ); - expect(newBalances[compliantMakerAddress2][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][zrxToken.address].minus(makerFeePaid), + expect(newBalances[validMakerAddress2][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[validMakerAddress2][zrxToken.address].minus(makerFeePaid), ); // Taker - expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount), + expect(newBalances[validTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[validTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount), ); - expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount.times(2)), + expect(newBalances[validTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[validTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount.times(2)), ); - expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), + expect(newBalances[validTakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[validTakerAddress][zrxToken.address].minus(takerFeePaid), ); // Fee recipient expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( @@ -528,25 +499,25 @@ describe(ContractName.BalanceThresholdFilter, () => { ); }); it('should revert if one maker does not meet the balance threshold', async () => { - // Create order set with one non-compliant maker address + // Create order set with one non-valid maker address const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - makerAddress: nonCompliantAddress, + makerAddress: invalidAddress, }); - const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; + const orders = [validSignedOrder, signedOrderWithBadMakerAddress]; // Execute transaction return expectTransactionFailedAsync( - erc721TakerBalanceThresholdWrapper.batchFillOrdersNoThrowAsync(orders, compliantTakerAddress, { + erc721TakerBalanceThresholdWrapper.batchFillOrdersNoThrowAsync(orders, validTakerAddress, { takerAssetFillAmounts, }), RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold, ); }); it('should revert if taker does not meet the balance threshold', async () => { - const orders = [compliantSignedOrder, compliantSignedOrder2]; + const orders = [validSignedOrder, validSignedOrder2]; const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; return expectTransactionFailedAsync( - erc721NonCompliantBalanceThresholdWrapper.batchFillOrdersNoThrowAsync(orders, nonCompliantAddress, { + erc721NonValidBalanceThresholdWrapper.batchFillOrdersNoThrowAsync(orders, invalidAddress, { takerAssetFillAmounts, }), RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold, @@ -557,68 +528,68 @@ describe(ContractName.BalanceThresholdFilter, () => { describe('batchFillOrKillOrders', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); - compliantSignedOrder = await orderFactory.newSignedOrderAsync(); - compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); + validSignedOrder = await orderFactory.newSignedOrderAsync(); + validSignedOrder2 = 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 orders = [validSignedOrder, validSignedOrder2]; const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; const txReceipt = await erc721TakerBalanceThresholdWrapper.batchFillOrKillOrdersAsync( orders, - compliantTakerAddress, + validTakerAddress, { takerAssetFillAmounts }, ); // Assert validated addresses const expectedValidatedAddresseses = [ - compliantSignedOrder.makerAddress, - compliantSignedOrder2.makerAddress, - compliantSignedFillOrderTx.signerAddress, + validSignedOrder.makerAddress, + validSignedOrder2.makerAddress, + validTakerAddress, ]; await 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(validSignedOrder.makerAssetAmount) + .dividedToIntegerBy(validSignedOrder.takerAssetAmount); + const makerFeePaid = validSignedOrder.makerFee .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); - const takerFeePaid = compliantSignedOrder.takerFee + .dividedToIntegerBy(validSignedOrder.makerAssetAmount); + const takerFeePaid = validSignedOrder.takerFee .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount) + .dividedToIntegerBy(validSignedOrder.makerAssetAmount) .times(2); // Maker #1 - expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), + expect(newBalances[validMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[validMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), ); - expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount), + expect(newBalances[validMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[validMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount), ); - expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), + expect(newBalances[validMakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[validMakerAddress][zrxToken.address].minus(makerFeePaid), ); // Maker #2 - expect(newBalances[compliantMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount), + expect(newBalances[validMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[validMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount), ); - expect(newBalances[compliantMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount), + expect(newBalances[validMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[validMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount), ); - expect(newBalances[compliantMakerAddress2][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][zrxToken.address].minus(makerFeePaid), + expect(newBalances[validMakerAddress2][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[validMakerAddress2][zrxToken.address].minus(makerFeePaid), ); // Taker - expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount), + expect(newBalances[validTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[validTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount), ); - expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount.times(2)), + expect(newBalances[validTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[validTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount.times(2)), ); - expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), + expect(newBalances[validTakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[validTakerAddress][zrxToken.address].minus(takerFeePaid), ); // Fee recipient expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( @@ -626,36 +597,36 @@ describe(ContractName.BalanceThresholdFilter, () => { ); }); it('should revert if one maker does not meet the balance threshold', async () => { - // Create order set with one non-compliant maker address + // Create order set with one non-valid maker address const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - makerAddress: nonCompliantAddress, + makerAddress: invalidAddress, }); - const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; + const orders = [validSignedOrder, signedOrderWithBadMakerAddress]; // Execute transaction return expectTransactionFailedAsync( - erc721TakerBalanceThresholdWrapper.batchFillOrKillOrdersAsync(orders, compliantTakerAddress, { + erc721TakerBalanceThresholdWrapper.batchFillOrKillOrdersAsync(orders, validTakerAddress, { takerAssetFillAmounts, }), RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold, ); }); it('should revert if taker does not meet the balance threshold', async () => { - const orders = [compliantSignedOrder, compliantSignedOrder2]; + const orders = [validSignedOrder, validSignedOrder2]; const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; return expectTransactionFailedAsync( - erc721NonCompliantBalanceThresholdWrapper.batchFillOrKillOrdersAsync(orders, nonCompliantAddress, { + erc721NonValidBalanceThresholdWrapper.batchFillOrKillOrdersAsync(orders, invalidAddress, { 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 tooBigTakerAssetFillAmount = validSignedOrder.takerAssetAmount.times(2); + const orders = [validSignedOrder, validSignedOrder2]; const takerAssetFillAmounts = [takerAssetFillAmount, tooBigTakerAssetFillAmount]; return expectTransactionFailedAsync( - erc721TakerBalanceThresholdWrapper.batchFillOrKillOrdersAsync(orders, compliantTakerAddress, { + erc721TakerBalanceThresholdWrapper.batchFillOrKillOrdersAsync(orders, validTakerAddress, { takerAssetFillAmounts, }), RevertReason.FailedExecution, @@ -666,65 +637,65 @@ describe(ContractName.BalanceThresholdFilter, () => { describe('fillOrder', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); - compliantSignedOrder = await orderFactory.newSignedOrderAsync(); + validSignedOrder = 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, + validSignedOrder, + validTakerAddress, { takerAssetFillAmount }, ); // Assert validated addresses const expectedValidatedAddresseses = [ - compliantSignedOrder.makerAddress, - compliantSignedFillOrderTx.signerAddress, + validSignedOrder.makerAddress, + validTakerAddress, ]; await assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); // Check balances const newBalances = await erc20Wrapper.getBalancesAsync(); const makerAssetFillAmount = takerAssetFillAmount - .times(compliantSignedOrder.makerAssetAmount) - .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); - const makerFeePaid = compliantSignedOrder.makerFee + .times(validSignedOrder.makerAssetAmount) + .dividedToIntegerBy(validSignedOrder.takerAssetAmount); + const makerFeePaid = validSignedOrder.makerFee .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); - const takerFeePaid = compliantSignedOrder.takerFee + .dividedToIntegerBy(validSignedOrder.makerAssetAmount); + const takerFeePaid = validSignedOrder.takerFee .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); - expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), + .dividedToIntegerBy(validSignedOrder.makerAssetAmount); + expect(newBalances[validMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[validMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), ); - expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount), + expect(newBalances[validMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[validMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount), ); - expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), + expect(newBalances[validMakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[validMakerAddress][zrxToken.address].minus(makerFeePaid), ); - expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount), + expect(newBalances[validTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[validTakerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount), ); - expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount), + expect(newBalances[validTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[validTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount), ); - expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), + expect(newBalances[validTakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[validTakerAddress][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 + // Create signed order with non-valid maker address const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - senderAddress: erc721CompliantForwarderInstance.address, - makerAddress: nonCompliantAddress, + senderAddress: erc721BalanceThresholdFilterInstance.address, + makerAddress: invalidAddress, }); // Execute transaction return expectTransactionFailedAsync( erc721TakerBalanceThresholdWrapper.fillOrderAsync( signedOrderWithBadMakerAddress, - compliantTakerAddress, + validTakerAddress, { takerAssetFillAmount }, ), RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold, @@ -732,7 +703,7 @@ describe(ContractName.BalanceThresholdFilter, () => { }); it('should revert if taker does not meet the balance threshold', async () => { return expectTransactionFailedAsync( - erc721NonCompliantBalanceThresholdWrapper.fillOrderAsync(compliantSignedOrder, nonCompliantAddress, { + erc721NonValidBalanceThresholdWrapper.fillOrderAsync(validSignedOrder, invalidAddress, { takerAssetFillAmount, }), RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold, @@ -743,65 +714,65 @@ describe(ContractName.BalanceThresholdFilter, () => { describe('fillOrderNoThrow', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); - compliantSignedOrder = await orderFactory.newSignedOrderAsync(); + validSignedOrder = 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, + validSignedOrder, + validTakerAddress, { takerAssetFillAmount }, ); // Assert validated addresses const expectedValidatedAddresseses = [ - compliantSignedOrder.makerAddress, - compliantSignedFillOrderTx.signerAddress, + validSignedOrder.makerAddress, + validTakerAddress, ]; await assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); // Check balances const newBalances = await erc20Wrapper.getBalancesAsync(); const makerAssetFillAmount = takerAssetFillAmount - .times(compliantSignedOrder.makerAssetAmount) - .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); - const makerFeePaid = compliantSignedOrder.makerFee + .times(validSignedOrder.makerAssetAmount) + .dividedToIntegerBy(validSignedOrder.takerAssetAmount); + const makerFeePaid = validSignedOrder.makerFee .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); - const takerFeePaid = compliantSignedOrder.takerFee + .dividedToIntegerBy(validSignedOrder.makerAssetAmount); + const takerFeePaid = validSignedOrder.takerFee .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); - expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), + .dividedToIntegerBy(validSignedOrder.makerAssetAmount); + expect(newBalances[validMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[validMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), ); - expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount), + expect(newBalances[validMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[validMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount), ); - expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), + expect(newBalances[validMakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[validMakerAddress][zrxToken.address].minus(makerFeePaid), ); - expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount), + expect(newBalances[validTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[validTakerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount), ); - expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount), + expect(newBalances[validTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[validTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount), ); - expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), + expect(newBalances[validTakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[validTakerAddress][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 + // Create signed order with non-valid maker address const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - senderAddress: erc721CompliantForwarderInstance.address, - makerAddress: nonCompliantAddress, + senderAddress: erc721BalanceThresholdFilterInstance.address, + makerAddress: invalidAddress, }); // Execute transaction return expectTransactionFailedAsync( erc721TakerBalanceThresholdWrapper.fillOrderNoThrowAsync( signedOrderWithBadMakerAddress, - compliantTakerAddress, + validTakerAddress, { takerAssetFillAmount }, ), RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold, @@ -809,9 +780,9 @@ describe(ContractName.BalanceThresholdFilter, () => { }); it('should revert if taker does not meet the balance threshold', async () => { return expectTransactionFailedAsync( - erc721NonCompliantBalanceThresholdWrapper.fillOrderNoThrowAsync( - compliantSignedOrder, - nonCompliantAddress, + erc721NonValidBalanceThresholdWrapper.fillOrderNoThrowAsync( + validSignedOrder, + invalidAddress, { takerAssetFillAmount }, ), RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold, @@ -822,66 +793,66 @@ describe(ContractName.BalanceThresholdFilter, () => { describe('fillOrKillOrder', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); - compliantSignedOrder = await orderFactory.newSignedOrderAsync(); + validSignedOrder = 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 takerAssetFillAmount_ = validSignedOrder.takerAssetAmount; const txReceipt = await erc721TakerBalanceThresholdWrapper.fillOrKillOrderAsync( - compliantSignedOrder, - compliantTakerAddress, + validSignedOrder, + validTakerAddress, { takerAssetFillAmount: takerAssetFillAmount_ }, ); // Assert validated addresses const expectedValidatedAddresseses = [ - compliantSignedOrder.makerAddress, - compliantSignedFillOrderTx.signerAddress, + validSignedOrder.makerAddress, + validTakerAddress, ]; await assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); // Check balances const newBalances = await erc20Wrapper.getBalancesAsync(); const makerAssetFillAmount = takerAssetFillAmount_ - .times(compliantSignedOrder.makerAssetAmount) - .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); - const makerFeePaid = compliantSignedOrder.makerFee + .times(validSignedOrder.makerAssetAmount) + .dividedToIntegerBy(validSignedOrder.takerAssetAmount); + const makerFeePaid = validSignedOrder.makerFee .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); - const takerFeePaid = compliantSignedOrder.takerFee + .dividedToIntegerBy(validSignedOrder.makerAssetAmount); + const takerFeePaid = validSignedOrder.takerFee .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); - expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), + .dividedToIntegerBy(validSignedOrder.makerAssetAmount); + expect(newBalances[validMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[validMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), ); - expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount_), + expect(newBalances[validMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[validMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount_), ); - expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), + expect(newBalances[validMakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[validMakerAddress][zrxToken.address].minus(makerFeePaid), ); - expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount_), + expect(newBalances[validTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[validTakerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount_), ); - expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount), + expect(newBalances[validTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[validTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount), ); - expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), + expect(newBalances[validTakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[validTakerAddress][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 + // Create signed order with non-valid maker address const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - senderAddress: erc721CompliantForwarderInstance.address, - makerAddress: nonCompliantAddress, + senderAddress: erc721BalanceThresholdFilterInstance.address, + makerAddress: invalidAddress, }); // Execute transaction return expectTransactionFailedAsync( erc721TakerBalanceThresholdWrapper.fillOrKillOrderAsync( signedOrderWithBadMakerAddress, - compliantTakerAddress, + validTakerAddress, { takerAssetFillAmount }, ), RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold, @@ -889,18 +860,18 @@ describe(ContractName.BalanceThresholdFilter, () => { }); it('should revert if taker does not meet the balance threshold', async () => { return expectTransactionFailedAsync( - erc721NonCompliantBalanceThresholdWrapper.fillOrKillOrderAsync( - compliantSignedOrder, - nonCompliantAddress, + erc721NonValidBalanceThresholdWrapper.fillOrKillOrderAsync( + validSignedOrder, + invalidAddress, { takerAssetFillAmount }, ), RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold, ); }); it('should revert if takerAssetFillAmount is not fully filled', async () => { - const tooBigTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.times(2); + const tooBigTakerAssetFillAmount = validSignedOrder.takerAssetAmount.times(2); return expectTransactionFailedAsync( - erc721TakerBalanceThresholdWrapper.fillOrKillOrderAsync(compliantSignedOrder, compliantTakerAddress, { + erc721TakerBalanceThresholdWrapper.fillOrKillOrderAsync(validSignedOrder, validTakerAddress, { takerAssetFillAmount: tooBigTakerAssetFillAmount, }), RevertReason.FailedExecution, @@ -911,98 +882,98 @@ describe(ContractName.BalanceThresholdFilter, () => { describe('marketSellOrders', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); - compliantSignedOrder = await orderFactory.newSignedOrderAsync(); - compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); + validSignedOrder = await orderFactory.newSignedOrderAsync(); + validSignedOrder2 = 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 orders = [validSignedOrder, validSignedOrder2]; + const cumulativeTakerAssetFillAmount = validSignedOrder.takerAssetAmount.plus(takerAssetFillAmount); const txReceipt = await erc721TakerBalanceThresholdWrapper.marketSellOrdersAsync( orders, - compliantTakerAddress, + validTakerAddress, { takerAssetFillAmount: cumulativeTakerAssetFillAmount }, ); // Assert validated addresses const expectedValidatedAddresseses = [ - compliantSignedOrder.makerAddress, - compliantSignedOrder2.makerAddress, - compliantSignedFillOrderTx.signerAddress, + validSignedOrder.makerAddress, + validSignedOrder2.makerAddress, + validTakerAddress, ]; await assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); // Check balances const newBalances = await erc20Wrapper.getBalancesAsync(); const makerAssetFillAmount2 = takerAssetFillAmount - .times(compliantSignedOrder.makerAssetAmount) - .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); - const makerFeePaid2 = compliantSignedOrder2.makerFee + .times(validSignedOrder.makerAssetAmount) + .dividedToIntegerBy(validSignedOrder.takerAssetAmount); + const makerFeePaid2 = validSignedOrder2.makerFee .times(makerAssetFillAmount2) - .dividedToIntegerBy(compliantSignedOrder2.makerAssetAmount); - const takerFeePaid2 = compliantSignedOrder2.takerFee + .dividedToIntegerBy(validSignedOrder2.makerAssetAmount); + const takerFeePaid2 = validSignedOrder2.takerFee .times(makerAssetFillAmount2) - .dividedToIntegerBy(compliantSignedOrder2.makerAssetAmount); - const takerFeePaid = compliantSignedOrder.takerFee.plus(takerFeePaid2); - const cumulativeMakerAssetFillAmount = compliantSignedOrder.makerAssetAmount.plus(makerAssetFillAmount2); + .dividedToIntegerBy(validSignedOrder2.makerAssetAmount); + const takerFeePaid = validSignedOrder.takerFee.plus(takerFeePaid2); + const cumulativeMakerAssetFillAmount = validSignedOrder.makerAssetAmount.plus(makerAssetFillAmount2); // Maker #1 - expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus( - compliantSignedOrder.makerAssetAmount, + expect(newBalances[validMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[validMakerAddress][defaultMakerAssetAddress].minus( + validSignedOrder.makerAssetAmount, ), ); - expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add( - compliantSignedOrder.takerAssetAmount, + expect(newBalances[validMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[validMakerAddress][defaultTakerAssetAddress].add( + validSignedOrder.takerAssetAmount, ), ); - expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][zrxToken.address].minus(compliantSignedOrder.makerFee), + expect(newBalances[validMakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[validMakerAddress][zrxToken.address].minus(validSignedOrder.makerFee), ); // Maker #2 - expect(newBalances[compliantMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount2), + expect(newBalances[validMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[validMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount2), ); - expect(newBalances[compliantMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount), + expect(newBalances[validMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[validMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount), ); - expect(newBalances[compliantMakerAddress2][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][zrxToken.address].minus(makerFeePaid2), + expect(newBalances[validMakerAddress2][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[validMakerAddress2][zrxToken.address].minus(makerFeePaid2), ); // Taker - expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount), + expect(newBalances[validTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[validTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount), ); - expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(cumulativeMakerAssetFillAmount), + expect(newBalances[validTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[validTakerAddress][defaultMakerAssetAddress].add(cumulativeMakerAssetFillAmount), ); - expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), + expect(newBalances[validTakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[validTakerAddress][zrxToken.address].minus(takerFeePaid), ); // Fee recipient expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( erc20Balances[feeRecipientAddress][zrxToken.address] - .add(compliantSignedOrder.makerFee) + .add(validSignedOrder.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 + // Create order set with one non-valid maker address const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - makerAddress: nonCompliantAddress, + makerAddress: invalidAddress, }); - const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; + const orders = [validSignedOrder, signedOrderWithBadMakerAddress]; // Execute transaction return expectTransactionFailedAsync( - erc721TakerBalanceThresholdWrapper.marketSellOrdersAsync(orders, compliantTakerAddress, { + erc721TakerBalanceThresholdWrapper.marketSellOrdersAsync(orders, validTakerAddress, { takerAssetFillAmount, }), RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold, ); }); it('should revert if taker does not meet the balance threshold', async () => { - const orders = [compliantSignedOrder, compliantSignedOrder2]; + const orders = [validSignedOrder, validSignedOrder2]; return expectTransactionFailedAsync( - erc721NonCompliantBalanceThresholdWrapper.marketSellOrdersAsync(orders, nonCompliantAddress, { + erc721NonValidBalanceThresholdWrapper.marketSellOrdersAsync(orders, invalidAddress, { takerAssetFillAmount, }), RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold, @@ -1013,98 +984,98 @@ describe(ContractName.BalanceThresholdFilter, () => { describe('marketSellOrdersNoThrow', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); - compliantSignedOrder = await orderFactory.newSignedOrderAsync(); - compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); + validSignedOrder = await orderFactory.newSignedOrderAsync(); + validSignedOrder2 = 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 orders = [validSignedOrder, validSignedOrder2]; + const cumulativeTakerAssetFillAmount = validSignedOrder.takerAssetAmount.plus(takerAssetFillAmount); const txReceipt = await erc721TakerBalanceThresholdWrapper.marketSellOrdersNoThrowAsync( orders, - compliantTakerAddress, + validTakerAddress, { takerAssetFillAmount: cumulativeTakerAssetFillAmount }, ); // Assert validated addresses const expectedValidatedAddresseses = [ - compliantSignedOrder.makerAddress, - compliantSignedOrder2.makerAddress, - compliantSignedFillOrderTx.signerAddress, + validSignedOrder.makerAddress, + validSignedOrder2.makerAddress, + validTakerAddress, ]; await assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); // Check balances const newBalances = await erc20Wrapper.getBalancesAsync(); const makerAssetFillAmount2 = takerAssetFillAmount - .times(compliantSignedOrder.makerAssetAmount) - .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); - const makerFeePaid2 = compliantSignedOrder2.makerFee + .times(validSignedOrder.makerAssetAmount) + .dividedToIntegerBy(validSignedOrder.takerAssetAmount); + const makerFeePaid2 = validSignedOrder2.makerFee .times(makerAssetFillAmount2) - .dividedToIntegerBy(compliantSignedOrder2.makerAssetAmount); - const takerFeePaid2 = compliantSignedOrder2.takerFee + .dividedToIntegerBy(validSignedOrder2.makerAssetAmount); + const takerFeePaid2 = validSignedOrder2.takerFee .times(makerAssetFillAmount2) - .dividedToIntegerBy(compliantSignedOrder2.makerAssetAmount); - const takerFeePaid = compliantSignedOrder.takerFee.plus(takerFeePaid2); - const cumulativeMakerAssetFillAmount = compliantSignedOrder.makerAssetAmount.plus(makerAssetFillAmount2); + .dividedToIntegerBy(validSignedOrder2.makerAssetAmount); + const takerFeePaid = validSignedOrder.takerFee.plus(takerFeePaid2); + const cumulativeMakerAssetFillAmount = validSignedOrder.makerAssetAmount.plus(makerAssetFillAmount2); // Maker #1 - expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus( - compliantSignedOrder.makerAssetAmount, + expect(newBalances[validMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[validMakerAddress][defaultMakerAssetAddress].minus( + validSignedOrder.makerAssetAmount, ), ); - expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add( - compliantSignedOrder.takerAssetAmount, + expect(newBalances[validMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[validMakerAddress][defaultTakerAssetAddress].add( + validSignedOrder.takerAssetAmount, ), ); - expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][zrxToken.address].minus(compliantSignedOrder.makerFee), + expect(newBalances[validMakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[validMakerAddress][zrxToken.address].minus(validSignedOrder.makerFee), ); // Maker #2 - expect(newBalances[compliantMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount2), + expect(newBalances[validMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[validMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount2), ); - expect(newBalances[compliantMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount), + expect(newBalances[validMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[validMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount), ); - expect(newBalances[compliantMakerAddress2][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][zrxToken.address].minus(makerFeePaid2), + expect(newBalances[validMakerAddress2][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[validMakerAddress2][zrxToken.address].minus(makerFeePaid2), ); // Taker - expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount), + expect(newBalances[validTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[validTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount), ); - expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(cumulativeMakerAssetFillAmount), + expect(newBalances[validTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[validTakerAddress][defaultMakerAssetAddress].add(cumulativeMakerAssetFillAmount), ); - expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), + expect(newBalances[validTakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[validTakerAddress][zrxToken.address].minus(takerFeePaid), ); // Fee recipient expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( erc20Balances[feeRecipientAddress][zrxToken.address] - .add(compliantSignedOrder.makerFee) + .add(validSignedOrder.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 + // Create order set with one non-valid maker address const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - makerAddress: nonCompliantAddress, + makerAddress: invalidAddress, }); - const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; + const orders = [validSignedOrder, signedOrderWithBadMakerAddress]; // Execute transaction return expectTransactionFailedAsync( - erc721TakerBalanceThresholdWrapper.marketSellOrdersNoThrowAsync(orders, compliantTakerAddress, { + erc721TakerBalanceThresholdWrapper.marketSellOrdersNoThrowAsync(orders, validTakerAddress, { takerAssetFillAmount, }), RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold, ); }); it('should revert if taker does not meet the balance threshold', async () => { - const orders = [compliantSignedOrder, compliantSignedOrder2]; + const orders = [validSignedOrder, validSignedOrder2]; return expectTransactionFailedAsync( - erc721NonCompliantBalanceThresholdWrapper.marketSellOrdersNoThrowAsync(orders, nonCompliantAddress, { + erc721NonValidBalanceThresholdWrapper.marketSellOrdersNoThrowAsync(orders, invalidAddress, { takerAssetFillAmount, }), RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold, @@ -1115,100 +1086,100 @@ describe(ContractName.BalanceThresholdFilter, () => { describe('marketBuyOrders', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); - compliantSignedOrder = await orderFactory.newSignedOrderAsync(); - compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); + validSignedOrder = await orderFactory.newSignedOrderAsync(); + validSignedOrder2 = 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 orders = [validSignedOrder, validSignedOrder2]; + const cumulativeTakerAssetFillAmount = validSignedOrder.takerAssetAmount.plus(takerAssetFillAmount); const makerAssetFillAmount2 = takerAssetFillAmount - .times(compliantSignedOrder.makerAssetAmount) - .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); - const cumulativeMakerAssetFillAmount = compliantSignedOrder.makerAssetAmount.plus(makerAssetFillAmount2); + .times(validSignedOrder.makerAssetAmount) + .dividedToIntegerBy(validSignedOrder.takerAssetAmount); + const cumulativeMakerAssetFillAmount = validSignedOrder.makerAssetAmount.plus(makerAssetFillAmount2); const txReceipt = await erc721TakerBalanceThresholdWrapper.marketBuyOrdersAsync( orders, - compliantTakerAddress, + validTakerAddress, { makerAssetFillAmount: cumulativeMakerAssetFillAmount }, ); // Assert validated addresses const expectedValidatedAddresseses = [ - compliantSignedOrder.makerAddress, - compliantSignedOrder2.makerAddress, - compliantSignedFillOrderTx.signerAddress, + validSignedOrder.makerAddress, + validSignedOrder2.makerAddress, + validTakerAddress, ]; await assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); // Check balances const newBalances = await erc20Wrapper.getBalancesAsync(); - const makerFeePaid2 = compliantSignedOrder2.makerFee + const makerFeePaid2 = validSignedOrder2.makerFee .times(makerAssetFillAmount2) - .dividedToIntegerBy(compliantSignedOrder2.makerAssetAmount); - const takerFeePaid2 = compliantSignedOrder2.takerFee + .dividedToIntegerBy(validSignedOrder2.makerAssetAmount); + const takerFeePaid2 = validSignedOrder2.takerFee .times(makerAssetFillAmount2) - .dividedToIntegerBy(compliantSignedOrder2.makerAssetAmount); - const takerFeePaid = compliantSignedOrder.takerFee.plus(takerFeePaid2); + .dividedToIntegerBy(validSignedOrder2.makerAssetAmount); + const takerFeePaid = validSignedOrder.takerFee.plus(takerFeePaid2); // Maker #1 - expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus( - compliantSignedOrder.makerAssetAmount, + expect(newBalances[validMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[validMakerAddress][defaultMakerAssetAddress].minus( + validSignedOrder.makerAssetAmount, ), ); - expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add( - compliantSignedOrder.takerAssetAmount, + expect(newBalances[validMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[validMakerAddress][defaultTakerAssetAddress].add( + validSignedOrder.takerAssetAmount, ), ); - expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][zrxToken.address].minus(compliantSignedOrder.makerFee), + expect(newBalances[validMakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[validMakerAddress][zrxToken.address].minus(validSignedOrder.makerFee), ); // Maker #2 - expect(newBalances[compliantMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount2), + expect(newBalances[validMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[validMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount2), ); - expect(newBalances[compliantMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount), + expect(newBalances[validMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[validMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount), ); - expect(newBalances[compliantMakerAddress2][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][zrxToken.address].minus(makerFeePaid2), + expect(newBalances[validMakerAddress2][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[validMakerAddress2][zrxToken.address].minus(makerFeePaid2), ); // Taker - expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount), + expect(newBalances[validTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[validTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount), ); - expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(cumulativeMakerAssetFillAmount), + expect(newBalances[validTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[validTakerAddress][defaultMakerAssetAddress].add(cumulativeMakerAssetFillAmount), ); - expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), + expect(newBalances[validTakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[validTakerAddress][zrxToken.address].minus(takerFeePaid), ); // Fee recipient expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( erc20Balances[feeRecipientAddress][zrxToken.address] - .add(compliantSignedOrder.makerFee) + .add(validSignedOrder.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 + // Create order set with one non-valid maker address const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - makerAddress: nonCompliantAddress, + makerAddress: invalidAddress, }); - const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; + const orders = [validSignedOrder, signedOrderWithBadMakerAddress]; // Execute transaction const dummyMakerAssetFillAmount = new BigNumber(0); return expectTransactionFailedAsync( - erc721TakerBalanceThresholdWrapper.marketBuyOrdersAsync(orders, compliantTakerAddress, { + erc721TakerBalanceThresholdWrapper.marketBuyOrdersAsync(orders, validTakerAddress, { makerAssetFillAmount: dummyMakerAssetFillAmount, }), RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold, ); }); it('should revert if taker does not meet the balance threshold', async () => { - const orders = [compliantSignedOrder, compliantSignedOrder2]; + const orders = [validSignedOrder, validSignedOrder2]; const dummyMakerAssetFillAmount = new BigNumber(0); return expectTransactionFailedAsync( - erc721NonCompliantBalanceThresholdWrapper.marketBuyOrdersAsync(orders, nonCompliantAddress, { + erc721NonValidBalanceThresholdWrapper.marketBuyOrdersAsync(orders, invalidAddress, { makerAssetFillAmount: dummyMakerAssetFillAmount, }), RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold, @@ -1219,100 +1190,100 @@ describe(ContractName.BalanceThresholdFilter, () => { describe('marketBuyOrdersNoThrowAsync', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); - compliantSignedOrder = await orderFactory.newSignedOrderAsync(); - compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); + validSignedOrder = await orderFactory.newSignedOrderAsync(); + validSignedOrder2 = 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 orders = [validSignedOrder, validSignedOrder2]; + const cumulativeTakerAssetFillAmount = validSignedOrder.takerAssetAmount.plus(takerAssetFillAmount); const makerAssetFillAmount2 = takerAssetFillAmount - .times(compliantSignedOrder.makerAssetAmount) - .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); - const cumulativeMakerAssetFillAmount = compliantSignedOrder.makerAssetAmount.plus(makerAssetFillAmount2); + .times(validSignedOrder.makerAssetAmount) + .dividedToIntegerBy(validSignedOrder.takerAssetAmount); + const cumulativeMakerAssetFillAmount = validSignedOrder.makerAssetAmount.plus(makerAssetFillAmount2); const txReceipt = await erc721TakerBalanceThresholdWrapper.marketBuyOrdersNoThrowAsync( orders, - compliantTakerAddress, + validTakerAddress, { makerAssetFillAmount: cumulativeMakerAssetFillAmount }, ); // Assert validated addresses const expectedValidatedAddresseses = [ - compliantSignedOrder.makerAddress, - compliantSignedOrder2.makerAddress, - compliantSignedFillOrderTx.signerAddress, + validSignedOrder.makerAddress, + validSignedOrder2.makerAddress, + validTakerAddress, ]; await assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); // Check balances const newBalances = await erc20Wrapper.getBalancesAsync(); - const makerFeePaid2 = compliantSignedOrder2.makerFee + const makerFeePaid2 = validSignedOrder2.makerFee .times(makerAssetFillAmount2) - .dividedToIntegerBy(compliantSignedOrder2.makerAssetAmount); - const takerFeePaid2 = compliantSignedOrder2.takerFee + .dividedToIntegerBy(validSignedOrder2.makerAssetAmount); + const takerFeePaid2 = validSignedOrder2.takerFee .times(makerAssetFillAmount2) - .dividedToIntegerBy(compliantSignedOrder2.makerAssetAmount); - const takerFeePaid = compliantSignedOrder.takerFee.plus(takerFeePaid2); + .dividedToIntegerBy(validSignedOrder2.makerAssetAmount); + const takerFeePaid = validSignedOrder.takerFee.plus(takerFeePaid2); // Maker #1 - expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus( - compliantSignedOrder.makerAssetAmount, + expect(newBalances[validMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[validMakerAddress][defaultMakerAssetAddress].minus( + validSignedOrder.makerAssetAmount, ), ); - expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add( - compliantSignedOrder.takerAssetAmount, + expect(newBalances[validMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[validMakerAddress][defaultTakerAssetAddress].add( + validSignedOrder.takerAssetAmount, ), ); - expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][zrxToken.address].minus(compliantSignedOrder.makerFee), + expect(newBalances[validMakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[validMakerAddress][zrxToken.address].minus(validSignedOrder.makerFee), ); // Maker #2 - expect(newBalances[compliantMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount2), + expect(newBalances[validMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[validMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount2), ); - expect(newBalances[compliantMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount), + expect(newBalances[validMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[validMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount), ); - expect(newBalances[compliantMakerAddress2][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][zrxToken.address].minus(makerFeePaid2), + expect(newBalances[validMakerAddress2][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[validMakerAddress2][zrxToken.address].minus(makerFeePaid2), ); // Taker - expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount), + expect(newBalances[validTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[validTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount), ); - expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(cumulativeMakerAssetFillAmount), + expect(newBalances[validTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[validTakerAddress][defaultMakerAssetAddress].add(cumulativeMakerAssetFillAmount), ); - expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), + expect(newBalances[validTakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[validTakerAddress][zrxToken.address].minus(takerFeePaid), ); // Fee recipient expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( erc20Balances[feeRecipientAddress][zrxToken.address] - .add(compliantSignedOrder.makerFee) + .add(validSignedOrder.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 + // Create order set with one non-valid maker address const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - makerAddress: nonCompliantAddress, + makerAddress: invalidAddress, }); - const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; + const orders = [validSignedOrder, signedOrderWithBadMakerAddress]; // Execute transaction const dummyMakerAssetFillAmount = new BigNumber(0); return expectTransactionFailedAsync( - erc721TakerBalanceThresholdWrapper.marketBuyOrdersNoThrowAsync(orders, compliantTakerAddress, { + erc721TakerBalanceThresholdWrapper.marketBuyOrdersNoThrowAsync(orders, validTakerAddress, { makerAssetFillAmount: dummyMakerAssetFillAmount, }), RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold, ); }); it('should revert if taker does not meet the balance threshold', async () => { - const orders = [compliantSignedOrder, compliantSignedOrder2]; + const orders = [validSignedOrder, validSignedOrder2]; const dummyMakerAssetFillAmount = new BigNumber(0); return expectTransactionFailedAsync( - erc721NonCompliantBalanceThresholdWrapper.marketBuyOrdersNoThrowAsync(orders, nonCompliantAddress, { + erc721NonValidBalanceThresholdWrapper.marketBuyOrdersNoThrowAsync(orders, invalidAddress, { makerAssetFillAmount: dummyMakerAssetFillAmount, }), RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold, @@ -1323,8 +1294,8 @@ describe(ContractName.BalanceThresholdFilter, () => { describe('matchOrders', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); - compliantSignedOrder = await orderFactory.newSignedOrderAsync(); - compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); + validSignedOrder = await orderFactory.newSignedOrderAsync(); + validSignedOrder2 = 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: @@ -1364,13 +1335,13 @@ describe(ContractName.BalanceThresholdFilter, () => { const txReceipt = await erc721TakerBalanceThresholdWrapper.matchOrdersAsync( signedOrderLeft, signedOrderRight, - compliantTakerAddress, + validTakerAddress, ); // Assert validated addresses const expectedValidatedAddresseses = [ signedOrderLeft.makerAddress, signedOrderRight.makerAddress, - compliantTakerAddress, + validTakerAddress, ]; await assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); // Check balances @@ -1392,10 +1363,10 @@ describe(ContractName.BalanceThresholdFilter, () => { ), ); expect( - newBalances[compliantTakerAddress][defaultMakerAssetAddress], + newBalances[validTakerAddress][defaultMakerAssetAddress], 'Checking taker ingress ERC20 account balance', ).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add( + erc20Balances[validTakerAddress][defaultMakerAssetAddress].add( expectedTransferAmounts.amountReceivedByTaker, ), ); @@ -1433,10 +1404,10 @@ describe(ContractName.BalanceThresholdFilter, () => { ), ); expect( - newBalances[compliantTakerAddress][zrxToken.address], + newBalances[validTakerAddress][zrxToken.address], 'Checking taker egress ERC20 account fees', ).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][zrxToken.address] + erc20Balances[validTakerAddress][zrxToken.address] .minus(expectedTransferAmounts.feePaidByTakerLeft) .sub(expectedTransferAmounts.feePaidByTakerRight), ); @@ -1453,43 +1424,43 @@ describe(ContractName.BalanceThresholdFilter, () => { ); }); it('should revert if left maker does not meet the balance threshold', async () => { - // Create signed order with non-compliant maker address + // Create signed order with non-valid maker address const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - senderAddress: erc721CompliantForwarderInstance.address, - makerAddress: nonCompliantAddress, + senderAddress: erc721BalanceThresholdFilterInstance.address, + makerAddress: invalidAddress, }); // Execute transaction return expectTransactionFailedAsync( erc721TakerBalanceThresholdWrapper.matchOrdersAsync( - compliantSignedOrder, + validSignedOrder, signedOrderWithBadMakerAddress, - compliantTakerAddress, + validTakerAddress, ), RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold, ); }); it('should revert if right maker does not meet the balance threshold', async () => { - // Create signed order with non-compliant maker address + // Create signed order with non-valid maker address const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - senderAddress: erc721CompliantForwarderInstance.address, - makerAddress: nonCompliantAddress, + senderAddress: erc721BalanceThresholdFilterInstance.address, + makerAddress: invalidAddress, }); // Execute transaction return expectTransactionFailedAsync( erc721TakerBalanceThresholdWrapper.matchOrdersAsync( signedOrderWithBadMakerAddress, - compliantSignedOrder, - compliantTakerAddress, + validSignedOrder, + validTakerAddress, ), RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold, ); }); it('should revert if taker does not meet the balance threshold', async () => { return expectTransactionFailedAsync( - erc721NonCompliantBalanceThresholdWrapper.matchOrdersAsync( - compliantSignedOrder, - compliantSignedOrder, - nonCompliantAddress, + erc721NonValidBalanceThresholdWrapper.matchOrdersAsync( + validSignedOrder, + validSignedOrder, + invalidAddress, ), RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold, ); @@ -1499,39 +1470,39 @@ describe(ContractName.BalanceThresholdFilter, () => { describe('cancelOrder', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); - compliantSignedOrder = await orderFactory.newSignedOrderAsync(); - compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); + validSignedOrder = await orderFactory.newSignedOrderAsync(); + validSignedOrder2 = 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, + validSignedOrder, ); expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); // Cancel const txReceipt = await erc721MakerBalanceThresholdWrapper.cancelOrderAsync( - compliantSignedOrder, - compliantSignedOrder.makerAddress, + validSignedOrder, + validSignedOrder.makerAddress, ); // Assert validated addresses const expectedValidatedAddresseses: string[] = []; await assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); // Check that order was cancelled const orderInfoAfterCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync( - compliantSignedOrder, + validSignedOrder, ); 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({}); + const signedOrderWithBadMakerAddress = await invalidOrderFactory.newSignedOrderAsync({}); // Verify order is not cancelled - const orderInfoBeforeCancelling = await erc721NonCompliantBalanceThresholdWrapper.getOrderInfoAsync( + const orderInfoBeforeCancelling = await erc721NonValidBalanceThresholdWrapper.getOrderInfoAsync( signedOrderWithBadMakerAddress, ); expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); // Cancel - const txReceipt = await erc721NonCompliantBalanceThresholdWrapper.cancelOrderAsync( + const txReceipt = await erc721NonValidBalanceThresholdWrapper.cancelOrderAsync( signedOrderWithBadMakerAddress, signedOrderWithBadMakerAddress.makerAddress, ); @@ -1552,13 +1523,13 @@ describe(ContractName.BalanceThresholdFilter, () => { }); it('Should successfully batch cancel orders if maker meets balance threshold', async () => { // Create orders to cancel - const compliantSignedOrders = [ + const validSignedOrders = [ await orderFactory.newSignedOrderAsync(), await orderFactory.newSignedOrderAsync(), await orderFactory.newSignedOrderAsync(), ]; // Verify orders are not cancelled - _.each(compliantSignedOrders, async signedOrder => { + _.each(validSignedOrders, async signedOrder => { const orderInfoBeforeCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync( signedOrder, ); @@ -1566,14 +1537,14 @@ describe(ContractName.BalanceThresholdFilter, () => { }); // Cancel const txReceipt = await erc721MakerBalanceThresholdWrapper.batchCancelOrdersAsync( - compliantSignedOrders, - compliantSignedOrder.makerAddress, + validSignedOrders, + validSignedOrder.makerAddress, ); // Assert validated addresses const expectedValidatedAddresseses: string[] = []; await assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); // Check that order was cancelled - _.each(compliantSignedOrders, async signedOrder => { + _.each(validSignedOrders, async signedOrder => { const orderInfoAfterCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync( signedOrder, ); @@ -1582,29 +1553,29 @@ describe(ContractName.BalanceThresholdFilter, () => { }); 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(), + const invalidSignedOrders = [ + await invalidOrderFactory.newSignedOrderAsync(), + await invalidOrderFactory.newSignedOrderAsync(), + await invalidOrderFactory.newSignedOrderAsync(), ]; // Verify orders are not cancelled - _.each(nonCompliantSignedOrders, async signedOrder => { - const orderInfoBeforeCancelling = await erc721NonCompliantBalanceThresholdWrapper.getOrderInfoAsync( + _.each(invalidSignedOrders, async signedOrder => { + const orderInfoBeforeCancelling = await erc721NonValidBalanceThresholdWrapper.getOrderInfoAsync( signedOrder, ); return expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); }); // Cancel - const txReceipt = await erc721NonCompliantBalanceThresholdWrapper.batchCancelOrdersAsync( - nonCompliantSignedOrders, - nonCompliantAddress, + const txReceipt = await erc721NonValidBalanceThresholdWrapper.batchCancelOrdersAsync( + invalidSignedOrders, + invalidAddress, ); // Assert validated addresses const expectedValidatedAddresseses: string[] = []; await assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); // Check that order was cancelled - _.each(nonCompliantSignedOrders, async signedOrder => { - const orderInfoAfterCancelling = await erc721NonCompliantBalanceThresholdWrapper.getOrderInfoAsync( + _.each(invalidSignedOrders, async signedOrder => { + const orderInfoAfterCancelling = await erc721NonValidBalanceThresholdWrapper.getOrderInfoAsync( signedOrder, ); return expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED); @@ -1618,13 +1589,13 @@ describe(ContractName.BalanceThresholdFilter, () => { }); it('Should successfully batch cancel orders if maker meets balance threshold', async () => { // Create orders to cancel - const compliantSignedOrders = [ + const validSignedOrders = [ 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 - _.each(compliantSignedOrders, async signedOrder => { + _.each(validSignedOrders, async signedOrder => { const orderInfoBeforeCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync( signedOrder, ); @@ -1634,13 +1605,13 @@ describe(ContractName.BalanceThresholdFilter, () => { const cancelOrdersUpToThisSalt = new BigNumber(1); const txReceipt = await erc721MakerBalanceThresholdWrapper.cancelOrdersUpToAsync( cancelOrdersUpToThisSalt, - compliantSignedOrder.makerAddress, + validSignedOrder.makerAddress, ); // Assert validated addresses const expectedValidatedAddresseses: string[] = []; await assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); // Check that order was cancelled - _.each(compliantSignedOrders, async (signedOrder, salt: number) => { + _.each(validSignedOrders, async (signedOrder, salt: number) => { const orderInfoAfterCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync( signedOrder, ); @@ -1654,30 +1625,30 @@ describe(ContractName.BalanceThresholdFilter, () => { }); 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) }), + const invalidSignedOrders = [ + await invalidOrderFactory.newSignedOrderAsync({ salt: new BigNumber(0) }), + await invalidOrderFactory.newSignedOrderAsync({ salt: new BigNumber(1) }), + await invalidOrderFactory.newSignedOrderAsync({ salt: new BigNumber(2) }), ]; // Verify orders are not cancelled - _.each(nonCompliantSignedOrders, async signedOrder => { - const orderInfoBeforeCancelling = await erc721NonCompliantBalanceThresholdWrapper.getOrderInfoAsync( + _.each(invalidSignedOrders, async signedOrder => { + const orderInfoBeforeCancelling = await erc721NonValidBalanceThresholdWrapper.getOrderInfoAsync( signedOrder, ); return expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); }); // Cancel const cancelOrdersUpToThisSalt = new BigNumber(1); - const txReceipt = await erc721NonCompliantBalanceThresholdWrapper.cancelOrdersUpToAsync( + const txReceipt = await erc721NonValidBalanceThresholdWrapper.cancelOrdersUpToAsync( cancelOrdersUpToThisSalt, - nonCompliantAddress, + invalidAddress, ); // Assert validated addresses const expectedValidatedAddresseses: string[] = []; await assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); // Check that order was cancelled - _.each(nonCompliantSignedOrders, async (signedOrder, salt: number) => { - const orderInfoAfterCancelling = await erc721NonCompliantBalanceThresholdWrapper.getOrderInfoAsync( + _.each(invalidSignedOrders, async (signedOrder, salt: number) => { + const orderInfoAfterCancelling = await erc721NonValidBalanceThresholdWrapper.getOrderInfoAsync( signedOrder, ); const saltAsBigNumber = new BigNumber(salt); -- cgit v1.2.3 From 22fd23643cb26ba1b79e9b13e5ef41c97ab7fe6a Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 11 Dec 2018 16:37:07 -0800 Subject: Run all tests for extensions --- contracts/extensions/test/extensions/balance_threshold_filter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/extensions/test/extensions/balance_threshold_filter.ts b/contracts/extensions/test/extensions/balance_threshold_filter.ts index ced9d6e25..6350c019f 100644 --- a/contracts/extensions/test/extensions/balance_threshold_filter.ts +++ b/contracts/extensions/test/extensions/balance_threshold_filter.ts @@ -46,7 +46,7 @@ interface ValidatedAddressesLog { args: { addresses: string[] }; } -describe.only(ContractName.BalanceThresholdFilter, () => { +describe(ContractName.BalanceThresholdFilter, () => { 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); -- cgit v1.2.3 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. --- .../BalanceThresholdFilter.sol | 10 +- .../MixinBalanceThresholdFilterCore.sol | 404 ++++++++------------- .../mixins/MBalanceThresholdFilterCore.sol | 7 +- .../utils/ExchangeSelectors/ExchangeSelectors.sol | 151 ++++++++ .../utils/ExchangeSelectors/ExchangeSelectors.sol | 151 -------- 5 files changed, 306 insertions(+), 417 deletions(-) create mode 100644 contracts/utils/contracts/utils/ExchangeSelectors/ExchangeSelectors.sol delete mode 100644 packages/contracts/contracts/utils/ExchangeSelectors/ExchangeSelectors.sol diff --git a/contracts/extensions/contracts/BalanceThresholdFilter/BalanceThresholdFilter.sol b/contracts/extensions/contracts/BalanceThresholdFilter/BalanceThresholdFilter.sol index a68f6805d..ea248793f 100644 --- a/contracts/extensions/contracts/BalanceThresholdFilter/BalanceThresholdFilter.sol +++ b/contracts/extensions/contracts/BalanceThresholdFilter/BalanceThresholdFilter.sol @@ -24,21 +24,23 @@ import "./interfaces/IThresholdAsset.sol"; import "./MixinBalanceThresholdFilterCore.sol"; -contract BalanceThresholdFilter is MixinBalanceThresholdFilterCore { +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. + /// @param balanceThreshold The minimum balance of `thresholdAsset` that must be held by makers/takers. constructor( address exchange, address thresholdAsset, - uint256 thresholdBalance + uint256 balanceThreshold ) public { EXCHANGE = IExchange(exchange); THRESHOLD_ASSET = IThresholdAsset(thresholdAsset); - THRESHOLD_BALANCE = thresholdBalance; + BALANCE_THRESHOLD = balanceThreshold; } } diff --git a/contracts/extensions/contracts/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol b/contracts/extensions/contracts/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol index 51dbae8f3..e78f9ced8 100644 --- a/contracts/extensions/contracts/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol +++ b/contracts/extensions/contracts/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol @@ -6,7 +6,7 @@ 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 + http://www.apache.org/licenses/LICENSE2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, @@ -17,31 +17,36 @@ */ pragma solidity 0.4.24; -pragma experimental ABIEncoderV2; import "./mixins/MBalanceThresholdFilterCore.sol"; +import "@0x/contracts-utils/contracts/utils/ExchangeSelectors/ExchangeSelectors.sol"; +import "@0x/contracts-libs/contracts/libs/LibOrder.sol"; -contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { +contract MixinBalanceThresholdFilterCore is + MBalanceThresholdFilterCore, + LibOrder, + ExchangeSelectors +{ /// @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 + /// 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. @@ -56,7 +61,7 @@ contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { external { // Validate addresses. - validateBalanceThresholdsOrRevert(); + validateBalanceThresholdsOrRevert(signerAddress); // All addresses are valid. Execute fillOrder. EXCHANGE.executeTransaction( @@ -67,6 +72,83 @@ contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { ); } + /// @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(uint256 offset) + internal + returns (bytes32 value) + { + assembly { + // 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(uint256 offset) + internal + returns (bytes32 value) + { + value = exchangeCalldataload(offset + 4); + } + + /// @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(address addressToValidate, address[] memory addressList) + internal + { + uint256 newAddressListLength = addressList.length + 1; + assembly { + // Store new array length + mstore(addressList, newAddressListLength) + mstore(0x40, add(addressList, add(0x20, mul(0x20, newAddressListLength)))) + } + addressList[newAddressListLength - 1] = 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 loadMakerAddressFromOrder(uint8 orderParamIndex) internal returns (address makerAddress) { + uint256 orderPtr = uint256(loadExchangeData(orderParamIndex * 0x20)); + makerAddress = address(loadExchangeData(orderPtr)); + } + + /// @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 loadMakerAddressesFromOrderArray(uint8 orderArrayParamIndex) + internal + returns (address[] makerAddresses) + { + uint256 orderArrayPtr = uint256(loadExchangeData(orderArrayParamIndex * 0x20)); + uint256 orderArrayLength = uint256(loadExchangeData(orderArrayPtr)); + uint256 orderArrayElementPtr = orderArrayPtr + 0x20; + uint256 orderArrayElementEndPtr = orderArrayElementPtr + (orderArrayLength * 0x20); + for(uint orderPtrOffset = orderArrayElementPtr; orderPtrOffset < orderArrayElementEndPtr; orderPtrOffset += 0x20) { + uint256 orderPtr = uint256(loadExchangeData(orderPtrOffset)); + address makerAddress = address(loadExchangeData(orderPtr + orderArrayElementPtr)); + recordAddressToValidate(makerAddress, makerAddresses); + } + } + /// @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 @@ -74,250 +156,56 @@ contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { /// 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() + function validateBalanceThresholdsOrRevert(address signerAddress) 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 - // solhint-disable no-empty-blocks - 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) - } - // solhint-enable no-empty-blocks - - ///// 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) - // solhint-disable max-line-length - for {let addressToValidate := addressesToValidateElementPtr} lt(addressToValidate, addressesToValidateElementEndPtr) {addressToValidate := add(addressToValidate, 0x20)} { - // solhint-enable max-line-length - // 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) - } + // Extract addresses to validate from Exchange calldata + address[] memory addressesToValidate = new address[](0); + bytes4 exchangeFunctionSelector = bytes4(exchangeCalldataload(0)); + if( + exchangeFunctionSelector == batchFillOrdersSelector || + exchangeFunctionSelector == batchFillOrdersNoThrowSelector || + exchangeFunctionSelector == batchFillOrKillOrdersSelector || + exchangeFunctionSelector == marketBuyOrdersSelector || + exchangeFunctionSelector == marketBuyOrdersNoThrowSelector || + exchangeFunctionSelector == marketSellOrdersSelector || + exchangeFunctionSelector == marketSellOrdersNoThrowSelector + ) { + addressesToValidate = loadMakerAddressesFromOrderArray(0); + recordAddressToValidate(signerAddress, addressesToValidate); + } else if( + exchangeFunctionSelector == fillOrderSelector || + exchangeFunctionSelector == fillOrderNoThrowSelector || + exchangeFunctionSelector == fillOrKillOrderSelector + ) { + address makerAddress = loadMakerAddressFromOrder(0); + recordAddressToValidate(makerAddress, addressesToValidate); + recordAddressToValidate(signerAddress, addressesToValidate); + } else if(exchangeFunctionSelector == matchOrdersSelector) { + address leftOrderAddress = loadMakerAddressFromOrder(0); + recordAddressToValidate(leftOrderAddress, addressesToValidate); + address rightOrderAddress = loadMakerAddressFromOrder(1); + recordAddressToValidate(rightOrderAddress, addressesToValidate); + recordAddressToValidate(signerAddress, addressesToValidate); + } else if( + exchangeFunctionSelector == cancelOrderSelector || + exchangeFunctionSelector == batchCancelOrdersSelector || + exchangeFunctionSelector == cancelOrdersUpToSelector + ) { + // Do nothing + } else { + revert("INVALID_OR_BLOCKED_EXCHANGE_SELECTOR"); + } - // 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) - } + // Validate account balances + uint256 balanceThreshold = BALANCE_THRESHOLD; + IThresholdAsset thresholdAsset = THRESHOLD_ASSET; + for(uint i = 0; i < addressesToValidate.length; ++i) { + uint256 addressBalance = thresholdAsset.balanceOf(addressesToValidate[i]); + if (addressBalance < balanceThreshold) { + revert("AT_LEAST_ONE_ADDRESS_DOES_NOT_MEET_BALANCE_THRESHOLD"); } - - // Record validated addresses - validatedAddresses := addressesToValidate } - - ///// If we hit this point then all addresses are valid ///// - emit ValidatedAddresses(validatedAddresses); + emit ValidatedAddresses(addressesToValidate); } } diff --git a/contracts/extensions/contracts/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol b/contracts/extensions/contracts/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol index af7a9453b..6aaa729fb 100644 --- a/contracts/extensions/contracts/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol +++ b/contracts/extensions/contracts/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol @@ -17,7 +17,6 @@ */ pragma solidity 0.4.24; -pragma experimental ABIEncoderV2; import "@0x/contracts-interfaces/contracts/protocol/Exchange/IExchange.sol"; import "../interfaces/IThresholdAsset.sol"; @@ -33,10 +32,10 @@ contract MBalanceThresholdFilterCore { IThresholdAsset internal THRESHOLD_ASSET; // The minimum balance of `THRESHOLD_ASSET` that must be held by makers/takers - uint256 internal THRESHOLD_BALANCE; + uint256 internal BALANCE_THRESHOLD; // solhint-enable var-name-mixedcase - // Addresses that hold at least `THRESHOLD_BALANCE` of `THRESHOLD_ASSET` + // Addresses that hold at least `BALANCE_THRESHOLD` of `THRESHOLD_ASSET` event ValidatedAddresses ( address[] addresses ); @@ -79,5 +78,5 @@ contract MBalanceThresholdFilterCore { /// 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; + function validateBalanceThresholdsOrRevert(address signerAddress) internal; } diff --git a/contracts/utils/contracts/utils/ExchangeSelectors/ExchangeSelectors.sol b/contracts/utils/contracts/utils/ExchangeSelectors/ExchangeSelectors.sol new file mode 100644 index 000000000..c361fd075 --- /dev/null +++ b/contracts/utils/contracts/utils/ExchangeSelectors/ExchangeSelectors.sol @@ -0,0 +1,151 @@ +/* + + 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 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 v1.2.3 From f3a2e3b6f3adb75d1920779df9dabb7cf476a996 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 13 Dec 2018 14:20:11 -0800 Subject: Moved exchange calldata functions to separate mixin --- .../MixinBalanceThresholdFilterCore.sol | 93 ++----------- .../utils/ExchangeSelectors/ExchangeSelectors.sol | 151 --------------------- 2 files changed, 9 insertions(+), 235 deletions(-) delete mode 100644 contracts/utils/contracts/utils/ExchangeSelectors/ExchangeSelectors.sol diff --git a/contracts/extensions/contracts/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol b/contracts/extensions/contracts/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol index e78f9ced8..8d15fe6c8 100644 --- a/contracts/extensions/contracts/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol +++ b/contracts/extensions/contracts/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol @@ -18,15 +18,17 @@ pragma solidity 0.4.24; -import "./mixins/MBalanceThresholdFilterCore.sol"; -import "@0x/contracts-utils/contracts/utils/ExchangeSelectors/ExchangeSelectors.sol"; +import "@0x/contracts-libs/contracts/libs/LibExchangeSelectors.sol"; import "@0x/contracts-libs/contracts/libs/LibOrder.sol"; +import "./mixins/MBalanceThresholdFilterCore.sol"; +import "./MixinExchangeCalldata.sol"; contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore, + MixinExchangeCalldata, LibOrder, - ExchangeSelectors + LibExchangeSelectors { /// @dev Executes an Exchange transaction iff the maker and taker meet @@ -72,82 +74,7 @@ contract MixinBalanceThresholdFilterCore is ); } - /// @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(uint256 offset) - internal - returns (bytes32 value) - { - assembly { - // 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(uint256 offset) - internal - returns (bytes32 value) - { - value = exchangeCalldataload(offset + 4); - } - - /// @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(address addressToValidate, address[] memory addressList) - internal - { - uint256 newAddressListLength = addressList.length + 1; - assembly { - // Store new array length - mstore(addressList, newAddressListLength) - mstore(0x40, add(addressList, add(0x20, mul(0x20, newAddressListLength)))) - } - addressList[newAddressListLength - 1] = 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 loadMakerAddressFromOrder(uint8 orderParamIndex) internal returns (address makerAddress) { - uint256 orderPtr = uint256(loadExchangeData(orderParamIndex * 0x20)); - makerAddress = address(loadExchangeData(orderPtr)); - } - - /// @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 loadMakerAddressesFromOrderArray(uint8 orderArrayParamIndex) - internal - returns (address[] makerAddresses) - { - uint256 orderArrayPtr = uint256(loadExchangeData(orderArrayParamIndex * 0x20)); - uint256 orderArrayLength = uint256(loadExchangeData(orderArrayPtr)); - uint256 orderArrayElementPtr = orderArrayPtr + 0x20; - uint256 orderArrayElementEndPtr = orderArrayElementPtr + (orderArrayLength * 0x20); - for(uint orderPtrOffset = orderArrayElementPtr; orderPtrOffset < orderArrayElementEndPtr; orderPtrOffset += 0x20) { - uint256 orderPtr = uint256(loadExchangeData(orderPtrOffset)); - address makerAddress = address(loadExchangeData(orderPtr + orderArrayElementPtr)); - recordAddressToValidate(makerAddress, makerAddresses); - } - } + /// @dev Validates addresses meet the balance threshold specified by `BALANCE_THRESHOLD` /// for the asset `THRESHOLD_ASSET`. If one address does not meet the thresold @@ -188,12 +115,10 @@ contract MixinBalanceThresholdFilterCore is recordAddressToValidate(rightOrderAddress, addressesToValidate); recordAddressToValidate(signerAddress, addressesToValidate); } else if( - exchangeFunctionSelector == cancelOrderSelector || - exchangeFunctionSelector == batchCancelOrdersSelector || - exchangeFunctionSelector == cancelOrdersUpToSelector + exchangeFunctionSelector != cancelOrderSelector && + exchangeFunctionSelector != batchCancelOrdersSelector && + exchangeFunctionSelector != cancelOrdersUpToSelector ) { - // Do nothing - } else { revert("INVALID_OR_BLOCKED_EXCHANGE_SELECTOR"); } diff --git a/contracts/utils/contracts/utils/ExchangeSelectors/ExchangeSelectors.sol b/contracts/utils/contracts/utils/ExchangeSelectors/ExchangeSelectors.sol deleted file mode 100644 index c361fd075..000000000 --- a/contracts/utils/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 v1.2.3 From dea6f35b0471913551d58a08f547974701fc0057 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 13 Dec 2018 14:26:11 -0800 Subject: Refactoring balance threshold filter --- .../BalanceThresholdFilter.sol | 1 - .../MixinBalanceThresholdFilterCore.sol | 63 ++++----- .../MixinExchangeCalldata.sol | 102 ++++++++++++++ .../interfaces/IBalanceThresholdFilterCore.sol | 56 ++++++++ .../mixins/MBalanceThresholdFilterCore.sol | 50 ++----- .../mixins/MExchangeCalldata.sol | 57 ++++++++ contracts/libs/contracts/libs/LibAddressArray.sol | 75 ++++++++++ .../libs/contracts/libs/LibExchangeSelectors.sol | 151 +++++++++++++++++++++ 8 files changed, 481 insertions(+), 74 deletions(-) create mode 100644 contracts/extensions/contracts/BalanceThresholdFilter/MixinExchangeCalldata.sol create mode 100644 contracts/extensions/contracts/BalanceThresholdFilter/interfaces/IBalanceThresholdFilterCore.sol create mode 100644 contracts/extensions/contracts/BalanceThresholdFilter/mixins/MExchangeCalldata.sol create mode 100644 contracts/libs/contracts/libs/LibAddressArray.sol create mode 100644 contracts/libs/contracts/libs/LibExchangeSelectors.sol diff --git a/contracts/extensions/contracts/BalanceThresholdFilter/BalanceThresholdFilter.sol b/contracts/extensions/contracts/BalanceThresholdFilter/BalanceThresholdFilter.sol index ea248793f..16cacd461 100644 --- a/contracts/extensions/contracts/BalanceThresholdFilter/BalanceThresholdFilter.sol +++ b/contracts/extensions/contracts/BalanceThresholdFilter/BalanceThresholdFilter.sol @@ -17,7 +17,6 @@ */ pragma solidity 0.4.24; -pragma experimental ABIEncoderV2; import "@0x/contracts-interfaces/contracts/protocol/Exchange/IExchange.sol"; import "./interfaces/IThresholdAsset.sol"; diff --git a/contracts/extensions/contracts/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol b/contracts/extensions/contracts/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol index 8d15fe6c8..a8947751a 100644 --- a/contracts/extensions/contracts/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol +++ b/contracts/extensions/contracts/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol @@ -62,8 +62,19 @@ contract MixinBalanceThresholdFilterCore is ) external { - // Validate addresses. - validateBalanceThresholdsOrRevert(signerAddress); + // Get accounts whose balances must be validated + address[] memory addressesToValidate = getAddressesToValidate(signerAddress); + + // Validate account balances + uint256 balanceThreshold = BALANCE_THRESHOLD; + IThresholdAsset thresholdAsset = THRESHOLD_ASSET; + for(uint256 i = 0; i < addressesToValidate.length; ++i) { + uint256 addressBalance = thresholdAsset.balanceOf(addressesToValidate[i]); + if (addressBalance < balanceThreshold) { + revert("AT_LEAST_ONE_ADDRESS_DOES_NOT_MEET_BALANCE_THRESHOLD"); + } + } + emit ValidatedAddresses(addressesToValidate); // All addresses are valid. Execute fillOrder. EXCHANGE.executeTransaction( @@ -74,20 +85,15 @@ contract MixinBalanceThresholdFilterCore is ); } - - - /// @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(address signerAddress) - internal + /// @dev Constructs an array of addresses to be validated. + /// Addresses depend on which Exchange function is to be called + /// (defined by `signedExchangeTransaction` above). + /// @param signerAddress Address of transaction signer. + /// @return addressesToValidate Array of addresses to validate. + function getAddressesToValidate(address signerAddress) + internal pure + returns (address[] memory addressesToValidate) { - // Extract addresses to validate from Exchange calldata - address[] memory addressesToValidate = new address[](0); bytes4 exchangeFunctionSelector = bytes4(exchangeCalldataload(0)); if( exchangeFunctionSelector == batchFillOrdersSelector || @@ -99,21 +105,21 @@ contract MixinBalanceThresholdFilterCore is exchangeFunctionSelector == marketSellOrdersNoThrowSelector ) { addressesToValidate = loadMakerAddressesFromOrderArray(0); - recordAddressToValidate(signerAddress, addressesToValidate); + addressesToValidate = addressesToValidate.append(signerAddress); } else if( exchangeFunctionSelector == fillOrderSelector || exchangeFunctionSelector == fillOrderNoThrowSelector || exchangeFunctionSelector == fillOrKillOrderSelector ) { address makerAddress = loadMakerAddressFromOrder(0); - recordAddressToValidate(makerAddress, addressesToValidate); - recordAddressToValidate(signerAddress, addressesToValidate); + addressesToValidate = addressesToValidate.append(makerAddress); + addressesToValidate = addressesToValidate.append(signerAddress); } else if(exchangeFunctionSelector == matchOrdersSelector) { - address leftOrderAddress = loadMakerAddressFromOrder(0); - recordAddressToValidate(leftOrderAddress, addressesToValidate); - address rightOrderAddress = loadMakerAddressFromOrder(1); - recordAddressToValidate(rightOrderAddress, addressesToValidate); - recordAddressToValidate(signerAddress, addressesToValidate); + address leftMakerAddress = loadMakerAddressFromOrder(0); + addressesToValidate = addressesToValidate.append(leftMakerAddress); + address rightMakerAddress = loadMakerAddressFromOrder(1); + addressesToValidate = addressesToValidate.append(rightMakerAddress); + addressesToValidate = addressesToValidate.append(signerAddress); } else if( exchangeFunctionSelector != cancelOrderSelector && exchangeFunctionSelector != batchCancelOrdersSelector && @@ -121,16 +127,5 @@ contract MixinBalanceThresholdFilterCore is ) { revert("INVALID_OR_BLOCKED_EXCHANGE_SELECTOR"); } - - // Validate account balances - uint256 balanceThreshold = BALANCE_THRESHOLD; - IThresholdAsset thresholdAsset = THRESHOLD_ASSET; - for(uint i = 0; i < addressesToValidate.length; ++i) { - uint256 addressBalance = thresholdAsset.balanceOf(addressesToValidate[i]); - if (addressBalance < balanceThreshold) { - revert("AT_LEAST_ONE_ADDRESS_DOES_NOT_MEET_BALANCE_THRESHOLD"); - } - } - emit ValidatedAddresses(addressesToValidate); } } diff --git a/contracts/extensions/contracts/BalanceThresholdFilter/MixinExchangeCalldata.sol b/contracts/extensions/contracts/BalanceThresholdFilter/MixinExchangeCalldata.sol new file mode 100644 index 000000000..12f601dea --- /dev/null +++ b/contracts/extensions/contracts/BalanceThresholdFilter/MixinExchangeCalldata.sol @@ -0,0 +1,102 @@ + + /* + + 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/LICENSE2.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; + +import "./mixins/MExchangeCalldata.sol"; +import "@0x/contracts-libs/contracts/libs/LibAddressArray.sol"; + + +contract MixinExchangeCalldata is + MExchangeCalldata +{ + + using LibAddressArray for address[]; + + /// @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(uint256 offset) + internal pure + returns (bytes32 value) + { + assembly { + // 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(uint256 offset) + internal pure + returns (bytes32 value) + { + value = exchangeCalldataload(offset + 4); + } + + /// @dev Extracts the maker address from an order stored in the Exchange calldata + /// (which is embedded in `signedExchangeTransaction`). + /// @param orderParamIndex Index of the order in the Exchange function's signature. + /// @return makerAddress The extracted maker address. + function loadMakerAddressFromOrder(uint256 orderParamIndex) + internal pure + returns (address makerAddress) + { + uint256 orderOffsetInBytes = orderParamIndex * 32; + uint256 orderPtr = uint256(loadExchangeData(orderOffsetInBytes)); + makerAddress = address(loadExchangeData(orderPtr)); + return 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 + /// @return makerAddresses The extracted maker addresses. + function loadMakerAddressesFromOrderArray(uint256 orderArrayParamIndex) + internal pure + returns (address[] makerAddresses) + { + uint256 orderArrayOffsetInBytes = orderArrayParamIndex * 32; + uint256 orderArrayPtr = uint256(loadExchangeData(orderArrayOffsetInBytes)); + uint256 orderArrayLength = uint256(loadExchangeData(orderArrayPtr)); + uint256 orderArrayLengthInBytes = orderArrayLength * 32; + uint256 orderArrayElementPtr = orderArrayPtr + 32; + uint256 orderArrayElementEndPtr = orderArrayElementPtr + orderArrayLengthInBytes; + for(uint orderPtrOffset = orderArrayElementPtr; orderPtrOffset < orderArrayElementEndPtr; orderPtrOffset += 32) { + uint256 orderPtr = uint256(loadExchangeData(orderPtrOffset)); + address makerAddress = address(loadExchangeData(orderPtr + orderArrayElementPtr)); + makerAddresses = makerAddresses.append(makerAddress); + } + return makerAddresses; + } +} diff --git a/contracts/extensions/contracts/BalanceThresholdFilter/interfaces/IBalanceThresholdFilterCore.sol b/contracts/extensions/contracts/BalanceThresholdFilter/interfaces/IBalanceThresholdFilterCore.sol new file mode 100644 index 000000000..37e607be1 --- /dev/null +++ b/contracts/extensions/contracts/BalanceThresholdFilter/interfaces/IBalanceThresholdFilterCore.sol @@ -0,0 +1,56 @@ + +/* + + 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 IBalanceThresholdFilterCore { + + /// @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; +} \ No newline at end of file diff --git a/contracts/extensions/contracts/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol b/contracts/extensions/contracts/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol index 6aaa729fb..b8b67e6ee 100644 --- a/contracts/extensions/contracts/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol +++ b/contracts/extensions/contracts/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol @@ -20,9 +20,12 @@ pragma solidity 0.4.24; import "@0x/contracts-interfaces/contracts/protocol/Exchange/IExchange.sol"; import "../interfaces/IThresholdAsset.sol"; +import "../interfaces/IBalanceThresholdFilterCore.sol"; -contract MBalanceThresholdFilterCore { +contract MBalanceThresholdFilterCore is + IBalanceThresholdFilterCore +{ // Points to 0x exchange contract // solhint-disable var-name-mixedcase @@ -40,43 +43,12 @@ 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: - /// - 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. + /// @dev Constructs an array of addresses to be validated. + /// Addresses depend on which Exchange function is to be called + /// (defined by `signedExchangeTransaction` above). /// @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(address signerAddress) internal; + /// @return addressesToValidate Array of addresses to validate. + function getAddressesToValidate(address signerAddress) + internal pure + returns (address[] memory addressesToValidate); } diff --git a/contracts/extensions/contracts/BalanceThresholdFilter/mixins/MExchangeCalldata.sol b/contracts/extensions/contracts/BalanceThresholdFilter/mixins/MExchangeCalldata.sol new file mode 100644 index 000000000..8e0414c17 --- /dev/null +++ b/contracts/extensions/contracts/BalanceThresholdFilter/mixins/MExchangeCalldata.sol @@ -0,0 +1,57 @@ + + /* + + 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/LICENSE2.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 MExchangeCalldata { + + /// @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(uint256 offset) + internal pure + returns (bytes32 value); + + /// @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(uint256 offset) + internal pure + returns (bytes32 value); + + /// @dev Extracts the maker address from an order stored in the Exchange calldata + /// (which is embedded in `signedExchangeTransaction`). + /// @param orderParamIndex Index of the order in the Exchange function's signature. + /// @return makerAddress The extracted maker address. + function loadMakerAddressFromOrder(uint256 orderParamIndex) + internal pure + returns (address 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 + /// @return makerAddresses The extracted maker addresses. + function loadMakerAddressesFromOrderArray(uint256 orderArrayParamIndex) + internal pure + returns (address[] makerAddresses); +} diff --git a/contracts/libs/contracts/libs/LibAddressArray.sol b/contracts/libs/contracts/libs/LibAddressArray.sol new file mode 100644 index 000000000..db76ddedb --- /dev/null +++ b/contracts/libs/contracts/libs/LibAddressArray.sol @@ -0,0 +1,75 @@ +/* + + 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; + +import "@0x/contracts-utils/contracts/utils/LibBytes/LibBytes.sol"; + + +library LibAddressArray { + + /// @dev Append a new address to an array of addresses. + /// The `addressArray` may need to be reallocated to make space + /// for the new address. Because of this we return the resulting + /// memory location of `addressArray`. + /// @param addressToAppend Address to append. + /// @return Array of addresses: [... addressArray, addressToAppend] + function append(address[] memory addressArray, address addressToAppend) + internal pure + returns (address[]) + { + // Get stats on address array and free memory + uint256 freeMemPtr = 0; + uint256 addressArrayBeginPtr = 0; + uint256 addressArrayEndPtr = 0; + uint256 addressArrayLength = addressArray.length; + uint256 addressArrayMemSizeInBytes = 32 + (32 * addressArrayLength); + assembly { + freeMemPtr := mload(0x40) + addressArrayBeginPtr := addressArray + addressArrayEndPtr := add(addressArray, addressArrayMemSizeInBytes) + } + + // If free memory begins at the end of `addressArray` + // then we can append `addressToAppend` directly. + // Otherwise, we must copy the array to free memory + // before appending new values to it. + if (freeMemPtr != addressArrayEndPtr) { + LibBytes.memCopy(freeMemPtr, addressArrayBeginPtr, addressArrayMemSizeInBytes); + assembly { + addressArray := freeMemPtr + addressArrayBeginPtr := addressArray + } + } + + // Append `addressToAppend` + addressArrayLength += 1; + addressArrayMemSizeInBytes += 32; + addressArrayEndPtr = addressArrayBeginPtr + addressArrayMemSizeInBytes; + freeMemPtr = addressArrayEndPtr; + assembly { + // Store new array length + mstore(addressArray, addressArrayLength) + + // Update `freeMemPtr` + mstore(0x40, freeMemPtr) + } + addressArray[addressArrayLength - 1] = addressToAppend; + return addressArray; + } +} diff --git a/contracts/libs/contracts/libs/LibExchangeSelectors.sol b/contracts/libs/contracts/libs/LibExchangeSelectors.sol new file mode 100644 index 000000000..6a5344340 --- /dev/null +++ b/contracts/libs/contracts/libs/LibExchangeSelectors.sol @@ -0,0 +1,151 @@ +/* + + 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 LibExchangeSelectors { + + // 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 v1.2.3 From b524ac7af2ebf0f00c50ae3c1ad7cd85ab8b6462 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 13 Dec 2018 15:58:58 -0800 Subject: Prettier / Linter on contracts + TS --- .../MixinBalanceThresholdFilterCore.sol | 14 +-- .../MixinExchangeCalldata.sol | 5 +- .../interfaces/IBalanceThresholdFilterCore.sol | 3 +- .../mixins/MExchangeCalldata.sol | 3 +- .../test/extensions/balance_threshold_filter.ts | 109 ++++++--------------- 5 files changed, 44 insertions(+), 90 deletions(-) diff --git a/contracts/extensions/contracts/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol b/contracts/extensions/contracts/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol index a8947751a..27767ad98 100644 --- a/contracts/extensions/contracts/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol +++ b/contracts/extensions/contracts/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol @@ -68,7 +68,7 @@ contract MixinBalanceThresholdFilterCore is // Validate account balances uint256 balanceThreshold = BALANCE_THRESHOLD; IThresholdAsset thresholdAsset = THRESHOLD_ASSET; - for(uint256 i = 0; i < addressesToValidate.length; ++i) { + for (uint256 i = 0; i < addressesToValidate.length; ++i) { uint256 addressBalance = thresholdAsset.balanceOf(addressesToValidate[i]); if (addressBalance < balanceThreshold) { revert("AT_LEAST_ONE_ADDRESS_DOES_NOT_MEET_BALANCE_THRESHOLD"); @@ -95,8 +95,9 @@ contract MixinBalanceThresholdFilterCore is returns (address[] memory addressesToValidate) { bytes4 exchangeFunctionSelector = bytes4(exchangeCalldataload(0)); - if( - exchangeFunctionSelector == batchFillOrdersSelector || + // solhint-disable expression-indent + if ( + exchangeFunctionSelector == batchFillOrdersSelector || exchangeFunctionSelector == batchFillOrdersNoThrowSelector || exchangeFunctionSelector == batchFillOrKillOrdersSelector || exchangeFunctionSelector == marketBuyOrdersSelector || @@ -106,7 +107,7 @@ contract MixinBalanceThresholdFilterCore is ) { addressesToValidate = loadMakerAddressesFromOrderArray(0); addressesToValidate = addressesToValidate.append(signerAddress); - } else if( + } else if ( exchangeFunctionSelector == fillOrderSelector || exchangeFunctionSelector == fillOrderNoThrowSelector || exchangeFunctionSelector == fillOrKillOrderSelector @@ -114,18 +115,19 @@ contract MixinBalanceThresholdFilterCore is address makerAddress = loadMakerAddressFromOrder(0); addressesToValidate = addressesToValidate.append(makerAddress); addressesToValidate = addressesToValidate.append(signerAddress); - } else if(exchangeFunctionSelector == matchOrdersSelector) { + } else if (exchangeFunctionSelector == matchOrdersSelector) { address leftMakerAddress = loadMakerAddressFromOrder(0); addressesToValidate = addressesToValidate.append(leftMakerAddress); address rightMakerAddress = loadMakerAddressFromOrder(1); addressesToValidate = addressesToValidate.append(rightMakerAddress); addressesToValidate = addressesToValidate.append(signerAddress); - } else if( + } else if ( exchangeFunctionSelector != cancelOrderSelector && exchangeFunctionSelector != batchCancelOrdersSelector && exchangeFunctionSelector != cancelOrdersUpToSelector ) { revert("INVALID_OR_BLOCKED_EXCHANGE_SELECTOR"); } + // solhint-enable expression-indent } } diff --git a/contracts/extensions/contracts/BalanceThresholdFilter/MixinExchangeCalldata.sol b/contracts/extensions/contracts/BalanceThresholdFilter/MixinExchangeCalldata.sol index 12f601dea..d49b7123f 100644 --- a/contracts/extensions/contracts/BalanceThresholdFilter/MixinExchangeCalldata.sol +++ b/contracts/extensions/contracts/BalanceThresholdFilter/MixinExchangeCalldata.sol @@ -78,8 +78,7 @@ contract MixinExchangeCalldata is } /// @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. + /// (which is embedded in `signedExchangeTransaction`). /// @param orderArrayParamIndex Index of the order array in the Exchange function's signature /// @return makerAddresses The extracted maker addresses. function loadMakerAddressesFromOrderArray(uint256 orderArrayParamIndex) @@ -92,7 +91,7 @@ contract MixinExchangeCalldata is uint256 orderArrayLengthInBytes = orderArrayLength * 32; uint256 orderArrayElementPtr = orderArrayPtr + 32; uint256 orderArrayElementEndPtr = orderArrayElementPtr + orderArrayLengthInBytes; - for(uint orderPtrOffset = orderArrayElementPtr; orderPtrOffset < orderArrayElementEndPtr; orderPtrOffset += 32) { + for (uint orderPtrOffset = orderArrayElementPtr; orderPtrOffset < orderArrayElementEndPtr; orderPtrOffset += 32) { uint256 orderPtr = uint256(loadExchangeData(orderPtrOffset)); address makerAddress = address(loadExchangeData(orderPtr + orderArrayElementPtr)); makerAddresses = makerAddresses.append(makerAddress); diff --git a/contracts/extensions/contracts/BalanceThresholdFilter/interfaces/IBalanceThresholdFilterCore.sol b/contracts/extensions/contracts/BalanceThresholdFilter/interfaces/IBalanceThresholdFilterCore.sol index 37e607be1..3d8e2bbd1 100644 --- a/contracts/extensions/contracts/BalanceThresholdFilter/interfaces/IBalanceThresholdFilterCore.sol +++ b/contracts/extensions/contracts/BalanceThresholdFilter/interfaces/IBalanceThresholdFilterCore.sol @@ -20,7 +20,6 @@ pragma solidity 0.4.24; - contract IBalanceThresholdFilterCore { /// @dev Executes an Exchange transaction iff the maker and taker meet @@ -53,4 +52,4 @@ contract IBalanceThresholdFilterCore { bytes signature ) external; -} \ No newline at end of file +} diff --git a/contracts/extensions/contracts/BalanceThresholdFilter/mixins/MExchangeCalldata.sol b/contracts/extensions/contracts/BalanceThresholdFilter/mixins/MExchangeCalldata.sol index 8e0414c17..bf2940fe1 100644 --- a/contracts/extensions/contracts/BalanceThresholdFilter/mixins/MExchangeCalldata.sol +++ b/contracts/extensions/contracts/BalanceThresholdFilter/mixins/MExchangeCalldata.sol @@ -47,8 +47,7 @@ contract MExchangeCalldata { returns (address 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. + /// (which is embedded in `signedExchangeTransaction`). /// @param orderArrayParamIndex Index of the order array in the Exchange function's signature /// @return makerAddresses The extracted maker addresses. function loadMakerAddressesFromOrderArray(uint256 orderArrayParamIndex) diff --git a/contracts/extensions/test/extensions/balance_threshold_filter.ts b/contracts/extensions/test/extensions/balance_threshold_filter.ts index 6350c019f..20397b14f 100644 --- a/contracts/extensions/test/extensions/balance_threshold_filter.ts +++ b/contracts/extensions/test/extensions/balance_threshold_filter.ts @@ -5,7 +5,6 @@ import { BigNumber } from '@0x/utils'; import { Web3Wrapper } from '@0x/web3-wrapper'; import * as chai from 'chai'; import { TransactionReceiptWithDecodedLogs } from 'ethereum-types'; -import * as ethUtil from 'ethereumjs-util'; import * as _ from 'lodash'; import { @@ -21,12 +20,9 @@ import { ContractName, ERC20BalancesByOwner, expectTransactionFailedAsync, - expectTransactionFailedWithoutReasonAsync, OrderFactory, OrderStatus, - orderUtils, provider, - SignedTransaction, TransactionFactory, txDefaults, web3Wrapper, @@ -77,7 +73,6 @@ describe(ContractName.BalanceThresholdFilter, () => { let defaultOrderParams: Partial; let validSignedOrder: SignedOrder; let validSignedOrder2: SignedOrder; - let validSignedFillOrderTx: SignedTransaction; let erc721BalanceThresholdFilterInstance: BalanceThresholdFilterContract; let erc20BalanceThresholdFilterInstance: BalanceThresholdFilterContract; @@ -257,10 +252,7 @@ describe(ContractName.BalanceThresholdFilter, () => { { takerAssetFillAmount }, ); // Assert validated addresses - const expectedValidatedAddresseses = [ - validSignedOrder.makerAddress, - validTakerAddress, - ]; + const expectedValidatedAddresseses = [validSignedOrder.makerAddress, validTakerAddress]; await assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); // Check balances const newBalances = await erc20Wrapper.getBalancesAsync(); @@ -308,7 +300,7 @@ describe(ContractName.BalanceThresholdFilter, () => { badSelectorHex, signatureHex, ), - RevertReason.InvalidOrBlockedExchangeSelector + RevertReason.InvalidOrBlockedExchangeSelector, ); }); it('should revert if senderAddress is not set to the valid forwarding contract', async () => { @@ -319,12 +311,10 @@ describe(ContractName.BalanceThresholdFilter, () => { }); // Call valid forwarder return expectTransactionFailedAsync( - erc721TakerBalanceThresholdWrapper.fillOrderAsync( - signedOrderWithBadSenderAddress, - validTakerAddress, - { takerAssetFillAmount }, - ), - RevertReason.FailedExecution + erc721TakerBalanceThresholdWrapper.fillOrderAsync(signedOrderWithBadSenderAddress, validTakerAddress, { + takerAssetFillAmount, + }), + RevertReason.FailedExecution, ); }); }); @@ -339,11 +329,9 @@ describe(ContractName.BalanceThresholdFilter, () => { // Execute a valid fill const orders = [validSignedOrder, validSignedOrder2]; const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; - const txReceipt = await erc721TakerBalanceThresholdWrapper.batchFillOrdersAsync( - orders, - validTakerAddress, - { takerAssetFillAmounts }, - ); + const txReceipt = await erc721TakerBalanceThresholdWrapper.batchFillOrdersAsync(orders, validTakerAddress, { + takerAssetFillAmounts, + }); // Assert validated addresses const expectedValidatedAddresseses = [ validSignedOrder.makerAddress, @@ -647,10 +635,7 @@ describe(ContractName.BalanceThresholdFilter, () => { { takerAssetFillAmount }, ); // Assert validated addresses - const expectedValidatedAddresseses = [ - validSignedOrder.makerAddress, - validTakerAddress, - ]; + const expectedValidatedAddresseses = [validSignedOrder.makerAddress, validTakerAddress]; await assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); // Check balances const newBalances = await erc20Wrapper.getBalancesAsync(); @@ -693,11 +678,9 @@ describe(ContractName.BalanceThresholdFilter, () => { }); // Execute transaction return expectTransactionFailedAsync( - erc721TakerBalanceThresholdWrapper.fillOrderAsync( - signedOrderWithBadMakerAddress, - validTakerAddress, - { takerAssetFillAmount }, - ), + erc721TakerBalanceThresholdWrapper.fillOrderAsync(signedOrderWithBadMakerAddress, validTakerAddress, { + takerAssetFillAmount, + }), RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold, ); }); @@ -724,10 +707,7 @@ describe(ContractName.BalanceThresholdFilter, () => { { takerAssetFillAmount }, ); // Assert validated addresses - const expectedValidatedAddresseses = [ - validSignedOrder.makerAddress, - validTakerAddress, - ]; + const expectedValidatedAddresseses = [validSignedOrder.makerAddress, validTakerAddress]; await assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); // Check balances const newBalances = await erc20Wrapper.getBalancesAsync(); @@ -780,11 +760,9 @@ describe(ContractName.BalanceThresholdFilter, () => { }); it('should revert if taker does not meet the balance threshold', async () => { return expectTransactionFailedAsync( - erc721NonValidBalanceThresholdWrapper.fillOrderNoThrowAsync( - validSignedOrder, - invalidAddress, - { takerAssetFillAmount }, - ), + erc721NonValidBalanceThresholdWrapper.fillOrderNoThrowAsync(validSignedOrder, invalidAddress, { + takerAssetFillAmount, + }), RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold, ); }); @@ -804,10 +782,7 @@ describe(ContractName.BalanceThresholdFilter, () => { { takerAssetFillAmount: takerAssetFillAmount_ }, ); // Assert validated addresses - const expectedValidatedAddresseses = [ - validSignedOrder.makerAddress, - validTakerAddress, - ]; + const expectedValidatedAddresseses = [validSignedOrder.makerAddress, validTakerAddress]; await assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); // Check balances const newBalances = await erc20Wrapper.getBalancesAsync(); @@ -860,11 +835,9 @@ describe(ContractName.BalanceThresholdFilter, () => { }); it('should revert if taker does not meet the balance threshold', async () => { return expectTransactionFailedAsync( - erc721NonValidBalanceThresholdWrapper.fillOrKillOrderAsync( - validSignedOrder, - invalidAddress, - { takerAssetFillAmount }, - ), + erc721NonValidBalanceThresholdWrapper.fillOrKillOrderAsync(validSignedOrder, invalidAddress, { + takerAssetFillAmount, + }), RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold, ); }); @@ -916,14 +889,10 @@ describe(ContractName.BalanceThresholdFilter, () => { const cumulativeMakerAssetFillAmount = validSignedOrder.makerAssetAmount.plus(makerAssetFillAmount2); // Maker #1 expect(newBalances[validMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[validMakerAddress][defaultMakerAssetAddress].minus( - validSignedOrder.makerAssetAmount, - ), + erc20Balances[validMakerAddress][defaultMakerAssetAddress].minus(validSignedOrder.makerAssetAmount), ); expect(newBalances[validMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[validMakerAddress][defaultTakerAssetAddress].add( - validSignedOrder.takerAssetAmount, - ), + erc20Balances[validMakerAddress][defaultTakerAssetAddress].add(validSignedOrder.takerAssetAmount), ); expect(newBalances[validMakerAddress][zrxToken.address]).to.be.bignumber.equal( erc20Balances[validMakerAddress][zrxToken.address].minus(validSignedOrder.makerFee), @@ -1018,14 +987,10 @@ describe(ContractName.BalanceThresholdFilter, () => { const cumulativeMakerAssetFillAmount = validSignedOrder.makerAssetAmount.plus(makerAssetFillAmount2); // Maker #1 expect(newBalances[validMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[validMakerAddress][defaultMakerAssetAddress].minus( - validSignedOrder.makerAssetAmount, - ), + erc20Balances[validMakerAddress][defaultMakerAssetAddress].minus(validSignedOrder.makerAssetAmount), ); expect(newBalances[validMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[validMakerAddress][defaultTakerAssetAddress].add( - validSignedOrder.takerAssetAmount, - ), + erc20Balances[validMakerAddress][defaultTakerAssetAddress].add(validSignedOrder.takerAssetAmount), ); expect(newBalances[validMakerAddress][zrxToken.address]).to.be.bignumber.equal( erc20Balances[validMakerAddress][zrxToken.address].minus(validSignedOrder.makerFee), @@ -1097,11 +1062,9 @@ describe(ContractName.BalanceThresholdFilter, () => { .times(validSignedOrder.makerAssetAmount) .dividedToIntegerBy(validSignedOrder.takerAssetAmount); const cumulativeMakerAssetFillAmount = validSignedOrder.makerAssetAmount.plus(makerAssetFillAmount2); - const txReceipt = await erc721TakerBalanceThresholdWrapper.marketBuyOrdersAsync( - orders, - validTakerAddress, - { makerAssetFillAmount: cumulativeMakerAssetFillAmount }, - ); + const txReceipt = await erc721TakerBalanceThresholdWrapper.marketBuyOrdersAsync(orders, validTakerAddress, { + makerAssetFillAmount: cumulativeMakerAssetFillAmount, + }); // Assert validated addresses const expectedValidatedAddresseses = [ validSignedOrder.makerAddress, @@ -1120,14 +1083,10 @@ describe(ContractName.BalanceThresholdFilter, () => { const takerFeePaid = validSignedOrder.takerFee.plus(takerFeePaid2); // Maker #1 expect(newBalances[validMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[validMakerAddress][defaultMakerAssetAddress].minus( - validSignedOrder.makerAssetAmount, - ), + erc20Balances[validMakerAddress][defaultMakerAssetAddress].minus(validSignedOrder.makerAssetAmount), ); expect(newBalances[validMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[validMakerAddress][defaultTakerAssetAddress].add( - validSignedOrder.takerAssetAmount, - ), + erc20Balances[validMakerAddress][defaultTakerAssetAddress].add(validSignedOrder.takerAssetAmount), ); expect(newBalances[validMakerAddress][zrxToken.address]).to.be.bignumber.equal( erc20Balances[validMakerAddress][zrxToken.address].minus(validSignedOrder.makerFee), @@ -1224,14 +1183,10 @@ describe(ContractName.BalanceThresholdFilter, () => { const takerFeePaid = validSignedOrder.takerFee.plus(takerFeePaid2); // Maker #1 expect(newBalances[validMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[validMakerAddress][defaultMakerAssetAddress].minus( - validSignedOrder.makerAssetAmount, - ), + erc20Balances[validMakerAddress][defaultMakerAssetAddress].minus(validSignedOrder.makerAssetAmount), ); expect(newBalances[validMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[validMakerAddress][defaultTakerAssetAddress].add( - validSignedOrder.takerAssetAmount, - ), + erc20Balances[validMakerAddress][defaultTakerAssetAddress].add(validSignedOrder.takerAssetAmount), ); expect(newBalances[validMakerAddress][zrxToken.address]).to.be.bignumber.equal( erc20Balances[validMakerAddress][zrxToken.address].minus(validSignedOrder.makerFee), -- cgit v1.2.3 From bb4ce9b3ad5658b16027c53cb4bad07149dcff4e Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 13 Dec 2018 16:02:13 -0800 Subject: Explicit returns --- .../BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol | 1 + .../contracts/BalanceThresholdFilter/MixinExchangeCalldata.sol | 2 ++ 2 files changed, 3 insertions(+) diff --git a/contracts/extensions/contracts/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol b/contracts/extensions/contracts/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol index 27767ad98..3e292c035 100644 --- a/contracts/extensions/contracts/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol +++ b/contracts/extensions/contracts/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol @@ -129,5 +129,6 @@ contract MixinBalanceThresholdFilterCore is revert("INVALID_OR_BLOCKED_EXCHANGE_SELECTOR"); } // solhint-enable expression-indent + return addressesToValidate; } } diff --git a/contracts/extensions/contracts/BalanceThresholdFilter/MixinExchangeCalldata.sol b/contracts/extensions/contracts/BalanceThresholdFilter/MixinExchangeCalldata.sol index d49b7123f..bd26a468f 100644 --- a/contracts/extensions/contracts/BalanceThresholdFilter/MixinExchangeCalldata.sol +++ b/contracts/extensions/contracts/BalanceThresholdFilter/MixinExchangeCalldata.sol @@ -50,6 +50,7 @@ contract MixinExchangeCalldata is let exchangeCalldataOffset := add(exchangeTxPtr, add(0x24, offset)) value := calldataload(exchangeCalldataOffset) } + return value; } /// @dev Convenience function that skips the 4 byte selector when loading @@ -61,6 +62,7 @@ contract MixinExchangeCalldata is returns (bytes32 value) { value = exchangeCalldataload(offset + 4); + return value; } /// @dev Extracts the maker address from an order stored in the Exchange calldata -- cgit v1.2.3 From 25722d81549382617df1c70e833748782ad3e3ac Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 13 Dec 2018 16:10:40 -0800 Subject: Updated comment `Execute fillOrder` -> `Execute exchange function` --- .../BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/extensions/contracts/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol b/contracts/extensions/contracts/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol index 3e292c035..2917403bd 100644 --- a/contracts/extensions/contracts/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol +++ b/contracts/extensions/contracts/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol @@ -76,7 +76,7 @@ contract MixinBalanceThresholdFilterCore is } emit ValidatedAddresses(addressesToValidate); - // All addresses are valid. Execute fillOrder. + // All addresses are valid. Execute exchange function. EXCHANGE.executeTransaction( salt, signerAddress, -- cgit v1.2.3 From 1de92659ee5d3cad687919bd3054d24e31461bfa Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 13 Dec 2018 18:07:29 -0800 Subject: Added Changelog for new Extensions --- contracts/extensions/CHANGELOG.json | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/contracts/extensions/CHANGELOG.json b/contracts/extensions/CHANGELOG.json index 19ac770af..99bc0265e 100644 --- a/contracts/extensions/CHANGELOG.json +++ b/contracts/extensions/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "timestamp": 1544753227, + "version": "1.0.3", + "changes": [ + { + "note": "Added Balance Threshold Filter" + } + ] + }, { "timestamp": 1544741676, "version": "1.0.2", -- cgit v1.2.3 From 00f5b94d0a585ccf4b1df4c9bddb4c19eb86cfe2 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Fri, 14 Dec 2018 11:22:52 -0800 Subject: Added `gas` field so tests pass on Geth; --- .../test/extensions/balance_threshold_filter.ts | 36 ++++++++++++++++++---- 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/contracts/extensions/test/extensions/balance_threshold_filter.ts b/contracts/extensions/test/extensions/balance_threshold_filter.ts index 20397b14f..07199d60b 100644 --- a/contracts/extensions/test/extensions/balance_threshold_filter.ts +++ b/contracts/extensions/test/extensions/balance_threshold_filter.ts @@ -428,7 +428,13 @@ describe(ContractName.BalanceThresholdFilter, () => { const txReceipt = await erc721TakerBalanceThresholdWrapper.batchFillOrdersNoThrowAsync( orders, validTakerAddress, - { takerAssetFillAmounts }, + { + takerAssetFillAmounts, + // HACK(albrow): We need to hardcode the gas estimate here because + // the Geth gas estimator doesn't work with the way we use + // delegatecall and swallow errors. + gas: 600000, + }, ); // Assert validated addresses const expectedValidatedAddresseses = [ @@ -704,7 +710,13 @@ describe(ContractName.BalanceThresholdFilter, () => { const txReceipt = await erc721TakerBalanceThresholdWrapper.fillOrderNoThrowAsync( validSignedOrder, validTakerAddress, - { takerAssetFillAmount }, + { + takerAssetFillAmount, + // HACK(albrow): We need to hardcode the gas estimate here because + // the Geth gas estimator doesn't work with the way we use + // delegatecall and swallow errors. + gas: 600000, + }, ); // Assert validated addresses const expectedValidatedAddresseses = [validSignedOrder.makerAddress, validTakerAddress]; @@ -963,7 +975,13 @@ describe(ContractName.BalanceThresholdFilter, () => { const txReceipt = await erc721TakerBalanceThresholdWrapper.marketSellOrdersNoThrowAsync( orders, validTakerAddress, - { takerAssetFillAmount: cumulativeTakerAssetFillAmount }, + { + takerAssetFillAmount: cumulativeTakerAssetFillAmount, + // HACK(albrow): We need to hardcode the gas estimate here because + // the Geth gas estimator doesn't work with the way we use + // delegatecall and swallow errors. + gas: 600000, + }, ); // Assert validated addresses const expectedValidatedAddresseses = [ @@ -1163,7 +1181,13 @@ describe(ContractName.BalanceThresholdFilter, () => { const txReceipt = await erc721TakerBalanceThresholdWrapper.marketBuyOrdersNoThrowAsync( orders, validTakerAddress, - { makerAssetFillAmount: cumulativeMakerAssetFillAmount }, + { + makerAssetFillAmount: cumulativeMakerAssetFillAmount, + // HACK(albrow): We need to hardcode the gas estimate here because + // the Geth gas estimator doesn't work with the way we use + // delegatecall and swallow errors. + gas: 600000, + }, ); // Assert validated addresses const expectedValidatedAddresseses = [ @@ -1493,7 +1517,7 @@ describe(ContractName.BalanceThresholdFilter, () => { // Cancel const txReceipt = await erc721MakerBalanceThresholdWrapper.batchCancelOrdersAsync( validSignedOrders, - validSignedOrder.makerAddress, + validSignedOrders[0].makerAddress, ); // Assert validated addresses const expectedValidatedAddresseses: string[] = []; @@ -1560,7 +1584,7 @@ describe(ContractName.BalanceThresholdFilter, () => { const cancelOrdersUpToThisSalt = new BigNumber(1); const txReceipt = await erc721MakerBalanceThresholdWrapper.cancelOrdersUpToAsync( cancelOrdersUpToThisSalt, - validSignedOrder.makerAddress, + validSignedOrders[0].makerAddress, ); // Assert validated addresses const expectedValidatedAddresseses: string[] = []; -- cgit v1.2.3 From afe200c4e1bd76d5f2dbfcb7898ca025a7bb3dd6 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Fri, 14 Dec 2018 11:26:38 -0800 Subject: Updated changelogs for new contracts --- contracts/extensions/CHANGELOG.json | 6 +++--- contracts/protocol/CHANGELOG.json | 9 +++++++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/contracts/extensions/CHANGELOG.json b/contracts/extensions/CHANGELOG.json index 99bc0265e..da4d9c2ba 100644 --- a/contracts/extensions/CHANGELOG.json +++ b/contracts/extensions/CHANGELOG.json @@ -1,10 +1,10 @@ [ { - "timestamp": 1544753227, - "version": "1.0.3", + "version": "1.1.0", "changes": [ { - "note": "Added Balance Threshold Filter" + "note": "Added Balance Threshold Filter", + "pr": 1383 } ] }, diff --git a/contracts/protocol/CHANGELOG.json b/contracts/protocol/CHANGELOG.json index 5c3798a69..2dca9794a 100644 --- a/contracts/protocol/CHANGELOG.json +++ b/contracts/protocol/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "version": "2.2.0", + "changes": [ + { + "note": "Added LibAddressArray", + "pr": 1383 + } + ] + }, { "timestamp": 1544741676, "version": "2.1.59", -- cgit v1.2.3 From d2a4fd570622de34a3d8e8a25735b025e3ac5f77 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 18 Dec 2018 11:36:05 -0800 Subject: Added documentation to `LibAddressArray.append` and switched `if` to `require` smt --- .../MixinBalanceThresholdFilterCore.sol | 7 ++++--- contracts/libs/contracts/libs/LibAddressArray.sol | 11 ++++++++++- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/contracts/extensions/contracts/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol b/contracts/extensions/contracts/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol index 2917403bd..ab6989115 100644 --- a/contracts/extensions/contracts/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol +++ b/contracts/extensions/contracts/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol @@ -70,9 +70,10 @@ contract MixinBalanceThresholdFilterCore is IThresholdAsset thresholdAsset = THRESHOLD_ASSET; for (uint256 i = 0; i < addressesToValidate.length; ++i) { uint256 addressBalance = thresholdAsset.balanceOf(addressesToValidate[i]); - if (addressBalance < balanceThreshold) { - revert("AT_LEAST_ONE_ADDRESS_DOES_NOT_MEET_BALANCE_THRESHOLD"); - } + require( + addressBalance >= balanceThreshold, + "AT_LEAST_ONE_ADDRESS_DOES_NOT_MEET_BALANCE_THRESHOLD" + ); } emit ValidatedAddresses(addressesToValidate); diff --git a/contracts/libs/contracts/libs/LibAddressArray.sol b/contracts/libs/contracts/libs/LibAddressArray.sol index db76ddedb..ccae2ac5f 100644 --- a/contracts/libs/contracts/libs/LibAddressArray.sol +++ b/contracts/libs/contracts/libs/LibAddressArray.sol @@ -45,11 +45,20 @@ library LibAddressArray { addressArrayEndPtr := add(addressArray, addressArrayMemSizeInBytes) } + // Cases for `freeMemPtr`: + // `freeMemPtr` == `addressArrayEndPtr`: Nothing occupies memory after `addressArray` + // `freeMemPtr` > `addressArrayEndPtr`: Some value occupies memory after `addressArray` + // `freeMemPtr` < `addressArrayEndPtr`: Memory has not been managed properly. + require( + freeMemPtr >= addressArrayEndPtr, + "INVALID_FREE_MEMORY_PTR" + ); + // If free memory begins at the end of `addressArray` // then we can append `addressToAppend` directly. // Otherwise, we must copy the array to free memory // before appending new values to it. - if (freeMemPtr != addressArrayEndPtr) { + if (freeMemPtr > addressArrayEndPtr) { LibBytes.memCopy(freeMemPtr, addressArrayBeginPtr, addressArrayMemSizeInBytes); assembly { addressArray := freeMemPtr -- cgit v1.2.3 From ca0ab385218d588aee10502e3d75ade30ad0b5d7 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 18 Dec 2018 11:53:41 -0800 Subject: Fixed solhint errors --- .../MixinBalanceThresholdFilterCore.sol | 28 +-- .../libs/contracts/libs/LibExchangeSelectors.sol | 231 +++++++++++---------- 2 files changed, 130 insertions(+), 129 deletions(-) diff --git a/contracts/extensions/contracts/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol b/contracts/extensions/contracts/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol index ab6989115..df830f36e 100644 --- a/contracts/extensions/contracts/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol +++ b/contracts/extensions/contracts/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol @@ -98,34 +98,34 @@ contract MixinBalanceThresholdFilterCore is bytes4 exchangeFunctionSelector = bytes4(exchangeCalldataload(0)); // solhint-disable expression-indent if ( - exchangeFunctionSelector == batchFillOrdersSelector || - exchangeFunctionSelector == batchFillOrdersNoThrowSelector || - exchangeFunctionSelector == batchFillOrKillOrdersSelector || - exchangeFunctionSelector == marketBuyOrdersSelector || - exchangeFunctionSelector == marketBuyOrdersNoThrowSelector || - exchangeFunctionSelector == marketSellOrdersSelector || - exchangeFunctionSelector == marketSellOrdersNoThrowSelector + exchangeFunctionSelector == BATCH_FILL_ORDERS_SELECTOR || + exchangeFunctionSelector == BATCH_FILL_ORDERS_NO_THROW_SELECTOR || + exchangeFunctionSelector == BATCH_FILL_OR_KILL_ORDERS_SELECTOR || + exchangeFunctionSelector == MARKET_BUY_ORDERS_SELECTOR || + exchangeFunctionSelector == MARKET_BUY_ORDERS_NO_THROW_SELECTOR || + exchangeFunctionSelector == MARKET_SELL_ORDERS_SELECTOR || + exchangeFunctionSelector == MARKET_SELL_ORDERS_NO_THROW_SELECTOR ) { addressesToValidate = loadMakerAddressesFromOrderArray(0); addressesToValidate = addressesToValidate.append(signerAddress); } else if ( - exchangeFunctionSelector == fillOrderSelector || - exchangeFunctionSelector == fillOrderNoThrowSelector || - exchangeFunctionSelector == fillOrKillOrderSelector + exchangeFunctionSelector == FILL_ORDER_SELECTOR || + exchangeFunctionSelector == FILL_ORDER_NO_THROW_SELECTOR || + exchangeFunctionSelector == FILL_OR_KILL_ORDER_SELECTOR ) { address makerAddress = loadMakerAddressFromOrder(0); addressesToValidate = addressesToValidate.append(makerAddress); addressesToValidate = addressesToValidate.append(signerAddress); - } else if (exchangeFunctionSelector == matchOrdersSelector) { + } else if (exchangeFunctionSelector == MATCH_ORDERS_SELECTOR) { address leftMakerAddress = loadMakerAddressFromOrder(0); addressesToValidate = addressesToValidate.append(leftMakerAddress); address rightMakerAddress = loadMakerAddressFromOrder(1); addressesToValidate = addressesToValidate.append(rightMakerAddress); addressesToValidate = addressesToValidate.append(signerAddress); } else if ( - exchangeFunctionSelector != cancelOrderSelector && - exchangeFunctionSelector != batchCancelOrdersSelector && - exchangeFunctionSelector != cancelOrdersUpToSelector + exchangeFunctionSelector != CANCEL_ORDER_SELECTOR && + exchangeFunctionSelector != BATCH_CANCEL_ORDERS_SELECTOR && + exchangeFunctionSelector != CANCEL_ORDERS_UP_TO_SELECTOR ) { revert("INVALID_OR_BLOCKED_EXCHANGE_SELECTOR"); } diff --git a/contracts/libs/contracts/libs/LibExchangeSelectors.sol b/contracts/libs/contracts/libs/LibExchangeSelectors.sol index 6a5344340..edb4f9cbd 100644 --- a/contracts/libs/contracts/libs/LibExchangeSelectors.sol +++ b/contracts/libs/contracts/libs/LibExchangeSelectors.sol @@ -21,131 +21,132 @@ pragma solidity 0.4.24; contract LibExchangeSelectors { - // allowedValidators - bytes4 constant allowedValidatorsSelector = 0x7b8e3514; - bytes4 constant allowedValidatorsSelectorGenerator = bytes4(keccak256('allowedValidators(address,address)')); + // solhint-disable max-line-length + // allowedValidators + bytes4 constant public ALLOWED_VALIDATORS_SELECTOR = 0x7b8e3514; + bytes4 constant public ALLOWED_VALIDATORS_SELECTOR_GENERATOR = bytes4(keccak256("allowedValidators(address,address)")); - // assetProxies - bytes4 constant assetProxiesSelector = 0x3fd3c997; - bytes4 constant assetProxiesSelectorGenerator = bytes4(keccak256('assetProxies(bytes4)')); + // assetProxies + bytes4 constant public ASSET_PROXIES_SELECTOR = 0x3fd3c997; + bytes4 constant public ASSET_PROXIES_SELECTOR_GENERATOR = 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)[])')); + // batchCancelOrders + bytes4 constant public BATCH_CANCEL_ORDERS_SELECTOR = 0x4ac14782; + bytes4 constant public BATCH_CANCEL_ORDERS_SELECTOR_GENERATOR = 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[])')); + // batchFillOrKillOrders + bytes4 constant public BATCH_FILL_OR_KILL_ORDERS_SELECTOR = 0x4d0ae546; + bytes4 constant public BATCH_FILL_OR_KILL_ORDERS_SELECTOR_GENERATOR = 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[])')); + // batchFillOrders + bytes4 constant public BATCH_FILL_ORDERS_SELECTOR = 0x297bb70b; + bytes4 constant public BATCH_FILL_ORDERS_SELECTOR_GENERATOR = 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[])')); + // batchFillOrdersNoThrow + bytes4 constant public BATCH_FILL_ORDERS_NO_THROW_SELECTOR = 0x50dde190; + bytes4 constant public BATCH_FILL_ORDERS_NO_THROW_SELECTOR_GENERATOR = 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))')); + // cancelOrder + bytes4 constant public CANCEL_ORDER_SELECTOR = 0xd46b02c3; + bytes4 constant public CANCEL_ORDER_SELECTOR_GENERATOR = bytes4(keccak256("cancelOrder((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes))")); + + // cancelOrdersUpTo + bytes4 constant public CANCEL_ORDERS_UP_TO_SELECTOR = 0x4f9559b1; + bytes4 constant public CANCEL_ORDERS_UP_TO_SELECTOR_GENERATOR = bytes4(keccak256("cancelOrdersUpTo(uint256)")); + + // cancelled + bytes4 constant public CANCELLED_SELECTOR = 0x2ac12622; + bytes4 constant public CANCELLED_SELECTOR_GENERATOR = bytes4(keccak256("cancelled(bytes32)")); + + // currentContextAddress + bytes4 constant public CURRENT_CONTEXT_ADDRESS_SELECTOR = 0xeea086ba; + bytes4 constant public CURRENT_CONTEXT_ADDRESS_SELECTOR_GENERATOR = bytes4(keccak256("currentContextAddress()")); + + // executeTransaction + bytes4 constant public EXECUTE_TRANSACTION_SELECTOR = 0xbfc8bfce; + bytes4 constant public EXECUTE_TRANSACTION_SELECTOR_GENERATOR = bytes4(keccak256("executeTransaction(uint256,address,bytes,bytes)")); + + // fillOrKillOrder + bytes4 constant public FILL_OR_KILL_ORDER_SELECTOR = 0x64a3bc15; + bytes4 constant public FILL_OR_KILL_ORDER_SELECTOR_GENERATOR = bytes4(keccak256("fillOrKillOrder((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),uint256,bytes)")); + + // fillOrder + bytes4 constant public FILL_ORDER_SELECTOR = 0xb4be83d5; + bytes4 constant public FILL_ORDER_SELECTOR_GENERATOR = bytes4(keccak256("fillOrder((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),uint256,bytes)")); + + // fillOrderNoThrow + bytes4 constant public FILL_ORDER_NO_THROW_SELECTOR = 0x3e228bae; + bytes4 constant public FILL_ORDER_NO_THROW_SELECTOR_GENERATOR = bytes4(keccak256("fillOrderNoThrow((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),uint256,bytes)")); + + // filled + bytes4 constant public FILLED_SELECTOR = 0x288cdc91; + bytes4 constant public FILLED_SELECTOR_GENERATOR = bytes4(keccak256("filled(bytes32)")); + + // getAssetProxy + bytes4 constant public GET_ASSET_PROXY_SELECTOR = 0x60704108; + bytes4 constant public GET_ASSET_PROXY_SELECTOR_GENERATOR = bytes4(keccak256("getAssetProxy(bytes4)")); + + // getOrderInfo + bytes4 constant public GET_ORDER_INFO_SELECTOR = 0xc75e0a81; + bytes4 constant public GET_ORDER_INFO_SELECTOR_GENERATOR = bytes4(keccak256("getOrderInfo((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes))")); + + // getOrdersInfo + bytes4 constant public GET_ORDERS_INFO_SELECTOR = 0x7e9d74dc; + bytes4 constant public GET_ORDERS_INFO_SELECTOR_GENERATOR = bytes4(keccak256("getOrdersInfo((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[])")); + + // isValidSignature + bytes4 constant public IS_VALID_SIGNATURE_SELECTOR = 0x93634702; + bytes4 constant public IS_VALID_SIGNATURE_SELECTOR_GENERATOR = bytes4(keccak256("isValidSignature(bytes32,address,bytes)")); + + // marketBuyOrders + bytes4 constant public MARKET_BUY_ORDERS_SELECTOR = 0xe5fa431b; + bytes4 constant public MARKET_BUY_ORDERS_SELECTOR_GENERATOR = bytes4(keccak256("marketBuyOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256,bytes[])")); + + // marketBuyOrdersNoThrow + bytes4 constant public MARKET_BUY_ORDERS_NO_THROW_SELECTOR = 0xa3e20380; + bytes4 constant public MARKET_BUY_ORDERS_NO_THROW_SELECTOR_GENERATOR = bytes4(keccak256("marketBuyOrdersNoThrow((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256,bytes[])")); + + // marketSellOrders + bytes4 constant public MARKET_SELL_ORDERS_SELECTOR = 0x7e1d9808; + bytes4 constant public MARKET_SELL_ORDERS_SELECTOR_GENERATOR = bytes4(keccak256("marketSellOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256,bytes[])")); + + // marketSellOrdersNoThrow + bytes4 constant public MARKET_SELL_ORDERS_NO_THROW_SELECTOR = 0xdd1c7d18; + bytes4 constant public MARKET_SELL_ORDERS_NO_THROW_SELECTOR_GENERATOR = bytes4(keccak256("marketSellOrdersNoThrow((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256,bytes[])")); + + // matchOrders + bytes4 constant public MATCH_ORDERS_SELECTOR = 0x3c28d861; + bytes4 constant public MATCH_ORDERS_SELECTOR_GENERATOR = 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 public ORDER_EPOCH_SELECTOR = 0xd9bfa73e; + bytes4 constant public ORDER_EPOCH_SELECTOR_GENERATOR = bytes4(keccak256("orderEpoch(address,address)")); + + // owner + bytes4 constant public OWNER_SELECTOR = 0x8da5cb5b; + bytes4 constant public OWNER_SELECTOR_GENERATOR = bytes4(keccak256("owner()")); - // cancelOrdersUpTo - bytes4 constant cancelOrdersUpToSelector = 0x4f9559b1; - bytes4 constant cancelOrdersUpToSelectorGenerator = bytes4(keccak256('cancelOrdersUpTo(uint256)')); + // preSign + bytes4 constant public PRE_SIGN_SELECTOR = 0x3683ef8e; + bytes4 constant public PRE_SIGN_SELECTOR_GENERATOR = bytes4(keccak256("preSign(bytes32,address,bytes)")); - // cancelled - bytes4 constant cancelledSelector = 0x2ac12622; - bytes4 constant cancelledSelectorGenerator = bytes4(keccak256('cancelled(bytes32)')); + // preSigned + bytes4 constant public PRE_SIGNED_SELECTOR = 0x82c174d0; + bytes4 constant public PRE_SIGNED_SELECTOR_GENERATOR = bytes4(keccak256("preSigned(bytes32,address)")); - // currentContextAddress - bytes4 constant currentContextAddressSelector = 0xeea086ba; - bytes4 constant currentContextAddressSelectorGenerator = bytes4(keccak256('currentContextAddress()')); + // registerAssetProxy + bytes4 constant public REGISTER_ASSET_PROXY_SELECTOR = 0xc585bb93; + bytes4 constant public REGISTER_ASSET_PROXY_SELECTOR_GENERATOR = bytes4(keccak256("registerAssetProxy(address)")); - // executeTransaction - bytes4 constant executeTransactionSelector = 0xbfc8bfce; - bytes4 constant executeTransactionSelectorGenerator = bytes4(keccak256('executeTransaction(uint256,address,bytes,bytes)')); + // setSignatureValidatorApproval + bytes4 constant public SET_SIGNATURE_VALIDATOR_APPROVAL_SELECTOR = 0x77fcce68; + bytes4 constant public SET_SIGNATURE_VALIDATOR_APPROVAL_SELECTOR_GENERATOR = bytes4(keccak256("setSignatureValidatorApproval(address,bool)")); - // 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)')); + // transactions + bytes4 constant public TRANSACTIONS_SELECTOR = 0x642f2eaf; + bytes4 constant public TRANSACTIONS_SELECTOR_GENERATOR = bytes4(keccak256("transactions(bytes32)")); - // 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)')); + // transferOwnership + bytes4 constant public TRANSFER_OWNERSHIP_SELECTOR = 0xf2fde38b; + bytes4 constant public TRANSFER_OWNERSHIP_SELECTOR_GENERATOR = bytes4(keccak256("transferOwnership(address)")); } \ No newline at end of file -- cgit v1.2.3 From a650d695cede55c61aeb3a24dade828f6cc57844 Mon Sep 17 00:00:00 2001 From: Alex Browne Date: Tue, 18 Dec 2018 16:26:10 -0800 Subject: Fix test-publish failure in contracts packages --- contracts/multisig/src/index.ts | 2 ++ contracts/protocol/src/index.ts | 1 - contracts/protocol/test/exchange/order_validator.ts | 5 ++--- 3 files changed, 4 insertions(+), 4 deletions(-) create mode 100644 contracts/multisig/src/index.ts diff --git a/contracts/multisig/src/index.ts b/contracts/multisig/src/index.ts new file mode 100644 index 000000000..d55f08ea2 --- /dev/null +++ b/contracts/multisig/src/index.ts @@ -0,0 +1,2 @@ +export * from './artifacts'; +export * from './wrappers'; diff --git a/contracts/protocol/src/index.ts b/contracts/protocol/src/index.ts index ba813e7ca..d55f08ea2 100644 --- a/contracts/protocol/src/index.ts +++ b/contracts/protocol/src/index.ts @@ -1,3 +1,2 @@ export * from './artifacts'; export * from './wrappers'; -export * from '../test/utils'; diff --git a/contracts/protocol/test/exchange/order_validator.ts b/contracts/protocol/test/exchange/order_validator.ts index 8f53426db..39b173fdd 100644 --- a/contracts/protocol/test/exchange/order_validator.ts +++ b/contracts/protocol/test/exchange/order_validator.ts @@ -18,14 +18,13 @@ import * as _ from 'lodash'; import { artifacts, ERC20ProxyContract, - ERC20Wrapper, ERC721ProxyContract, - ERC721Wrapper, ExchangeContract, - ExchangeWrapper, OrderValidatorContract, } from '../../src'; +import { ERC20Wrapper, ERC721Wrapper, ExchangeWrapper } from '../utils'; + chaiSetup.configure(); const expect = chai.expect; const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); -- cgit v1.2.3 From 768e1d541c445dd84395afbd0cb04a1163e3f1c7 Mon Sep 17 00:00:00 2001 From: Alex Browne Date: Tue, 18 Dec 2018 18:22:54 -0800 Subject: Make @0x/contracts-test-utils a dependency instead of a devDependency --- contracts/protocol/package.json | 2 +- contracts/protocol/src/index.ts | 1 + contracts/protocol/test/exchange/order_validator.ts | 5 +++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/contracts/protocol/package.json b/contracts/protocol/package.json index 838189371..122ce07c3 100644 --- a/contracts/protocol/package.json +++ b/contracts/protocol/package.json @@ -44,7 +44,6 @@ "homepage": "https://github.com/0xProject/0x-monorepo/contracts/protocol/README.md", "devDependencies": { "@0x/abi-gen": "^1.0.19", - "@0x/contracts-test-utils": "^1.0.2", "@0x/dev-utils": "^1.0.21", "@0x/sol-compiler": "^1.1.16", "@0x/sol-cov": "^2.1.16", @@ -75,6 +74,7 @@ "@0x/contracts-interfaces": "^1.0.2", "@0x/contracts-libs": "^1.0.2", "@0x/contracts-multisig": "^1.0.2", + "@0x/contracts-test-utils": "^1.0.2", "@0x/contracts-tokens": "^1.0.2", "@0x/contracts-utils": "^1.0.2", "@0x/order-utils": "^3.0.7", diff --git a/contracts/protocol/src/index.ts b/contracts/protocol/src/index.ts index d55f08ea2..ba813e7ca 100644 --- a/contracts/protocol/src/index.ts +++ b/contracts/protocol/src/index.ts @@ -1,2 +1,3 @@ export * from './artifacts'; export * from './wrappers'; +export * from '../test/utils'; diff --git a/contracts/protocol/test/exchange/order_validator.ts b/contracts/protocol/test/exchange/order_validator.ts index 39b173fdd..8f53426db 100644 --- a/contracts/protocol/test/exchange/order_validator.ts +++ b/contracts/protocol/test/exchange/order_validator.ts @@ -18,13 +18,14 @@ import * as _ from 'lodash'; import { artifacts, ERC20ProxyContract, + ERC20Wrapper, ERC721ProxyContract, + ERC721Wrapper, ExchangeContract, + ExchangeWrapper, OrderValidatorContract, } from '../../src'; -import { ERC20Wrapper, ERC721Wrapper, ExchangeWrapper } from '../utils'; - chaiSetup.configure(); const expect = chai.expect; const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); -- cgit v1.2.3 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(+) 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 v1.2.3 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(-) 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 v1.2.3 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(-) 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 v1.2.3 From bce7ae78bc78a27355d524eac8a6d0e91d05e522 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Wed, 19 Dec 2018 13:05:47 +0000 Subject: Bump up stale to close to 30 days --- .github/stale.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/stale.yml b/.github/stale.yml index af12c62d5..09eb40a77 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -1,7 +1,7 @@ # Number of days of inactivity before an issue becomes stale daysUntilStale: 30 # Number of days of inactivity before a stale issue is closed -daysUntilClose: 7 +daysUntilClose: 30 # Issues with these labels will never be considered stale exemptLabels: - pinned -- cgit v1.2.3 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(+) 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 v1.2.3