From 36668f94090fa5c35f636f6b7ba597fcb89c068d Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Thu, 16 Aug 2018 15:28:59 +1000 Subject: [Contract-wrappers] Exchange execute transaction encoder --- .../src/contract_wrappers/exchange_wrapper.ts | 6 + .../src/utils/execute_transaction_encoder.ts | 124 +++++++++++++++++++++ .../test/execute_transaction_encoder_test.ts | 113 +++++++++++++++++++ 3 files changed, 243 insertions(+) create mode 100644 packages/contract-wrappers/src/utils/execute_transaction_encoder.ts create mode 100644 packages/contract-wrappers/test/execute_transaction_encoder_test.ts diff --git a/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts index 48bd00f90..12d6a8fd3 100644 --- a/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts +++ b/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts @@ -21,6 +21,7 @@ import { } from '../types'; import { assert } from '../utils/assert'; import { decorators } from '../utils/decorators'; +import { ExecuteTransactionEncoder } from '../utils/execute_transaction_encoder'; import { ContractWrapper } from './contract_wrapper'; import { ExchangeContract, ExchangeEventArgs, ExchangeEvents } from './generated/exchange'; @@ -1097,6 +1098,11 @@ export class ExchangeWrapper extends ContractWrapper { const zrxAssetData = assetDataUtils.encodeERC20AssetData(zrxTokenAddress); return zrxAssetData; } + public async executeTransactionEncoderAsync(): Promise { + const exchangeInstance = await this._getExchangeContractAsync(); + const encoder = new ExecuteTransactionEncoder(exchangeInstance); + return encoder; + } // tslint:disable:no-unused-variable private _invalidateContractInstances(): void { this.unsubscribeAll(); diff --git a/packages/contract-wrappers/src/utils/execute_transaction_encoder.ts b/packages/contract-wrappers/src/utils/execute_transaction_encoder.ts new file mode 100644 index 000000000..9c941c550 --- /dev/null +++ b/packages/contract-wrappers/src/utils/execute_transaction_encoder.ts @@ -0,0 +1,124 @@ +import { schemas } from '@0xproject/json-schemas'; +import { EIP712Schema, EIP712Types, EIP712Utils } from '@0xproject/order-utils'; +import { Order, SignedOrder } from '@0xproject/types'; +import { BigNumber } from '@0xproject/utils'; +import _ = require('lodash'); + +import { ExchangeContract } from '../contract_wrappers/generated/exchange'; + +import { assert } from './assert'; + +const EIP712_ZEROEX_TRANSACTION_SCHEMA: EIP712Schema = { + name: 'ZeroExTransaction', + parameters: [ + { name: 'salt', type: EIP712Types.Uint256 }, + { name: 'signerAddress', type: EIP712Types.Address }, + { name: 'data', type: EIP712Types.Bytes }, + ], +}; + +export class ExecuteTransactionEncoder { + private _exchangeInstance: ExchangeContract; + constructor(exchangeInstance: ExchangeContract) { + this._exchangeInstance = exchangeInstance; + } + public getExecuteTransactionHex(data: string, salt: BigNumber, signerAddress: string): string { + const exchangeAddress = this._exchangeInstance.address; + const executeTransactionData = { + salt, + signerAddress, + data, + }; + const executeTransactionHashBuff = EIP712Utils.structHash( + EIP712_ZEROEX_TRANSACTION_SCHEMA, + executeTransactionData, + ); + const eip721MessageBuffer = EIP712Utils.createEIP712Message(executeTransactionHashBuff, exchangeAddress); + const messageHex = `0x${eip721MessageBuffer.toString('hex')}`; + return messageHex; + } + public fillOrder(signedOrder: SignedOrder, takerAssetFillAmount: BigNumber): string { + assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema); + assert.isValidBaseUnitAmount('takerAssetFillAmount', takerAssetFillAmount); + const abiEncodedData = this._exchangeInstance.fillOrder.getABIEncodedTransactionData( + signedOrder, + takerAssetFillAmount, + signedOrder.signature, + ); + return abiEncodedData; + } + public fillOrderNoThrow(signedOrder: SignedOrder, takerAssetFillAmount: BigNumber): string { + assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema); + assert.isValidBaseUnitAmount('takerAssetFillAmount', takerAssetFillAmount); + const abiEncodedData = this._exchangeInstance.fillOrderNoThrow.getABIEncodedTransactionData( + signedOrder, + takerAssetFillAmount, + signedOrder.signature, + ); + return abiEncodedData; + } + public fillOrKillOrder(signedOrder: SignedOrder, takerAssetFillAmount: BigNumber): string { + assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema); + assert.isValidBaseUnitAmount('takerAssetFillAmount', takerAssetFillAmount); + const abiEncodedData = this._exchangeInstance.fillOrKillOrder.getABIEncodedTransactionData( + signedOrder, + takerAssetFillAmount, + signedOrder.signature, + ); + return abiEncodedData; + } + public cancelOrdersUpTo(targetOrderEpoch: BigNumber): string { + assert.isBigNumber('targetOrderEpoch', targetOrderEpoch); + const abiEncodedData = this._exchangeInstance.cancelOrdersUpTo.getABIEncodedTransactionData(targetOrderEpoch); + return abiEncodedData; + } + public cancelOrder(order: Order | SignedOrder): string { + assert.doesConformToSchema('order', order, schemas.orderSchema); + const abiEncodedData = this._exchangeInstance.cancelOrder.getABIEncodedTransactionData(order); + return abiEncodedData; + } + public marketSellOrders(signedOrders: SignedOrder[], takerAssetFillAmount: BigNumber): string { + assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema); + assert.isBigNumber('takerAssetFillAmount', takerAssetFillAmount); + const signatures = _.map(signedOrders, signedOrder => signedOrder.signature); + const abiEncodedData = this._exchangeInstance.marketSellOrders.getABIEncodedTransactionData( + signedOrders, + takerAssetFillAmount, + signatures, + ); + return abiEncodedData; + } + public marketSellOrdersNoThrow(signedOrders: SignedOrder[], takerAssetFillAmount: BigNumber): string { + assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema); + assert.isBigNumber('takerAssetFillAmount', takerAssetFillAmount); + const signatures = _.map(signedOrders, signedOrder => signedOrder.signature); + const abiEncodedData = this._exchangeInstance.marketSellOrdersNoThrow.getABIEncodedTransactionData( + signedOrders, + takerAssetFillAmount, + signatures, + ); + return abiEncodedData; + } + public marketBuyOrders(signedOrders: SignedOrder[], makerAssetFillAmount: BigNumber): string { + assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema); + assert.isBigNumber('makerAssetFillAmount', makerAssetFillAmount); + const signatures = _.map(signedOrders, signedOrder => signedOrder.signature); + const abiEncodedData = this._exchangeInstance.marketBuyOrders.getABIEncodedTransactionData( + signedOrders, + makerAssetFillAmount, + signatures, + ); + return abiEncodedData; + } + public marketBuyOrdersNoThrow(signedOrders: SignedOrder[], makerAssetFillAmount: BigNumber): string { + assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema); + assert.isBigNumber('makerAssetFillAmount', makerAssetFillAmount); + const signatures = _.map(signedOrders, signedOrder => signedOrder.signature); + const abiEncodedData = this._exchangeInstance.marketBuyOrdersNoThrow.getABIEncodedTransactionData( + signedOrders, + makerAssetFillAmount, + signatures, + ); + return abiEncodedData; + } +} diff --git a/packages/contract-wrappers/test/execute_transaction_encoder_test.ts b/packages/contract-wrappers/test/execute_transaction_encoder_test.ts new file mode 100644 index 000000000..13635dfb8 --- /dev/null +++ b/packages/contract-wrappers/test/execute_transaction_encoder_test.ts @@ -0,0 +1,113 @@ +import { BlockchainLifecycle } from '@0xproject/dev-utils'; +import { FillScenarios } from '@0xproject/fill-scenarios'; +import { assetDataUtils, ecSignOrderHashAsync, generatePseudoRandomSalt } from '@0xproject/order-utils'; +import { SignedOrder, SignerType } from '@0xproject/types'; +import { BigNumber } from '@0xproject/utils'; +import * as chai from 'chai'; +import 'mocha'; + +import { ContractWrappers } from '../src'; + +import { chaiSetup } from './utils/chai_setup'; +import { constants } from './utils/constants'; +import { tokenUtils } from './utils/token_utils'; +import { provider, web3Wrapper } from './utils/web3_wrapper'; + +chaiSetup.configure(); +const expect = chai.expect; +const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); + +describe('ExecuteTransactionEncoder', () => { + let contractWrappers: ContractWrappers; + let userAddresses: string[]; + let zrxTokenAddress: string; + let fillScenarios: FillScenarios; + let exchangeContractAddress: string; + let makerTokenAddress: string; + let takerTokenAddress: string; + let coinbase: string; + let makerAddress: string; + let senderAddress: string; + let takerAddress: string; + let makerAssetData: string; + let takerAssetData: string; + let feeRecipient: string; + let txHash: string; + const fillableAmount = new BigNumber(5); + const takerTokenFillAmount = new BigNumber(5); + let signedOrder: SignedOrder; + let anotherSignedOrder: SignedOrder; + const config = { + networkId: constants.TESTRPC_NETWORK_ID, + blockPollingIntervalMs: 0, + }; + before(async () => { + await blockchainLifecycle.startAsync(); + contractWrappers = new ContractWrappers(provider, config); + exchangeContractAddress = contractWrappers.exchange.getContractAddress(); + userAddresses = await web3Wrapper.getAvailableAddressesAsync(); + zrxTokenAddress = tokenUtils.getProtocolTokenAddress(); + fillScenarios = new FillScenarios( + provider, + userAddresses, + zrxTokenAddress, + exchangeContractAddress, + contractWrappers.erc20Proxy.getContractAddress(), + contractWrappers.erc721Proxy.getContractAddress(), + ); + [coinbase, makerAddress, takerAddress, feeRecipient, senderAddress] = userAddresses; + [makerTokenAddress, takerTokenAddress] = tokenUtils.getDummyERC20TokenAddresses(); + [makerAssetData, takerAssetData] = [ + assetDataUtils.encodeERC20AssetData(makerTokenAddress), + assetDataUtils.encodeERC20AssetData(takerTokenAddress), + ]; + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerAssetData, + takerAssetData, + makerAddress, + takerAddress, + fillableAmount, + ); + anotherSignedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerAssetData, + takerAssetData, + makerAddress, + takerAddress, + fillableAmount, + ); + }); + after(async () => { + await blockchainLifecycle.revertAsync(); + }); + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + }); + describe('encoder', () => { + describe('#fillOrderAsync', () => { + it('should fill a valid order', async () => { + const encoder = await contractWrappers.exchange.executeTransactionEncoderAsync(); + const salt = generatePseudoRandomSalt(); + const signerAddress = takerAddress; + const data = encoder.fillOrder(signedOrder, takerTokenFillAmount); + const encodedTransaction = encoder.getExecuteTransactionHex(data, salt, signerAddress); + const signature = await ecSignOrderHashAsync( + provider, + encodedTransaction, + signerAddress, + SignerType.Default, + ); + txHash = await contractWrappers.exchange.executeTransactionAsync( + salt, + signerAddress, + data, + signature, + senderAddress, + ); + await web3Wrapper.awaitTransactionSuccessAsync(txHash, constants.AWAIT_TRANSACTION_MINED_MS); + }); + }); + }); +}); -- cgit v1.2.3