From 41444e7edef51fdda5e969b177cadc466c7abaaa Mon Sep 17 00:00:00 2001 From: Amir Bandeali Date: Thu, 25 Apr 2019 09:37:56 -0700 Subject: [PATCH] Refactor transaction tests to iclude more combinations and to cover all revert cases --- contracts/exchange/test/transactions.ts | 1044 +++++++++++------ .../test/utils/exchange_data_encoder.ts | 8 +- 2 files changed, 678 insertions(+), 374 deletions(-) diff --git a/contracts/exchange/test/transactions.ts b/contracts/exchange/test/transactions.ts index e3e645874f..250b77fe09 100644 --- a/contracts/exchange/test/transactions.ts +++ b/contracts/exchange/test/transactions.ts @@ -4,9 +4,9 @@ import { DummyERC20TokenContract } from '@0x/contracts-erc20'; import { chaiSetup, constants, - ERC20BalancesByOwner, + FillResults, + LogDecoder, OrderFactory, - orderUtils, provider, TransactionFactory, txDefaults, @@ -20,20 +20,27 @@ import { orderHashUtils, transactionHashUtils, } from '@0x/order-utils'; -import { - EIP712DomainWithDefaultSchema, - OrderStatus, - OrderWithoutDomain, - RevertReason, - SignedOrder, - SignedZeroExTransaction, -} from '@0x/types'; -import { BigNumber, providerUtils } from '@0x/utils'; +import { EIP712DomainWithDefaultSchema, OrderStatus, RevertReason } from '@0x/types'; +import { AbiEncoder, BigNumber, providerUtils } from '@0x/utils'; import * as chai from 'chai'; +import { LogWithDecodedArgs, MethodAbi } from 'ethereum-types'; import * as ethUtil from 'ethereumjs-util'; import * as _ from 'lodash'; -import { artifacts, ExchangeContract, ExchangeWrapper, ExchangeWrapperContract, WhitelistContract } from '../src/'; +import { + artifacts, + constants as exchangeConstants, + ExchangeCancelEventArgs, + ExchangeCancelUpToEventArgs, + ExchangeContract, + exchangeDataEncoder, + ExchangeFillEventArgs, + ExchangeFunctionName, + ExchangeSignatureValidatorApprovalEventArgs, + ExchangeWrapper, + ExchangeWrapperContract, + WhitelistContract, +} from '../src/'; chaiSetup.configure(); const expect = chai.expect; @@ -46,18 +53,15 @@ describe('Exchange transactions', () => { let makerAddress: string; let takerAddress: string; let feeRecipientAddress: string; + let validatorAddress: string; let erc20TokenA: DummyERC20TokenContract; let erc20TokenB: DummyERC20TokenContract; let zrxToken: DummyERC20TokenContract; - let exchange: ExchangeContract; + let exchangeInstance: ExchangeContract; let erc20Proxy: ERC20ProxyContract; - let erc20Balances: ERC20BalancesByOwner; let domain: EIP712DomainWithDefaultSchema; - let signedOrder: SignedOrder; - let signedTx: SignedZeroExTransaction; - let orderWithoutDomain: OrderWithoutDomain; let orderFactory: OrderFactory; let makerTransactionFactory: TransactionFactory; let takerTransactionFactory: TransactionFactory; @@ -84,11 +88,14 @@ describe('Exchange transactions', () => { before(async () => { chainId = await providerUtils.getChainIdAsync(provider); const accounts = await web3Wrapper.getAvailableAddressesAsync(); - const usedAddresses = ([owner, senderAddress, makerAddress, takerAddress, feeRecipientAddress] = _.slice( - accounts, - 0, - 5, - )); + const usedAddresses = ([ + owner, + senderAddress, + makerAddress, + takerAddress, + feeRecipientAddress, + validatorAddress, + ] = _.slice(accounts, 0, 6)); erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner); @@ -100,18 +107,18 @@ describe('Exchange transactions', () => { erc20Proxy = await erc20Wrapper.deployProxyAsync(); await erc20Wrapper.setBalancesAndAllowancesAsync(); - exchange = await ExchangeContract.deployFrom0xArtifactAsync( + exchangeInstance = await ExchangeContract.deployFrom0xArtifactAsync( artifacts.Exchange, provider, txDefaults, assetDataUtils.encodeERC20AssetData(zrxToken.address), new BigNumber(chainId), ); - exchangeWrapper = new ExchangeWrapper(exchange, provider); + exchangeWrapper = new ExchangeWrapper(exchangeInstance, provider); await exchangeWrapper.registerAssetProxyAsync(erc20Proxy.address, owner); await web3Wrapper.awaitTransactionSuccessAsync( - await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(exchange.address, { from: owner }), + await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(exchangeInstance.address, { from: owner }), constants.AWAIT_TRANSACTION_MINED_MS, ); @@ -119,13 +126,12 @@ describe('Exchange transactions', () => { defaultTakerTokenAddress = erc20TokenB.address; domain = { - verifyingContractAddress: exchange.address, + verifyingContractAddress: exchangeInstance.address, chainId, }; const defaultOrderParams = { ...constants.STATIC_ORDER_PARAMS, - senderAddress, makerAddress, feeRecipientAddress, makerAssetData: assetDataUtils.encodeERC20AssetData(defaultMakerTokenAddress), @@ -135,379 +141,677 @@ describe('Exchange transactions', () => { makerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddress)]; takerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(takerAddress)]; orderFactory = new OrderFactory(makerPrivateKey, defaultOrderParams); - makerTransactionFactory = new TransactionFactory(makerPrivateKey, exchange.address, chainId); - takerTransactionFactory = new TransactionFactory(takerPrivateKey, exchange.address, chainId); + makerTransactionFactory = new TransactionFactory(makerPrivateKey, exchangeInstance.address, chainId); + takerTransactionFactory = new TransactionFactory(takerPrivateKey, exchangeInstance.address, chainId); }); describe('executeTransaction', () => { - describe('fillOrder', () => { - let takerAssetFillAmount: BigNumber; - beforeEach(async () => { - erc20Balances = await erc20Wrapper.getBalancesAsync(); - signedOrder = await orderFactory.newSignedOrderAsync(); - orderWithoutDomain = orderUtils.getOrderWithoutDomain(signedOrder); - - takerAssetFillAmount = signedOrder.takerAssetAmount.div(2); - const data = exchange.fillOrder.getABIEncodedTransactionData( - orderWithoutDomain, - takerAssetFillAmount, - signedOrder.signature, - ); - signedTx = takerTransactionFactory.newSignedTransaction(data); - }); - - it('should throw if signature is invalid', async () => { - const v = ethUtil.toBuffer(signedTx.signature.slice(0, 4)); - const invalidR = ethUtil.sha3('invalidR'); - const invalidS = ethUtil.sha3('invalidS'); - const signatureType = ethUtil.toBuffer(`0x${signedTx.signature.slice(-2)}`); - const invalidSigBuff = Buffer.concat([v, invalidR, invalidS, signatureType]); - const invalidSigHex = `0x${invalidSigBuff.toString('hex')}`; - signedTx.signature = invalidSigHex; - const transactionHashHex = transactionHashUtils.getTransactionHashHex(signedTx); - const expectedError = new ExchangeRevertErrors.TransactionSignatureError( - transactionHashHex, - signedTx.signerAddress, - signedTx.signature, - ); - const tx = exchangeWrapper.executeTransactionAsync(signedTx, senderAddress); - return expect(tx).to.revertWith(expectedError); - }); - - it('should throw if not called by specified sender', async () => { - const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); - const transactionHashHex = transactionHashUtils.getTransactionHashHex(signedTx); - const expectedError = new ExchangeRevertErrors.TransactionExecutionError( - transactionHashHex, - new ExchangeRevertErrors.InvalidSenderError(orderHashHex, takerAddress).encode(), - ); - const tx = exchangeWrapper.executeTransactionAsync(signedTx, takerAddress); - return expect(tx).to.revertWith(expectedError); - }); - - it('should transfer the correct amounts when signed by taker and called by sender', async () => { - await exchangeWrapper.executeTransactionAsync(signedTx, senderAddress); - const newBalances = await erc20Wrapper.getBalancesAsync(); - const makerAssetFillAmount = takerAssetFillAmount - .times(signedOrder.makerAssetAmount) - .dividedToIntegerBy(signedOrder.takerAssetAmount); - const makerFeePaid = signedOrder.makerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(signedOrder.makerAssetAmount); - const takerFeePaid = signedOrder.takerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(signedOrder.makerAssetAmount); - expect(newBalances[makerAddress][defaultMakerTokenAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultMakerTokenAddress].minus(makerAssetFillAmount), - ); - expect(newBalances[makerAddress][defaultTakerTokenAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultTakerTokenAddress].plus(takerAssetFillAmount), - ); - expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[makerAddress][zrxToken.address].minus(makerFeePaid), - ); - expect(newBalances[takerAddress][defaultTakerTokenAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultTakerTokenAddress].minus(takerAssetFillAmount), - ); - expect(newBalances[takerAddress][defaultMakerTokenAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultMakerTokenAddress].plus(makerAssetFillAmount), - ); - expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[takerAddress][zrxToken.address].minus(takerFeePaid), - ); - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].plus(makerFeePaid.plus(takerFeePaid)), - ); - }); - - it('should throw if the a 0x transaction with the same transactionHash has already been executed', async () => { - await exchangeWrapper.executeTransactionAsync(signedTx, senderAddress); - const transactionHashHex = transactionHashUtils.getTransactionHashHex(signedTx); - const expectedError = new ExchangeRevertErrors.TransactionError( - ExchangeRevertErrors.TransactionErrorCode.AlreadyExecuted, - transactionHashHex, - ); - const tx = exchangeWrapper.executeTransactionAsync(signedTx, senderAddress); - return expect(tx).to.revertWith(expectedError); - }); - - it('should reset the currentContextAddress', async () => { - await exchangeWrapper.executeTransactionAsync(signedTx, senderAddress); - const currentContextAddress = await exchange.currentContextAddress.callAsync(); - expect(currentContextAddress).to.equal(constants.NULL_ADDRESS); - }); + describe('single order fills', () => { + for (const fnName of exchangeConstants.SINGLE_FILL_FN_NAMES) { + it(`${fnName} should revert if signature is invalid and not called by signer`, async () => { + const orders = [await orderFactory.newSignedOrderAsync()]; + const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders); + const transaction = takerTransactionFactory.newSignedTransaction(data); + const v = ethUtil.toBuffer(transaction.signature.slice(0, 4)); + const invalidR = ethUtil.sha3('invalidR'); + const invalidS = ethUtil.sha3('invalidS'); + const signatureType = ethUtil.toBuffer(`0x${transaction.signature.slice(-2)}`); + const invalidSigBuff = Buffer.concat([v, invalidR, invalidS, signatureType]); + const invalidSigHex = `0x${invalidSigBuff.toString('hex')}`; + transaction.signature = invalidSigHex; + const transactionHashHex = transactionHashUtils.getTransactionHashHex(transaction); + const expectedError = new ExchangeRevertErrors.TransactionSignatureError( + transactionHashHex, + transaction.signerAddress, + transaction.signature, + ); + const tx = exchangeWrapper.executeTransactionAsync(transaction, senderAddress); + return expect(tx).to.revertWith(expectedError); + }); + it(`${fnName} should be successful if signed by taker and called by sender`, async () => { + const orders = [await orderFactory.newSignedOrderAsync()]; + const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders); + const transaction = takerTransactionFactory.newSignedTransaction(data); + const transactionReceipt = await exchangeWrapper.executeTransactionAsync( + transaction, + senderAddress, + ); + const fillLogs = transactionReceipt.logs.filter( + log => (log as LogWithDecodedArgs).event === 'Fill', + ); + expect(fillLogs.length).to.eq(1); + const fillLogArgs = (fillLogs[0] as LogWithDecodedArgs).args; + expect(fillLogArgs.makerAddress).to.eq(makerAddress); + expect(fillLogArgs.takerAddress).to.eq(takerAddress); + expect(fillLogArgs.senderAddress).to.eq(senderAddress); + expect(fillLogArgs.feeRecipientAddress).to.eq(feeRecipientAddress); + expect(fillLogArgs.makerAssetData).to.eq(orders[0].makerAssetData); + expect(fillLogArgs.takerAssetData).to.eq(orders[0].takerAssetData); + expect(fillLogArgs.makerAssetFilledAmount).to.bignumber.eq(orders[0].makerAssetAmount); + expect(fillLogArgs.takerAssetFilledAmount).to.bignumber.eq(orders[0].takerAssetAmount); + expect(fillLogArgs.makerFeePaid).to.bignumber.eq(orders[0].makerFee); + expect(fillLogArgs.takerFeePaid).to.bignumber.eq(orders[0].takerFee); + expect(fillLogArgs.orderHash).to.eq(orderHashUtils.getOrderHashHex(orders[0])); + }); + it(`${fnName} should be successful if called by taker without a transaction signature`, async () => { + const orders = [await orderFactory.newSignedOrderAsync()]; + const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders); + const transaction = takerTransactionFactory.newSignedTransaction(data); + transaction.signature = constants.NULL_BYTES; + const transactionReceipt = await exchangeWrapper.executeTransactionAsync(transaction, takerAddress); + const fillLogs = transactionReceipt.logs.filter( + log => (log as LogWithDecodedArgs).event === 'Fill', + ); + expect(fillLogs.length).to.eq(1); + const fillLogArgs = (fillLogs[0] as LogWithDecodedArgs).args; + expect(fillLogArgs.makerAddress).to.eq(makerAddress); + expect(fillLogArgs.takerAddress).to.eq(takerAddress); + expect(fillLogArgs.senderAddress).to.eq(takerAddress); + expect(fillLogArgs.feeRecipientAddress).to.eq(feeRecipientAddress); + expect(fillLogArgs.makerAssetData).to.eq(orders[0].makerAssetData); + expect(fillLogArgs.takerAssetData).to.eq(orders[0].takerAssetData); + expect(fillLogArgs.makerAssetFilledAmount).to.bignumber.eq(orders[0].makerAssetAmount); + expect(fillLogArgs.takerAssetFilledAmount).to.bignumber.eq(orders[0].takerAssetAmount); + expect(fillLogArgs.makerFeePaid).to.bignumber.eq(orders[0].makerFee); + expect(fillLogArgs.takerFeePaid).to.bignumber.eq(orders[0].takerFee); + expect(fillLogArgs.orderHash).to.eq(orderHashUtils.getOrderHashHex(orders[0])); + }); + it(`${fnName} should return the correct data if successful`, async () => { + const order = await orderFactory.newSignedOrderAsync(); + const orders = [order]; + const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders); + const transaction = takerTransactionFactory.newSignedTransaction(data); + const returnData = await exchangeInstance.executeTransaction.callAsync( + transaction, + transaction.signature, + { + from: senderAddress, + }, + ); + const abi = artifacts.Exchange.compilerOutput.abi; + const methodAbi = abi.filter(abiItem => (abiItem as MethodAbi).name === fnName)[0] as MethodAbi; + const abiEncoder = new AbiEncoder.Method(methodAbi); + const fillResults: FillResults = abiEncoder.decodeReturnValues(returnData).fillResults; + expect(fillResults.makerAssetFilledAmount).to.be.bignumber.eq(order.makerAssetAmount); + expect(fillResults.takerAssetFilledAmount).to.be.bignumber.eq(order.takerAssetAmount); + expect(fillResults.makerFeePaid).to.be.bignumber.eq(order.makerFee); + expect(fillResults.takerFeePaid).to.be.bignumber.eq(order.takerFee); + }); + it(`${fnName} should revert if transaction has already been executed`, async () => { + const orders = [await orderFactory.newSignedOrderAsync()]; + const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders); + const transaction = takerTransactionFactory.newSignedTransaction(data); + await exchangeWrapper.executeTransactionAsync(transaction, senderAddress); + const transactionHashHex = transactionHashUtils.getTransactionHashHex(transaction); + const expectedError = new ExchangeRevertErrors.TransactionError( + ExchangeRevertErrors.TransactionErrorCode.AlreadyExecuted, + transactionHashHex, + ); + const tx = exchangeWrapper.executeTransactionAsync(transaction, senderAddress); + return expect(tx).to.revertWith(expectedError); + }); + it(`${fnName} should revert and rethrow error if executeTransaction is called recursively with a signature`, async () => { + const orders = [await orderFactory.newSignedOrderAsync()]; + const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders); + const transaction = takerTransactionFactory.newSignedTransaction(data); + const transactionHashHex = transactionHashUtils.getTransactionHashHex(transaction); + const recursiveData = exchangeInstance.executeTransaction.getABIEncodedTransactionData( + transaction, + transaction.signature, + ); + const recursiveTransaction = takerTransactionFactory.newSignedTransaction(recursiveData); + const recursiveTransactionHashHex = transactionHashUtils.getTransactionHashHex( + recursiveTransaction, + ); + const noReentrancyError = new ExchangeRevertErrors.TransactionError( + ExchangeRevertErrors.TransactionErrorCode.NoReentrancy, + transactionHashHex, + ).encode(); + const expectedError = new ExchangeRevertErrors.TransactionExecutionError( + recursiveTransactionHashHex, + noReentrancyError, + ); + const tx = exchangeWrapper.executeTransactionAsync(recursiveTransaction, senderAddress); + return expect(tx).to.revertWith(expectedError); + }); + it(`${fnName} should be successful if executeTransaction is called recursively by taker without a signature`, async () => { + const orders = [await orderFactory.newSignedOrderAsync()]; + const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders); + const transaction = takerTransactionFactory.newSignedTransaction(data); + const recursiveData = exchangeInstance.executeTransaction.getABIEncodedTransactionData( + transaction, + constants.NULL_BYTES, + ); + const recursiveTransaction = takerTransactionFactory.newSignedTransaction(recursiveData); + const transactionReceipt = await exchangeWrapper.executeTransactionAsync( + recursiveTransaction, + takerAddress, + ); + const fillLogs = transactionReceipt.logs.filter( + log => (log as LogWithDecodedArgs).event === 'Fill', + ); + expect(fillLogs.length).to.eq(1); + const fillLogArgs = (fillLogs[0] as LogWithDecodedArgs).args; + expect(fillLogArgs.makerAddress).to.eq(makerAddress); + expect(fillLogArgs.takerAddress).to.eq(takerAddress); + expect(fillLogArgs.senderAddress).to.eq(takerAddress); + expect(fillLogArgs.feeRecipientAddress).to.eq(feeRecipientAddress); + expect(fillLogArgs.makerAssetData).to.eq(orders[0].makerAssetData); + expect(fillLogArgs.takerAssetData).to.eq(orders[0].takerAssetData); + expect(fillLogArgs.makerAssetFilledAmount).to.bignumber.eq(orders[0].makerAssetAmount); + expect(fillLogArgs.takerAssetFilledAmount).to.bignumber.eq(orders[0].takerAssetAmount); + expect(fillLogArgs.makerFeePaid).to.bignumber.eq(orders[0].makerFee); + expect(fillLogArgs.takerFeePaid).to.bignumber.eq(orders[0].takerFee); + expect(fillLogArgs.orderHash).to.eq(orderHashUtils.getOrderHashHex(orders[0])); + }); + if (fnName !== ExchangeFunctionName.FillOrderNoThrow) { + it(`${fnName} should revert and rethrow error if the underlying function reverts`, async () => { + const order = await orderFactory.newSignedOrderAsync(); + order.signature = constants.NULL_BYTES; + const orders = [order]; + const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders); + const transaction = takerTransactionFactory.newSignedTransaction(data); + const transactionHashHex = transactionHashUtils.getTransactionHashHex(transaction); + const nestedError = new ExchangeRevertErrors.SignatureError( + ExchangeRevertErrors.SignatureErrorCode.InvalidLength, + orderHashUtils.getOrderHashHex(order), + order.makerAddress, + order.signature, + ).encode(); + const expectedError = new ExchangeRevertErrors.TransactionExecutionError( + transactionHashHex, + nestedError, + ); + const tx = exchangeWrapper.executeTransactionAsync(transaction, senderAddress); + return expect(tx).to.revertWith(expectedError); + }); + } + } }); - describe('cancelOrder', () => { - beforeEach(async () => { - const data = exchange.cancelOrder.getABIEncodedTransactionData(orderWithoutDomain); - signedTx = makerTransactionFactory.newSignedTransaction(data); - }); - - it('should throw if not called by specified sender', async () => { - const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); - const transactionHashHex = transactionHashUtils.getTransactionHashHex(signedTx); + it('should revert if not signed by or called by maker', async () => { + const order = await orderFactory.newSignedOrderAsync(); + const orders = [order]; + const data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.CancelOrder, orders); + const transaction = takerTransactionFactory.newSignedTransaction(data); + const transactionHashHex = transactionHashUtils.getTransactionHashHex(transaction); + const nestedError = new ExchangeRevertErrors.InvalidMakerError( + orderHashUtils.getOrderHashHex(order), + takerAddress, + ).encode(); const expectedError = new ExchangeRevertErrors.TransactionExecutionError( transactionHashHex, - new ExchangeRevertErrors.InvalidSenderError(orderHashHex, makerAddress).encode(), + nestedError, ); - const tx = exchangeWrapper.executeTransactionAsync(signedTx, makerAddress); + const tx = exchangeWrapper.executeTransactionAsync(transaction, senderAddress); return expect(tx).to.revertWith(expectedError); }); - - it('should cancel the order when signed by maker and called by sender', async () => { - await exchangeWrapper.executeTransactionAsync(signedTx, senderAddress); - const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); - const expectedError = new ExchangeRevertErrors.OrderStatusError(OrderStatus.Cancelled, orderHashHex); - const tx = exchangeWrapper.fillOrderAsync(signedOrder, senderAddress); - return expect(tx).to.revertWith(expectedError); + it('should be successful if signed by maker and called by sender', async () => { + const orders = [await orderFactory.newSignedOrderAsync()]; + const data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.CancelOrder, orders); + const transaction = makerTransactionFactory.newSignedTransaction(data); + const transactionReceipt = await exchangeWrapper.executeTransactionAsync(transaction, senderAddress); + const cancelLogs = transactionReceipt.logs.filter( + log => (log as LogWithDecodedArgs).event === 'Cancel', + ); + expect(cancelLogs.length).to.eq(1); + const cancelLogArgs = (cancelLogs[0] as LogWithDecodedArgs).args; + expect(cancelLogArgs.makerAddress).to.eq(makerAddress); + expect(cancelLogArgs.senderAddress).to.eq(senderAddress); + expect(cancelLogArgs.feeRecipientAddress).to.eq(feeRecipientAddress); + expect(cancelLogArgs.makerAssetData).to.eq(orders[0].makerAssetData); + expect(cancelLogArgs.takerAssetData).to.eq(orders[0].takerAssetData); + expect(cancelLogArgs.orderHash).to.eq(orderHashUtils.getOrderHashHex(orders[0])); + }); + it('should be successful if called by maker without a signature', async () => { + const orders = [await orderFactory.newSignedOrderAsync()]; + const data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.CancelOrder, orders); + const transaction = makerTransactionFactory.newSignedTransaction(data); + transaction.signature = constants.NULL_BYTES; + const transactionReceipt = await exchangeWrapper.executeTransactionAsync(transaction, makerAddress); + const cancelLogs = transactionReceipt.logs.filter( + log => (log as LogWithDecodedArgs).event === 'Cancel', + ); + expect(cancelLogs.length).to.eq(1); + const cancelLogArgs = (cancelLogs[0] as LogWithDecodedArgs).args; + expect(cancelLogArgs.makerAddress).to.eq(makerAddress); + expect(cancelLogArgs.senderAddress).to.eq(makerAddress); + expect(cancelLogArgs.feeRecipientAddress).to.eq(feeRecipientAddress); + expect(cancelLogArgs.makerAssetData).to.eq(orders[0].makerAssetData); + expect(cancelLogArgs.takerAssetData).to.eq(orders[0].takerAssetData); + expect(cancelLogArgs.orderHash).to.eq(orderHashUtils.getOrderHashHex(orders[0])); + }); + }); + describe('batchCancelOrders', () => { + it('should revert if not signed by or called by maker', async () => { + const orders = [await orderFactory.newSignedOrderAsync(), await orderFactory.newSignedOrderAsync()]; + const data = exchangeDataEncoder.encodeOrdersToExchangeData( + ExchangeFunctionName.BatchCancelOrders, + orders, + ); + const transaction = takerTransactionFactory.newSignedTransaction(data); + const transactionHashHex = transactionHashUtils.getTransactionHashHex(transaction); + const nestedError = new ExchangeRevertErrors.InvalidMakerError( + orderHashUtils.getOrderHashHex(orders[0]), + takerAddress, + ).encode(); + const expectedError = new ExchangeRevertErrors.TransactionExecutionError( + transactionHashHex, + nestedError, + ); + const tx = exchangeWrapper.executeTransactionAsync(transaction, senderAddress); + return expect(tx).to.revertWith(expectedError); + }); + it('should be successful if signed by maker and called by sender', async () => { + const orders = [await orderFactory.newSignedOrderAsync(), await orderFactory.newSignedOrderAsync()]; + const data = exchangeDataEncoder.encodeOrdersToExchangeData( + ExchangeFunctionName.BatchCancelOrders, + orders, + ); + const transaction = makerTransactionFactory.newSignedTransaction(data); + const transactionReceipt = await exchangeWrapper.executeTransactionAsync(transaction, senderAddress); + const cancelLogs = transactionReceipt.logs.filter( + log => (log as LogWithDecodedArgs).event === 'Cancel', + ); + expect(cancelLogs.length).to.eq(orders.length); + orders.forEach((order, index) => { + const cancelLogArgs = (cancelLogs[index] as LogWithDecodedArgs).args; + expect(cancelLogArgs.makerAddress).to.eq(makerAddress); + expect(cancelLogArgs.senderAddress).to.eq(senderAddress); + expect(cancelLogArgs.feeRecipientAddress).to.eq(feeRecipientAddress); + expect(cancelLogArgs.makerAssetData).to.eq(order.makerAssetData); + expect(cancelLogArgs.takerAssetData).to.eq(order.takerAssetData); + expect(cancelLogArgs.orderHash).to.eq(orderHashUtils.getOrderHashHex(order)); + }); + }); + it('should be successful if called by maker without a signature', async () => { + const orders = [await orderFactory.newSignedOrderAsync(), await orderFactory.newSignedOrderAsync()]; + const data = exchangeDataEncoder.encodeOrdersToExchangeData( + ExchangeFunctionName.BatchCancelOrders, + orders, + ); + const transaction = makerTransactionFactory.newSignedTransaction(data); + transaction.signature = constants.NULL_BYTES; + const transactionReceipt = await exchangeWrapper.executeTransactionAsync(transaction, makerAddress); + const cancelLogs = transactionReceipt.logs.filter( + log => (log as LogWithDecodedArgs).event === 'Cancel', + ); + expect(cancelLogs.length).to.eq(orders.length); + orders.forEach((order, index) => { + const cancelLogArgs = (cancelLogs[index] as LogWithDecodedArgs).args; + expect(cancelLogArgs.makerAddress).to.eq(makerAddress); + expect(cancelLogArgs.senderAddress).to.eq(makerAddress); + expect(cancelLogArgs.feeRecipientAddress).to.eq(feeRecipientAddress); + expect(cancelLogArgs.makerAssetData).to.eq(order.makerAssetData); + expect(cancelLogArgs.takerAssetData).to.eq(order.takerAssetData); + expect(cancelLogArgs.orderHash).to.eq(orderHashUtils.getOrderHashHex(order)); + }); }); }); - describe('cancelOrdersUpTo', () => { - let exchangeWrapperContract: ExchangeWrapperContract; - - before(async () => { - exchangeWrapperContract = await ExchangeWrapperContract.deployFrom0xArtifactAsync( - artifacts.ExchangeWrapper, - provider, - txDefaults, - exchange.address, + it('should be successful if signed by maker and called by sender', async () => { + const targetEpoch = constants.ZERO_AMOUNT; + const data = exchangeInstance.cancelOrdersUpTo.getABIEncodedTransactionData(targetEpoch); + const transaction = makerTransactionFactory.newSignedTransaction(data); + const transactionReceipt = await exchangeWrapper.executeTransactionAsync(transaction, senderAddress); + const cancelLogs = transactionReceipt.logs.filter( + log => (log as LogWithDecodedArgs).event === 'CancelUpTo', ); + expect(cancelLogs.length).to.eq(1); + const cancelLogArgs = (cancelLogs[0] as LogWithDecodedArgs).args; + expect(cancelLogArgs.makerAddress).to.eq(makerAddress); + expect(cancelLogArgs.senderAddress).to.eq(senderAddress); + expect(cancelLogArgs.orderEpoch).to.bignumber.eq(targetEpoch.plus(1)); }); + it('should be successful if called by maker without a signature', async () => { + const targetEpoch = constants.ZERO_AMOUNT; + const data = exchangeInstance.cancelOrdersUpTo.getABIEncodedTransactionData(targetEpoch); + const transaction = makerTransactionFactory.newSignedTransaction(data); + const transactionReceipt = await exchangeWrapper.executeTransactionAsync(transaction, makerAddress); + const cancelLogs = transactionReceipt.logs.filter( + log => (log as LogWithDecodedArgs).event === 'CancelUpTo', + ); + expect(cancelLogs.length).to.eq(1); + const cancelLogArgs = (cancelLogs[0] as LogWithDecodedArgs).args; + expect(cancelLogArgs.makerAddress).to.eq(makerAddress); + expect(cancelLogArgs.senderAddress).to.eq(constants.NULL_ADDRESS); + expect(cancelLogArgs.orderEpoch).to.bignumber.eq(targetEpoch.plus(1)); + }); + }); + describe('preSign', () => { + it('should preSign a hash for the signer', async () => { + const order = await orderFactory.newSignedOrderAsync(); + const orderHash = orderHashUtils.getOrderHashHex(order); + const data = exchangeInstance.preSign.getABIEncodedTransactionData(orderHash); + const transaction = takerTransactionFactory.newSignedTransaction(data); + let isPreSigned = await exchangeInstance.preSigned.callAsync(orderHash, takerAddress); + expect(isPreSigned).to.be.eq(false); + await exchangeWrapper.executeTransactionAsync(transaction, senderAddress); + isPreSigned = await exchangeInstance.preSigned.callAsync(orderHash, takerAddress); + expect(isPreSigned).to.be.eq(true); + }); + it('should preSign a hash for the caller if called without a signature', async () => { + const order = await orderFactory.newSignedOrderAsync(); + const orderHash = orderHashUtils.getOrderHashHex(order); + const data = exchangeInstance.preSign.getABIEncodedTransactionData(orderHash); + const transaction = takerTransactionFactory.newSignedTransaction(data); + transaction.signature = constants.NULL_BYTES; + let isPreSigned = await exchangeInstance.preSigned.callAsync(orderHash, takerAddress); + expect(isPreSigned).to.be.eq(false); + await exchangeWrapper.executeTransactionAsync(transaction, takerAddress); + isPreSigned = await exchangeInstance.preSigned.callAsync(orderHash, takerAddress); + expect(isPreSigned).to.be.eq(true); + }); + }); + describe('setSignatureValidatorApproval', () => { + it('should approve a validator for the signer', async () => { + const shouldApprove = true; + const data = exchangeInstance.setSignatureValidatorApproval.getABIEncodedTransactionData( + validatorAddress, + shouldApprove, + ); + const transaction = takerTransactionFactory.newSignedTransaction(data); + const transactionReceipt = await exchangeWrapper.executeTransactionAsync(transaction, senderAddress); + const validatorApprovalLogs = transactionReceipt.logs.filter( + log => + (log as LogWithDecodedArgs).event === + 'SignatureValidatorApproval', + ); + expect(validatorApprovalLogs.length).to.eq(1); + const validatorApprovalLogArgs = (validatorApprovalLogs[0] as LogWithDecodedArgs< + ExchangeSignatureValidatorApprovalEventArgs + >).args; + expect(validatorApprovalLogArgs.signerAddress).to.eq(takerAddress); + expect(validatorApprovalLogArgs.validatorAddress).to.eq(validatorAddress); + expect(validatorApprovalLogArgs.approved).to.eq(shouldApprove); + }); + it('should approve a validator for the caller if called with no signature', async () => { + const shouldApprove = true; + const data = exchangeInstance.setSignatureValidatorApproval.getABIEncodedTransactionData( + validatorAddress, + shouldApprove, + ); + const transaction = takerTransactionFactory.newSignedTransaction(data); + transaction.signature = constants.NULL_BYTES; + const transactionReceipt = await exchangeWrapper.executeTransactionAsync(transaction, takerAddress); + const validatorApprovalLogs = transactionReceipt.logs.filter( + log => + (log as LogWithDecodedArgs).event === + 'SignatureValidatorApproval', + ); + expect(validatorApprovalLogs.length).to.eq(1); + const validatorApprovalLogArgs = (validatorApprovalLogs[0] as LogWithDecodedArgs< + ExchangeSignatureValidatorApprovalEventArgs + >).args; + expect(validatorApprovalLogArgs.signerAddress).to.eq(takerAddress); + expect(validatorApprovalLogArgs.validatorAddress).to.eq(validatorAddress); + expect(validatorApprovalLogArgs.approved).to.eq(shouldApprove); + }); + }); + describe('setOrderValidatorApproval', () => { + it('should approve a validator for the signer', async () => { + const shouldApprove = true; + const data = exchangeInstance.setOrderValidatorApproval.getABIEncodedTransactionData( + validatorAddress, + shouldApprove, + ); + const transaction = takerTransactionFactory.newSignedTransaction(data); + const transactionReceipt = await exchangeWrapper.executeTransactionAsync(transaction, senderAddress); + const validatorApprovalLogs = transactionReceipt.logs.filter( + log => + (log as LogWithDecodedArgs).event === + 'SignatureValidatorApproval', + ); + expect(validatorApprovalLogs.length).to.eq(1); + const validatorApprovalLogArgs = (validatorApprovalLogs[0] as LogWithDecodedArgs< + ExchangeSignatureValidatorApprovalEventArgs + >).args; + expect(validatorApprovalLogArgs.signerAddress).to.eq(takerAddress); + expect(validatorApprovalLogArgs.validatorAddress).to.eq(validatorAddress); + expect(validatorApprovalLogArgs.approved).to.eq(shouldApprove); + }); + it('should approve a validator for the caller if called without a signature', async () => { + const shouldApprove = true; + const data = exchangeInstance.setOrderValidatorApproval.getABIEncodedTransactionData( + validatorAddress, + shouldApprove, + ); + const transaction = takerTransactionFactory.newSignedTransaction(data); + transaction.signature = constants.NULL_BYTES; + const transactionReceipt = await exchangeWrapper.executeTransactionAsync(transaction, takerAddress); + const validatorApprovalLogs = transactionReceipt.logs.filter( + log => + (log as LogWithDecodedArgs).event === + 'SignatureValidatorApproval', + ); + expect(validatorApprovalLogs.length).to.eq(1); + const validatorApprovalLogArgs = (validatorApprovalLogs[0] as LogWithDecodedArgs< + ExchangeSignatureValidatorApprovalEventArgs + >).args; + expect(validatorApprovalLogArgs.signerAddress).to.eq(takerAddress); + expect(validatorApprovalLogArgs.validatorAddress).to.eq(validatorAddress); + expect(validatorApprovalLogArgs.approved).to.eq(shouldApprove); + }); + }); + describe('batchExecuteTransactions', () => {}); + describe.only('examples', () => { + describe('ExchangeWrapper', () => { + let exchangeWrapperContract: ExchangeWrapperContract; - it("should cancel an order if called from the order's sender", async () => { - const orderSalt = new BigNumber(0); - signedOrder = await orderFactory.newSignedOrderAsync({ - senderAddress: exchangeWrapperContract.address, - salt: orderSalt, + before(async () => { + exchangeWrapperContract = await ExchangeWrapperContract.deployFrom0xArtifactAsync( + artifacts.ExchangeWrapper, + provider, + txDefaults, + exchangeInstance.address, + ); }); - const targetOrderEpoch = orderSalt.plus(1); - const cancelData = exchange.cancelOrdersUpTo.getABIEncodedTransactionData(targetOrderEpoch); - const signedCancelTx = makerTransactionFactory.newSignedTransaction(cancelData); - await exchangeWrapperContract.cancelOrdersUpTo.sendTransactionAsync( - targetOrderEpoch, - signedCancelTx.salt, - signedCancelTx.signature, - { - from: makerAddress, - }, - ); - const takerAssetFillAmount = signedOrder.takerAssetAmount; - orderWithoutDomain = orderUtils.getOrderWithoutDomain(signedOrder); - const fillData = exchange.fillOrder.getABIEncodedTransactionData( - orderWithoutDomain, - takerAssetFillAmount, - signedOrder.signature, - ); - const signedFillTx = takerTransactionFactory.newSignedTransaction(fillData); - const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); - const transactionHashHex = transactionHashUtils.getTransactionHashHex(signedFillTx); - const expectedError = new ExchangeRevertErrors.TransactionExecutionError( - transactionHashHex, - new ExchangeRevertErrors.OrderStatusError(OrderStatus.Cancelled, orderHashHex).encode(), - ); - const tx = exchangeWrapperContract.fillOrder.sendTransactionAsync( - orderWithoutDomain, - takerAssetFillAmount, - signedFillTx.salt, - signedOrder.signature, - signedFillTx.signature, - { from: takerAddress }, - ); - return expect(tx).to.revertWith(expectedError); - }); + it("should cancel an order if called from the order's sender", async () => { + const orderSalt = constants.ZERO_AMOUNT; + const signedOrder = await orderFactory.newSignedOrderAsync({ + senderAddress: exchangeWrapperContract.address, + salt: orderSalt, + }); + const targetOrderEpoch = orderSalt.plus(1); + const cancelData = exchangeInstance.cancelOrdersUpTo.getABIEncodedTransactionData(targetOrderEpoch); + const cancelTransaction = makerTransactionFactory.newSignedTransaction(cancelData); + await web3Wrapper.awaitTransactionSuccessAsync( + await exchangeWrapperContract.cancelOrdersUpTo.sendTransactionAsync( + targetOrderEpoch, + cancelTransaction.salt, + cancelTransaction.signature, + { from: makerAddress }, + ), + constants.AWAIT_TRANSACTION_MINED_MS, + ); - it("should not cancel an order if not called from the order's sender", async () => { - const orderSalt = new BigNumber(0); - signedOrder = await orderFactory.newSignedOrderAsync({ - senderAddress: exchangeWrapperContract.address, - salt: orderSalt, + const takerAssetFillAmount = signedOrder.takerAssetAmount; + const fillData = exchangeInstance.fillOrder.getABIEncodedTransactionData( + signedOrder, + takerAssetFillAmount, + signedOrder.signature, + ); + const fillTransaction = takerTransactionFactory.newSignedTransaction(fillData); + const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); + const transactionHashHex = transactionHashUtils.getTransactionHashHex(fillTransaction); + const expectedError = new ExchangeRevertErrors.TransactionExecutionError( + transactionHashHex, + new ExchangeRevertErrors.OrderStatusError(OrderStatus.Cancelled, orderHashHex).encode(), + ); + const tx = exchangeWrapperContract.fillOrder.sendTransactionAsync( + signedOrder, + takerAssetFillAmount, + fillTransaction.salt, + signedOrder.signature, + fillTransaction.signature, + { from: takerAddress }, + ); + return expect(tx).to.revertWith(expectedError); }); - const targetOrderEpoch = orderSalt.plus(1); - await exchangeWrapper.cancelOrdersUpToAsync(targetOrderEpoch, makerAddress); - erc20Balances = await erc20Wrapper.getBalancesAsync(); - const takerAssetFillAmount = signedOrder.takerAssetAmount; - orderWithoutDomain = orderUtils.getOrderWithoutDomain(signedOrder); - const data = exchange.fillOrder.getABIEncodedTransactionData( - orderWithoutDomain, - takerAssetFillAmount, - signedOrder.signature, - ); - signedTx = takerTransactionFactory.newSignedTransaction(data); - await exchangeWrapperContract.fillOrder.sendTransactionAsync( - orderWithoutDomain, - takerAssetFillAmount, - signedTx.salt, - signedOrder.signature, - signedTx.signature, - { from: takerAddress }, - ); + it("should not cancel an order if not called from the order's sender", async () => { + const orderSalt = constants.ZERO_AMOUNT; + const signedOrder = await orderFactory.newSignedOrderAsync({ + senderAddress: exchangeWrapperContract.address, + salt: orderSalt, + }); + const targetOrderEpoch = orderSalt.plus(1); + await exchangeWrapper.cancelOrdersUpToAsync(targetOrderEpoch, makerAddress); - const newBalances = await erc20Wrapper.getBalancesAsync(); - const makerAssetFillAmount = takerAssetFillAmount - .times(signedOrder.makerAssetAmount) - .dividedToIntegerBy(signedOrder.takerAssetAmount); - const makerFeePaid = signedOrder.makerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(signedOrder.makerAssetAmount); - const takerFeePaid = signedOrder.takerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(signedOrder.makerAssetAmount); - expect(newBalances[makerAddress][defaultMakerTokenAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultMakerTokenAddress].minus(makerAssetFillAmount), - ); - expect(newBalances[makerAddress][defaultTakerTokenAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultTakerTokenAddress].plus(takerAssetFillAmount), - ); - expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[makerAddress][zrxToken.address].minus(makerFeePaid), - ); - expect(newBalances[takerAddress][defaultTakerTokenAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultTakerTokenAddress].minus(takerAssetFillAmount), - ); - expect(newBalances[takerAddress][defaultMakerTokenAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultMakerTokenAddress].plus(makerAssetFillAmount), - ); - expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[takerAddress][zrxToken.address].minus(takerFeePaid), - ); - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].plus(makerFeePaid.plus(takerFeePaid)), - ); + const takerAssetFillAmount = signedOrder.takerAssetAmount; + const data = exchangeInstance.fillOrder.getABIEncodedTransactionData( + signedOrder, + takerAssetFillAmount, + signedOrder.signature, + ); + const transaction = takerTransactionFactory.newSignedTransaction(data); + const logDecoder = new LogDecoder(web3Wrapper, artifacts); + const transactionReceipt = await logDecoder.getTxWithDecodedLogsAsync( + await exchangeWrapperContract.fillOrder.sendTransactionAsync( + signedOrder, + takerAssetFillAmount, + transaction.salt, + signedOrder.signature, + transaction.signature, + { from: takerAddress }, + ), + ); + const fillLogs = transactionReceipt.logs.filter( + log => (log as LogWithDecodedArgs).event === 'Fill', + ); + expect(fillLogs.length).to.eq(1); + const fillLogArgs = (fillLogs[0] as LogWithDecodedArgs).args; + expect(fillLogArgs.makerAddress).to.eq(makerAddress); + expect(fillLogArgs.takerAddress).to.eq(takerAddress); + expect(fillLogArgs.senderAddress).to.eq(exchangeWrapperContract.address); + expect(fillLogArgs.feeRecipientAddress).to.eq(feeRecipientAddress); + expect(fillLogArgs.makerAssetData).to.eq(signedOrder.makerAssetData); + expect(fillLogArgs.takerAssetData).to.eq(signedOrder.takerAssetData); + expect(fillLogArgs.makerAssetFilledAmount).to.bignumber.eq(signedOrder.makerAssetAmount); + expect(fillLogArgs.takerAssetFilledAmount).to.bignumber.eq(signedOrder.takerAssetAmount); + expect(fillLogArgs.makerFeePaid).to.bignumber.eq(signedOrder.makerFee); + expect(fillLogArgs.takerFeePaid).to.bignumber.eq(signedOrder.takerFee); + expect(fillLogArgs.orderHash).to.eq(orderHashUtils.getOrderHashHex(signedOrder)); + }); }); - }); - }); + describe('Whitelist', () => { + let whitelistContract: WhitelistContract; + before(async () => { + whitelistContract = await WhitelistContract.deployFrom0xArtifactAsync( + artifacts.Whitelist, + provider, + txDefaults, + exchangeInstance.address, + ); + const isApproved = true; + await web3Wrapper.awaitTransactionSuccessAsync( + await exchangeInstance.setSignatureValidatorApproval.sendTransactionAsync( + whitelistContract.address, + isApproved, + { from: takerAddress }, + ), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + }); - describe('Whitelist', () => { - let whitelist: WhitelistContract; - let whitelistOrderFactory: OrderFactory; + it('should revert if maker has not been whitelisted', async () => { + const isApproved = true; + await web3Wrapper.awaitTransactionSuccessAsync( + await whitelistContract.updateWhitelistStatus.sendTransactionAsync(takerAddress, isApproved, { + from: owner, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); - before(async () => { - whitelist = await WhitelistContract.deployFrom0xArtifactAsync( - artifacts.Whitelist, - provider, - txDefaults, - exchange.address, - ); - const isApproved = true; - await web3Wrapper.awaitTransactionSuccessAsync( - await exchange.setSignatureValidatorApproval.sendTransactionAsync(whitelist.address, isApproved, { - from: takerAddress, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - const defaultOrderParams = { - ...constants.STATIC_ORDER_PARAMS, - senderAddress: whitelist.address, - makerAddress, - feeRecipientAddress, - makerAssetData: assetDataUtils.encodeERC20AssetData(defaultMakerTokenAddress), - takerAssetData: assetDataUtils.encodeERC20AssetData(defaultTakerTokenAddress), - domain, - }; - whitelistOrderFactory = new OrderFactory(makerPrivateKey, defaultOrderParams); - }); + const signedOrder = await orderFactory.newSignedOrderAsync({ + senderAddress: whitelistContract.address, + }); + const takerAssetFillAmount = signedOrder.takerAssetAmount; + const salt = generatePseudoRandomSalt(); + const tx = whitelistContract.fillOrderIfWhitelisted.sendTransactionAsync( + signedOrder, + takerAssetFillAmount, + salt, + signedOrder.signature, + { from: takerAddress }, + ); + return expect(tx).to.revertWith(RevertReason.MakerNotWhitelisted); + }); - beforeEach(async () => { - signedOrder = await whitelistOrderFactory.newSignedOrderAsync(); - erc20Balances = await erc20Wrapper.getBalancesAsync(); - }); + it('should revert if taker has not been whitelisted', async () => { + const isApproved = true; + await web3Wrapper.awaitTransactionSuccessAsync( + await whitelistContract.updateWhitelistStatus.sendTransactionAsync(makerAddress, isApproved, { + from: owner, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); - it('should revert if maker has not been whitelisted', async () => { - const isApproved = true; - await web3Wrapper.awaitTransactionSuccessAsync( - await whitelist.updateWhitelistStatus.sendTransactionAsync(takerAddress, isApproved, { from: owner }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); + const signedOrder = await orderFactory.newSignedOrderAsync({ + senderAddress: whitelistContract.address, + }); + const takerAssetFillAmount = signedOrder.takerAssetAmount; + const salt = generatePseudoRandomSalt(); + const tx = whitelistContract.fillOrderIfWhitelisted.sendTransactionAsync( + signedOrder, + takerAssetFillAmount, + salt, + signedOrder.signature, + { from: takerAddress }, + ); + return expect(tx).to.revertWith(RevertReason.TakerNotWhitelisted); + }); - orderWithoutDomain = orderUtils.getOrderWithoutDomain(signedOrder); - const takerAssetFillAmount = signedOrder.takerAssetAmount; - const salt = generatePseudoRandomSalt(); - const tx = whitelist.fillOrderIfWhitelisted.sendTransactionAsync( - orderWithoutDomain, - takerAssetFillAmount, - salt, - signedOrder.signature, - { from: takerAddress }, - ); - return expect(tx).to.revertWith(RevertReason.MakerNotWhitelisted); - }); + it('should fill the order if maker and taker have been whitelisted', async () => { + const isApproved = true; + await web3Wrapper.awaitTransactionSuccessAsync( + await whitelistContract.updateWhitelistStatus.sendTransactionAsync(makerAddress, isApproved, { + from: owner, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); - it('should revert if taker has not been whitelisted', async () => { - const isApproved = true; - await web3Wrapper.awaitTransactionSuccessAsync( - await whitelist.updateWhitelistStatus.sendTransactionAsync(makerAddress, isApproved, { from: owner }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); + await web3Wrapper.awaitTransactionSuccessAsync( + await whitelistContract.updateWhitelistStatus.sendTransactionAsync(takerAddress, isApproved, { + from: owner, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); - orderWithoutDomain = orderUtils.getOrderWithoutDomain(signedOrder); - const takerAssetFillAmount = signedOrder.takerAssetAmount; - const salt = generatePseudoRandomSalt(); - const tx = whitelist.fillOrderIfWhitelisted.sendTransactionAsync( - orderWithoutDomain, - takerAssetFillAmount, - salt, - signedOrder.signature, - { from: takerAddress }, - ); - return expect(tx).to.revertWith(RevertReason.TakerNotWhitelisted); - }); + const signedOrder = await orderFactory.newSignedOrderAsync({ + senderAddress: whitelistContract.address, + }); + const takerAssetFillAmount = signedOrder.takerAssetAmount; + const salt = generatePseudoRandomSalt(); + const logDecoder = new LogDecoder(web3Wrapper, artifacts); + const transactionReceipt = await logDecoder.getTxWithDecodedLogsAsync( + await whitelistContract.fillOrderIfWhitelisted.sendTransactionAsync( + signedOrder, + takerAssetFillAmount, + salt, + signedOrder.signature, + { from: takerAddress }, + ), + ); - it('should fill the order if maker and taker have been whitelisted', async () => { - const isApproved = true; - await web3Wrapper.awaitTransactionSuccessAsync( - await whitelist.updateWhitelistStatus.sendTransactionAsync(makerAddress, isApproved, { from: owner }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - - await web3Wrapper.awaitTransactionSuccessAsync( - await whitelist.updateWhitelistStatus.sendTransactionAsync(takerAddress, isApproved, { from: owner }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - - orderWithoutDomain = orderUtils.getOrderWithoutDomain(signedOrder); - const takerAssetFillAmount = signedOrder.takerAssetAmount; - const salt = generatePseudoRandomSalt(); - await web3Wrapper.awaitTransactionSuccessAsync( - await whitelist.fillOrderIfWhitelisted.sendTransactionAsync( - orderWithoutDomain, - takerAssetFillAmount, - salt, - signedOrder.signature, - { from: takerAddress }, - ), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - - const newBalances = await erc20Wrapper.getBalancesAsync(); - - const makerAssetFillAmount = signedOrder.makerAssetAmount; - const makerFeePaid = signedOrder.makerFee; - const takerFeePaid = signedOrder.takerFee; - - expect(newBalances[makerAddress][defaultMakerTokenAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultMakerTokenAddress].minus(makerAssetFillAmount), - ); - expect(newBalances[makerAddress][defaultTakerTokenAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultTakerTokenAddress].plus(takerAssetFillAmount), - ); - expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[makerAddress][zrxToken.address].minus(makerFeePaid), - ); - expect(newBalances[takerAddress][defaultTakerTokenAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultTakerTokenAddress].minus(takerAssetFillAmount), - ); - expect(newBalances[takerAddress][defaultMakerTokenAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultMakerTokenAddress].plus(makerAssetFillAmount), - ); - expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[takerAddress][zrxToken.address].minus(takerFeePaid), - ); - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].plus(makerFeePaid.plus(takerFeePaid)), - ); + const fillLogs = transactionReceipt.logs.filter( + log => (log as LogWithDecodedArgs).event === 'Fill', + ); + expect(fillLogs.length).to.eq(1); + const fillLogArgs = (fillLogs[0] as LogWithDecodedArgs).args; + expect(fillLogArgs.makerAddress).to.eq(makerAddress); + expect(fillLogArgs.takerAddress).to.eq(takerAddress); + expect(fillLogArgs.senderAddress).to.eq(whitelistContract.address); + expect(fillLogArgs.feeRecipientAddress).to.eq(feeRecipientAddress); + expect(fillLogArgs.makerAssetData).to.eq(signedOrder.makerAssetData); + expect(fillLogArgs.takerAssetData).to.eq(signedOrder.takerAssetData); + expect(fillLogArgs.makerAssetFilledAmount).to.bignumber.eq(signedOrder.makerAssetAmount); + expect(fillLogArgs.takerAssetFilledAmount).to.bignumber.eq(signedOrder.takerAssetAmount); + expect(fillLogArgs.makerFeePaid).to.bignumber.eq(signedOrder.makerFee); + expect(fillLogArgs.takerFeePaid).to.bignumber.eq(signedOrder.takerFee); + expect(fillLogArgs.orderHash).to.eq(orderHashUtils.getOrderHashHex(signedOrder)); + }); + }); }); }); }); diff --git a/contracts/exchange/test/utils/exchange_data_encoder.ts b/contracts/exchange/test/utils/exchange_data_encoder.ts index 51a6945b6e..3c20992cf2 100644 --- a/contracts/exchange/test/utils/exchange_data_encoder.ts +++ b/contracts/exchange/test/utils/exchange_data_encoder.ts @@ -47,10 +47,10 @@ export const exchangeDataEncoder = { true, ); } else if (fnName === ExchangeFunctionName.SetOrderValidatorApproval) { - // data = exchangeInstance.setOrderValidatorApproval.getABIEncodedTransactionData( - // constants.NULL_ADDRESS, - // true, - // ); + data = exchangeInstance.setOrderValidatorApproval.getABIEncodedTransactionData( + constants.NULL_ADDRESS, + true, + ); } else { throw new Error(`Error: ${fnName} not a supported function`); }