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 From 1c68057999ad9cb8be5018bffeeb6c1e45e645ad Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Fri, 17 Aug 2018 13:03:07 +1000 Subject: Rename to Transaction Encoder. Add tests for all encoding methods. --- packages/contract-wrappers/CHANGELOG.json | 9 + .../src/contract_wrappers/exchange_wrapper.ts | 11 +- .../src/utils/execute_transaction_encoder.ts | 124 --------- .../src/utils/transaction_encoder.ts | 293 +++++++++++++++++++++ .../test/execute_transaction_encoder_test.ts | 113 -------- .../test/transaction_encoder_test.ts | 211 +++++++++++++++ 6 files changed, 521 insertions(+), 240 deletions(-) delete mode 100644 packages/contract-wrappers/src/utils/execute_transaction_encoder.ts create mode 100644 packages/contract-wrappers/src/utils/transaction_encoder.ts delete mode 100644 packages/contract-wrappers/test/execute_transaction_encoder_test.ts create mode 100644 packages/contract-wrappers/test/transaction_encoder_test.ts diff --git a/packages/contract-wrappers/CHANGELOG.json b/packages/contract-wrappers/CHANGELOG.json index fdf779338..d50043b0d 100644 --- a/packages/contract-wrappers/CHANGELOG.json +++ b/packages/contract-wrappers/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "version": "1.0.1-rc.4", + "changes": [ + { + "pr": 975, + "note": "Added Transaction Encoder for use with 0x Exchange executeTransaction" + } + ] + }, { "version": "1.0.1-rc.3", "changes": [ diff --git a/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts index 12d6a8fd3..5a4b40547 100644 --- a/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts +++ b/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts @@ -21,7 +21,7 @@ import { } from '../types'; import { assert } from '../utils/assert'; import { decorators } from '../utils/decorators'; -import { ExecuteTransactionEncoder } from '../utils/execute_transaction_encoder'; +import { TransactionEncoder } from '../utils/transaction_encoder'; import { ContractWrapper } from './contract_wrapper'; import { ExchangeContract, ExchangeEventArgs, ExchangeEvents } from './generated/exchange'; @@ -1098,9 +1098,14 @@ export class ExchangeWrapper extends ContractWrapper { const zrxAssetData = assetDataUtils.encodeERC20AssetData(zrxTokenAddress); return zrxAssetData; } - public async executeTransactionEncoderAsync(): Promise { + /** + * Returns a Transaction Encoder. Transaction messages exist for the purpose of calling methods on the Exchange contract + * in the context of another address. + * @return TransactionEncoder + */ + public async transactionEncoderAsync(): Promise { const exchangeInstance = await this._getExchangeContractAsync(); - const encoder = new ExecuteTransactionEncoder(exchangeInstance); + const encoder = new TransactionEncoder(exchangeInstance); return encoder; } // tslint:disable:no-unused-variable diff --git a/packages/contract-wrappers/src/utils/execute_transaction_encoder.ts b/packages/contract-wrappers/src/utils/execute_transaction_encoder.ts deleted file mode 100644 index 9c941c550..000000000 --- a/packages/contract-wrappers/src/utils/execute_transaction_encoder.ts +++ /dev/null @@ -1,124 +0,0 @@ -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/src/utils/transaction_encoder.ts b/packages/contract-wrappers/src/utils/transaction_encoder.ts new file mode 100644 index 000000000..5c2a94b74 --- /dev/null +++ b/packages/contract-wrappers/src/utils/transaction_encoder.ts @@ -0,0 +1,293 @@ +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 }, + ], +}; + +/** + * Transaction Encoder. Transaction messages exist for the purpose of calling methods on the Exchange contract + * in the context of another address. For example, UserA can encode and sign a fillOrder transaction and UserB + * can submit this to the blockchain. The Exchange context executes as if UserA had directly submitted this transaction. + */ +export class TransactionEncoder { + private _exchangeInstance: ExchangeContract; + constructor(exchangeInstance: ExchangeContract) { + this._exchangeInstance = exchangeInstance; + } + /** + * Encodes the transaction data for use with the Exchange contract. + * @param data The ABI Encoded 0x Exchange method. I.e fillOrder + * @param salt A random value to provide uniqueness and prevent replay attacks. + * @param signerAddress The address which will sign this transaction. + * @return An unsigned hex encoded transaction for use in 0x Exchange executeTransaction. + */ + public getTransactionHex(data: string, salt: BigNumber, signerAddress: string): string { + const exchangeAddress = this._getExchangeContract().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; + } + /** + * Encodes a fillOrder transaction. + * @param signedOrder An object that conforms to the SignedOrder interface. + * @param takerAssetFillAmount The amount of the order (in taker asset baseUnits) that you wish to fill. + * @return Hex encoded abi of the function call. + */ + public fillOrderTx(signedOrder: SignedOrder, takerAssetFillAmount: BigNumber): string { + assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema); + assert.isValidBaseUnitAmount('takerAssetFillAmount', takerAssetFillAmount); + const abiEncodedData = this._getExchangeContract().fillOrder.getABIEncodedTransactionData( + signedOrder, + takerAssetFillAmount, + signedOrder.signature, + ); + return abiEncodedData; + } + /** + * Encodes a fillOrderNoThrow transaction. + * @param signedOrder An object that conforms to the SignedOrder interface. + * @param takerAssetFillAmount The amount of the order (in taker asset baseUnits) that you wish to fill. + * @return Hex encoded abi of the function call. + */ + public fillOrderNoThrowTx(signedOrder: SignedOrder, takerAssetFillAmount: BigNumber): string { + assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema); + assert.isValidBaseUnitAmount('takerAssetFillAmount', takerAssetFillAmount); + const abiEncodedData = this._getExchangeContract().fillOrderNoThrow.getABIEncodedTransactionData( + signedOrder, + takerAssetFillAmount, + signedOrder.signature, + ); + return abiEncodedData; + } + /** + * Encodes a fillOrKillOrder transaction. + * @param signedOrder An object that conforms to the SignedOrder interface. + * @param takerAssetFillAmount The amount of the order (in taker asset baseUnits) that you wish to fill. + * @return Hex encoded abi of the function call. + */ + public fillOrKillOrderTx(signedOrder: SignedOrder, takerAssetFillAmount: BigNumber): string { + assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema); + assert.isValidBaseUnitAmount('takerAssetFillAmount', takerAssetFillAmount); + const abiEncodedData = this._getExchangeContract().fillOrKillOrder.getABIEncodedTransactionData( + signedOrder, + takerAssetFillAmount, + signedOrder.signature, + ); + return abiEncodedData; + } + /** + * Encodes a batchFillOrders transaction. + * @param signedOrders An array of signed orders to fill. + * @param takerAssetFillAmounts The amounts of the orders (in taker asset baseUnits) that you wish to fill. + * @return Hex encoded abi of the function call. + */ + public batchFillOrdersTx(signedOrders: SignedOrder[], takerAssetFillAmounts: BigNumber[]): string { + assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema); + _.forEach(takerAssetFillAmounts, takerAssetFillAmount => + assert.isBigNumber('takerAssetFillAmount', takerAssetFillAmount), + ); + const signatures = _.map(signedOrders, signedOrder => signedOrder.signature); + const abiEncodedData = this._getExchangeContract().batchFillOrders.getABIEncodedTransactionData( + signedOrders, + takerAssetFillAmounts, + signatures, + ); + return abiEncodedData; + } + /** + * Encodes a batchFillOrKillOrders transaction. + * @param signedOrders An array of signed orders to fill. + * @param takerAssetFillAmounts The amounts of the orders (in taker asset baseUnits) that you wish to fill. + * @return Hex encoded abi of the function call. + */ + public batchFillOrKillOrdersTx(signedOrders: SignedOrder[], takerAssetFillAmounts: BigNumber[]): string { + assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema); + _.forEach(takerAssetFillAmounts, takerAssetFillAmount => + assert.isBigNumber('takerAssetFillAmount', takerAssetFillAmount), + ); + const signatures = _.map(signedOrders, signedOrder => signedOrder.signature); + const abiEncodedData = this._getExchangeContract().batchFillOrKillOrders.getABIEncodedTransactionData( + signedOrders, + takerAssetFillAmounts, + signatures, + ); + return abiEncodedData; + } + /** + * Encodes a batchFillOrdersNoThrow transaction. + * @param signedOrders An array of signed orders to fill. + * @param takerAssetFillAmounts The amounts of the orders (in taker asset baseUnits) that you wish to fill. + * @return Hex encoded abi of the function call. + */ + public batchFillOrdersNoThrowTx(signedOrders: SignedOrder[], takerAssetFillAmounts: BigNumber[]): string { + assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema); + _.forEach(takerAssetFillAmounts, takerAssetFillAmount => + assert.isBigNumber('takerAssetFillAmount', takerAssetFillAmount), + ); + const signatures = _.map(signedOrders, signedOrder => signedOrder.signature); + const abiEncodedData = this._getExchangeContract().batchFillOrdersNoThrow.getABIEncodedTransactionData( + signedOrders, + takerAssetFillAmounts, + signatures, + ); + return abiEncodedData; + } + /** + * Encodes a batchCancelOrders transaction. + * @param signedOrders An array of orders to cancel. + * @return Hex encoded abi of the function call. + */ + public batchCancelOrdersTx(signedOrders: SignedOrder[]): string { + assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema); + const abiEncodedData = this._getExchangeContract().batchCancelOrders.getABIEncodedTransactionData(signedOrders); + return abiEncodedData; + } + /** + * Encodes a cancelOrdersUpTo transaction. + * @param targetOrderEpoch Target order epoch. + * @return Hex encoded abi of the function call. + */ + public cancelOrdersUpToTx(targetOrderEpoch: BigNumber): string { + assert.isBigNumber('targetOrderEpoch', targetOrderEpoch); + const abiEncodedData = this._getExchangeContract().cancelOrdersUpTo.getABIEncodedTransactionData( + targetOrderEpoch, + ); + return abiEncodedData; + } + /** + * Encodes a cancelOrder transaction. + * @param order An object that conforms to the Order or SignedOrder interface. The order you would like to cancel. + * @return Hex encoded abi of the function call. + */ + public cancelOrderTx(order: Order | SignedOrder): string { + assert.doesConformToSchema('order', order, schemas.orderSchema); + const abiEncodedData = this._getExchangeContract().cancelOrder.getABIEncodedTransactionData(order); + return abiEncodedData; + } + /** + * Encodes a marketSellOrders transaction. + * @param signedOrders An array of signed orders to fill. + * @param takerAssetFillAmount Taker asset fill amount. + * @return Hex encoded abi of the function call. + */ + public marketSellOrdersTx(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._getExchangeContract().marketSellOrders.getABIEncodedTransactionData( + signedOrders, + takerAssetFillAmount, + signatures, + ); + return abiEncodedData; + } + /** + * Encodes a marketSellOrdersNoThrow transaction. + * @param signedOrders An array of signed orders to fill. + * @param takerAssetFillAmount Taker asset fill amount. + * @return Hex encoded abi of the function call. + */ + public marketSellOrdersNoThrowTx(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._getExchangeContract().marketSellOrdersNoThrow.getABIEncodedTransactionData( + signedOrders, + takerAssetFillAmount, + signatures, + ); + return abiEncodedData; + } + /** + * Encodes a maketBuyOrders transaction. + * @param signedOrders An array of signed orders to fill. + * @param makerAssetFillAmount Maker asset fill amount. + * @return Hex encoded abi of the function call. + */ + public marketBuyOrdersTx(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._getExchangeContract().marketBuyOrders.getABIEncodedTransactionData( + signedOrders, + makerAssetFillAmount, + signatures, + ); + return abiEncodedData; + } + /** + * Encodes a maketBuyOrdersNoThrow transaction. + * @param signedOrders An array of signed orders to fill. + * @param makerAssetFillAmount Maker asset fill amount. + * @return Hex encoded abi of the function call. + */ + public marketBuyOrdersNoThrowTx(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._getExchangeContract().marketBuyOrdersNoThrow.getABIEncodedTransactionData( + signedOrders, + makerAssetFillAmount, + signatures, + ); + return abiEncodedData; + } + /** + * Encodes a preSign transaction. + * @param hash Hash to pre-sign + * @param signerAddress Address that should have signed the given hash. + * @param signature Proof that the hash has been signed by signer. + * @return Hex encoded abi of the function call. + */ + public preSignTx(hash: string, signerAddress: string, signature: string): string { + assert.isHexString('hash', hash); + assert.isETHAddressHex('signerAddress', signerAddress); + assert.isHexString('signature', signature); + const abiEncodedData = this._getExchangeContract().preSign.getABIEncodedTransactionData( + hash, + signerAddress, + signature, + ); + return abiEncodedData; + } + /** + * Encodes a setSignatureValidatorApproval transaction. + * @param validatorAddress Validator contract address. + * @param isApproved Boolean value to set approval to. + * @return Hex encoded abi of the function call. + */ + public setSignatureValidatorApprovalTx(validatorAddress: string, isApproved: boolean): string { + assert.isETHAddressHex('validatorAddress', validatorAddress); + assert.isBoolean('isApproved', isApproved); + const abiEncodedData = this._getExchangeContract().setSignatureValidatorApproval.getABIEncodedTransactionData( + validatorAddress, + isApproved, + ); + return abiEncodedData; + } + private _getExchangeContract(): ExchangeContract { + return this._exchangeInstance; + } +} diff --git a/packages/contract-wrappers/test/execute_transaction_encoder_test.ts b/packages/contract-wrappers/test/execute_transaction_encoder_test.ts deleted file mode 100644 index 13635dfb8..000000000 --- a/packages/contract-wrappers/test/execute_transaction_encoder_test.ts +++ /dev/null @@ -1,113 +0,0 @@ -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); - }); - }); - }); -}); diff --git a/packages/contract-wrappers/test/transaction_encoder_test.ts b/packages/contract-wrappers/test/transaction_encoder_test.ts new file mode 100644 index 000000000..10222dbc1 --- /dev/null +++ b/packages/contract-wrappers/test/transaction_encoder_test.ts @@ -0,0 +1,211 @@ +import { BlockchainLifecycle } from '@0xproject/dev-utils'; +import { FillScenarios } from '@0xproject/fill-scenarios'; +import { assetDataUtils, ecSignOrderHashAsync, generatePseudoRandomSalt, orderHashUtils } from '@0xproject/order-utils'; +import { SignedOrder, SignerType } from '@0xproject/types'; +import { BigNumber } from '@0xproject/utils'; +import 'mocha'; + +import { ContractWrappers } from '../src'; +import { TransactionEncoder } from '../src/utils/transaction_encoder'; + +import { constants } from './utils/constants'; +import { tokenUtils } from './utils/token_utils'; +import { provider, web3Wrapper } from './utils/web3_wrapper'; + +const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); + +describe('TransactionEncoder', () => { + let contractWrappers: ContractWrappers; + let userAddresses: 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 txHash: string; + const fillableAmount = new BigNumber(5); + const takerTokenFillAmount = new BigNumber(5); + let signedOrder: 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(); + const zrxTokenAddress = tokenUtils.getProtocolTokenAddress(); + fillScenarios = new FillScenarios( + provider, + userAddresses, + zrxTokenAddress, + exchangeContractAddress, + contractWrappers.erc20Proxy.getContractAddress(), + contractWrappers.erc721Proxy.getContractAddress(), + ); + [coinbase, makerAddress, takerAddress, senderAddress] = userAddresses; + [makerTokenAddress, takerTokenAddress] = tokenUtils.getDummyERC20TokenAddresses(); + [makerAssetData, takerAssetData] = [ + assetDataUtils.encodeERC20AssetData(makerTokenAddress), + assetDataUtils.encodeERC20AssetData(takerTokenAddress), + ]; + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerAssetData, + takerAssetData, + makerAddress, + takerAddress, + fillableAmount, + ); + }); + after(async () => { + await blockchainLifecycle.revertAsync(); + }); + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + }); + describe('encode and executeTransaction', () => { + const executeTransactionOrThrowAsync = async ( + encoder: TransactionEncoder, + data: string, + signerAddress: string = takerAddress, + ): Promise => { + const salt = generatePseudoRandomSalt(); + const encodedTransaction = encoder.getTransactionHex(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); + }; + describe('#fillOrderTx', () => { + it('should successfully execute the transaction', async () => { + const encoder = await contractWrappers.exchange.transactionEncoderAsync(); + const data = encoder.fillOrderTx(signedOrder, takerTokenFillAmount); + await executeTransactionOrThrowAsync(encoder, data); + }); + }); + describe('#fillOrderNoThrowTx', () => { + it('should successfully execute the transaction', async () => { + const encoder = await contractWrappers.exchange.transactionEncoderAsync(); + const data = encoder.fillOrderNoThrowTx(signedOrder, takerTokenFillAmount); + await executeTransactionOrThrowAsync(encoder, data); + }); + }); + describe('#fillOrKillOrderTx', () => { + it('should successfully execute the transaction', async () => { + const encoder = await contractWrappers.exchange.transactionEncoderAsync(); + const data = encoder.fillOrKillOrderTx(signedOrder, takerTokenFillAmount); + await executeTransactionOrThrowAsync(encoder, data); + }); + }); + describe('#marketSellOrdersTx', () => { + it('should successfully execute the transaction', async () => { + const encoder = await contractWrappers.exchange.transactionEncoderAsync(); + const data = encoder.marketSellOrdersTx([signedOrder], takerTokenFillAmount); + await executeTransactionOrThrowAsync(encoder, data); + }); + }); + describe('#marketSellOrdersNoThrowTx', () => { + it('should successfully execute the transaction', async () => { + const encoder = await contractWrappers.exchange.transactionEncoderAsync(); + const data = encoder.marketSellOrdersNoThrowTx([signedOrder], takerTokenFillAmount); + await executeTransactionOrThrowAsync(encoder, data); + }); + }); + describe('#marketBuyOrdersTx', () => { + it('should successfully execute the transaction', async () => { + const encoder = await contractWrappers.exchange.transactionEncoderAsync(); + const data = encoder.marketBuyOrdersTx([signedOrder], fillableAmount); + await executeTransactionOrThrowAsync(encoder, data); + }); + }); + describe('#marketBuyOrdersNoThrowTx', () => { + it('should successfully execute the transaction', async () => { + const encoder = await contractWrappers.exchange.transactionEncoderAsync(); + const data = encoder.marketBuyOrdersNoThrowTx([signedOrder], fillableAmount); + await executeTransactionOrThrowAsync(encoder, data); + }); + }); + describe('#preSignTx', () => { + it('should successfully execute the transaction', async () => { + const encoder = await contractWrappers.exchange.transactionEncoderAsync(); + const orderHash = orderHashUtils.getOrderHashHex(signedOrder); + const signature = signedOrder.signature; + const data = encoder.preSignTx(orderHash, makerAddress, signature); + await executeTransactionOrThrowAsync(encoder, data); + }); + }); + describe('#setSignatureValidatorApprovalTx', () => { + it('should successfully execute the transaction', async () => { + const encoder = await contractWrappers.exchange.transactionEncoderAsync(); + const isApproved = true; + const data = encoder.setSignatureValidatorApprovalTx(senderAddress, isApproved); + await executeTransactionOrThrowAsync(encoder, data); + }); + }); + describe('#batchFillOrdersTx', () => { + it('should successfully execute the transaction', async () => { + const encoder = await contractWrappers.exchange.transactionEncoderAsync(); + const data = encoder.batchFillOrdersTx([signedOrder], [takerTokenFillAmount]); + await executeTransactionOrThrowAsync(encoder, data); + }); + }); + describe('#batchFillOrKillOrdersTx', () => { + it('should successfully execute the transaction', async () => { + const encoder = await contractWrappers.exchange.transactionEncoderAsync(); + const data = encoder.batchFillOrKillOrdersTx([signedOrder], [takerTokenFillAmount]); + await executeTransactionOrThrowAsync(encoder, data); + }); + }); + describe('#batchFillOrdersNoThrowTx', () => { + it('should successfully execute the transaction', async () => { + const encoder = await contractWrappers.exchange.transactionEncoderAsync(); + const data = encoder.batchFillOrdersNoThrowTx([signedOrder], [takerTokenFillAmount]); + await executeTransactionOrThrowAsync(encoder, data); + }); + }); + describe('#batchCancelOrdersTx', () => { + it('should successfully execute the transaction', async () => { + const encoder = await contractWrappers.exchange.transactionEncoderAsync(); + const data = encoder.batchCancelOrdersTx([signedOrder]); + const signerAddress = makerAddress; + await executeTransactionOrThrowAsync(encoder, data, signerAddress); + }); + }); + describe('#cancelOrderTx', () => { + it('should successfully execute the transaction', async () => { + const encoder = await contractWrappers.exchange.transactionEncoderAsync(); + const data = encoder.cancelOrderTx(signedOrder); + const signerAddress = makerAddress; + await executeTransactionOrThrowAsync(encoder, data, signerAddress); + }); + }); + describe('#cancelOrdersUpToTx', () => { + it('should successfully execute the transaction', async () => { + const encoder = await contractWrappers.exchange.transactionEncoderAsync(); + const targetEpoch = signedOrder.salt; + const data = encoder.cancelOrdersUpToTx(targetEpoch); + const signerAddress = makerAddress; + await executeTransactionOrThrowAsync(encoder, data, signerAddress); + }); + }); + }); +}); -- cgit