diff --git a/contracts/erc20-bridge-sampler/test/erc20-bridge-sampler.ts b/contracts/erc20-bridge-sampler/test/erc20-bridge-sampler.ts index 2f47d7dca0..d008c938b0 100644 --- a/contracts/erc20-bridge-sampler/test/erc20-bridge-sampler.ts +++ b/contracts/erc20-bridge-sampler/test/erc20-bridge-sampler.ts @@ -198,7 +198,7 @@ blockchainTests('erc20-bridge-sampler', env => { } function getDeterministicOrderInfo(order: Order): OrderInfo { - const hash = getPackedHash(toHex(order.salt, 32)); + const hash = getPackedHash(hexLeftPad(order.salt, 32)); return { orderHash: hash, orderStatus: new BigNumber(hash).mod(255).toNumber(), diff --git a/contracts/exchange/test/transactions.ts b/contracts/exchange/test/transactions.ts deleted file mode 100644 index 584fa60eb0..0000000000 --- a/contracts/exchange/test/transactions.ts +++ /dev/null @@ -1,996 +0,0 @@ -// tslint:disable: max-file-line-count -import { ERC20ProxyContract, ERC20Wrapper } from '@0x/contracts-asset-proxy'; -import { DevUtilsContract } from '@0x/contracts-dev-utils'; -import { artifacts as erc20Artifacts, DummyERC20TokenContract } from '@0x/contracts-erc20'; -import { - blockchainTests, - constants, - describe, - ExchangeFunctionName, - expect, - getLatestBlockTimestampAsync, - OrderFactory, - orderHashUtils, - TransactionFactory, - transactionHashUtils, -} from '@0x/contracts-test-utils'; -import { FillResults, OrderStatus } from '@0x/types'; -import { AbiEncoder, BigNumber, ExchangeRevertErrors } from '@0x/utils'; -import { LogWithDecodedArgs, MethodAbi } from 'ethereum-types'; -import * as ethUtil from 'ethereumjs-util'; -import * as _ from 'lodash'; - -import { exchangeDataEncoder } from '../src/exchange_data_encoder'; - -import { artifacts as localArtifacts } from './artifacts'; -import { ExchangeWrapper } from './utils/exchange_wrapper'; -import { - ExchangeCancelEventArgs, - ExchangeCancelUpToEventArgs, - ExchangeContract, - ExchangeFillEventArgs, - ExchangeSignatureValidatorApprovalEventArgs, - ExchangeTransactionExecutionEventArgs, -} from './wrappers'; - -const artifacts = { ...erc20Artifacts, ...localArtifacts }; - -// tslint:disable:no-unnecessary-type-assertion -blockchainTests.resets('Exchange transactions', env => { - let chainId: number; - let senderAddress: string; - let owner: string; - let makerAddress: string; - let takerAddress: string; - let feeRecipientAddress: string; - let validatorAddress: string; - let taker2Address: string; - - let erc20TokenA: DummyERC20TokenContract; - let erc20TokenB: DummyERC20TokenContract; - let takerFeeToken: DummyERC20TokenContract; - let makerFeeToken: DummyERC20TokenContract; - let exchangeInstance: ExchangeContract; - let erc20Proxy: ERC20ProxyContract; - - let orderFactory: OrderFactory; - let makerTransactionFactory: TransactionFactory; - let takerTransactionFactory: TransactionFactory; - let taker2TransactionFactory: TransactionFactory; - let exchangeWrapper: ExchangeWrapper; - let erc20Wrapper: ERC20Wrapper; - - let defaultMakerTokenAddress: string; - let defaultTakerTokenAddress: string; - let defaultMakerFeeTokenAddress: string; - let defaultTakerFeeTokenAddress: string; - let makerPrivateKey: Buffer; - let takerPrivateKey: Buffer; - let taker2PrivateKey: Buffer; - - const devUtils = new DevUtilsContract(constants.NULL_ADDRESS, env.provider, env.txDefaults); - before(async () => { - chainId = await env.getChainIdAsync(); - const accounts = await env.getAccountAddressesAsync(); - const usedAddresses = ([ - owner, - senderAddress, - makerAddress, - takerAddress, - feeRecipientAddress, - validatorAddress, - taker2Address, - ] = _.slice(accounts, 0, 7)); - - erc20Wrapper = new ERC20Wrapper(env.provider, usedAddresses, owner); - - const numDummyErc20ToDeploy = 4; - [erc20TokenA, erc20TokenB, takerFeeToken, makerFeeToken] = await erc20Wrapper.deployDummyTokensAsync( - numDummyErc20ToDeploy, - constants.DUMMY_TOKEN_DECIMALS, - ); - erc20Proxy = await erc20Wrapper.deployProxyAsync(); - await erc20Wrapper.setBalancesAndAllowancesAsync(); - - exchangeInstance = await ExchangeContract.deployFrom0xArtifactAsync( - artifacts.Exchange, - env.provider, - env.txDefaults, - {}, - new BigNumber(chainId), - ); - exchangeWrapper = new ExchangeWrapper(exchangeInstance); - await exchangeWrapper.registerAssetProxyAsync(erc20Proxy.address, owner); - - await erc20Proxy.addAuthorizedAddress(exchangeInstance.address).awaitTransactionSuccessAsync({ from: owner }); - - defaultMakerTokenAddress = erc20TokenA.address; - defaultTakerTokenAddress = erc20TokenB.address; - defaultMakerFeeTokenAddress = makerFeeToken.address; - defaultTakerFeeTokenAddress = takerFeeToken.address; - - const defaultOrderParams = { - ...constants.STATIC_ORDER_PARAMS, - makerAddress, - feeRecipientAddress, - makerAssetData: await devUtils.encodeERC20AssetData(defaultMakerTokenAddress).callAsync(), - takerAssetData: await devUtils.encodeERC20AssetData(defaultTakerTokenAddress).callAsync(), - makerFeeAssetData: await devUtils.encodeERC20AssetData(defaultMakerFeeTokenAddress).callAsync(), - takerFeeAssetData: await devUtils.encodeERC20AssetData(defaultTakerFeeTokenAddress).callAsync(), - exchangeAddress: exchangeInstance.address, - chainId, - }; - makerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddress)]; - takerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(takerAddress)]; - taker2PrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(taker2Address)]; - orderFactory = new OrderFactory(makerPrivateKey, defaultOrderParams); - makerTransactionFactory = new TransactionFactory(makerPrivateKey, exchangeInstance.address, chainId); - takerTransactionFactory = new TransactionFactory(takerPrivateKey, exchangeInstance.address, chainId); - taker2TransactionFactory = new TransactionFactory(taker2PrivateKey, exchangeInstance.address, chainId); - }); - describe('executeTransaction', () => { - describe('general functionality', () => { - it('should log the correct transactionHash if successfully executed', async () => { - const order = await orderFactory.newSignedOrderAsync(); - const orders = [order]; - const data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, orders); - const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data }); - const transactionReceipt = await exchangeWrapper.executeTransactionAsync(transaction, takerAddress); - const transactionExecutionLogs = transactionReceipt.logs.filter( - log => - (log as LogWithDecodedArgs).event === - 'TransactionExecution', - ); - expect(transactionExecutionLogs.length).to.eq(1); - const executionLogArgs = (transactionExecutionLogs[0] as LogWithDecodedArgs< - ExchangeTransactionExecutionEventArgs - >).args; - expect(executionLogArgs.transactionHash).to.equal( - transactionHashUtils.getTransactionHashHex(transaction), - ); - }); - it('should revert if the transaction is expired', async () => { - const order = await orderFactory.newSignedOrderAsync(); - const orders = [order]; - const data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, orders); - const currentTimestamp = await getLatestBlockTimestampAsync(); - const transaction = await takerTransactionFactory.newSignedTransactionAsync({ - data, - expirationTimeSeconds: new BigNumber(currentTimestamp).minus(10), - }); - const transactionHashHex = transactionHashUtils.getTransactionHashHex(transaction); - const expectedError = new ExchangeRevertErrors.TransactionError( - ExchangeRevertErrors.TransactionErrorCode.Expired, - transactionHashHex, - ); - const tx = exchangeWrapper.executeTransactionAsync(transaction, senderAddress); - return expect(tx).to.revertWith(expectedError); - }); - it('should revert if the actual gasPrice is greater than expected', async () => { - const order = await orderFactory.newSignedOrderAsync(); - const orders = [order]; - const data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, orders); - const transaction = await takerTransactionFactory.newSignedTransactionAsync({ - data, - }); - const transactionHashHex = transactionHashUtils.getTransactionHashHex(transaction); - const actualGasPrice = transaction.gasPrice.plus(1); - const expectedError = new ExchangeRevertErrors.TransactionGasPriceError( - transactionHashHex, - actualGasPrice, - transaction.gasPrice, - ); - const tx = exchangeInstance - .executeTransaction(transaction, transaction.signature) - .sendTransactionAsync({ gasPrice: actualGasPrice, from: senderAddress }); - return expect(tx).to.revertWith(expectedError); - }); - it('should revert if the actual gasPrice is less than expected', async () => { - const order = await orderFactory.newSignedOrderAsync(); - const orders = [order]; - const data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, orders); - const transaction = await takerTransactionFactory.newSignedTransactionAsync({ - data, - }); - const transactionHashHex = transactionHashUtils.getTransactionHashHex(transaction); - const actualGasPrice = transaction.gasPrice.minus(1); - const expectedError = new ExchangeRevertErrors.TransactionGasPriceError( - transactionHashHex, - actualGasPrice, - transaction.gasPrice, - ); - const tx = exchangeInstance - .executeTransaction(transaction, transaction.signature) - .sendTransactionAsync({ gasPrice: actualGasPrice, from: senderAddress }); - return expect(tx).to.revertWith(expectedError); - }); - }); - describe('fill methods', () => { - for (const fnName of [ - ...constants.SINGLE_FILL_FN_NAMES, - ...constants.BATCH_FILL_FN_NAMES, - ...constants.MARKET_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 = await takerTransactionFactory.newSignedTransactionAsync({ 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.SignatureError( - ExchangeRevertErrors.SignatureErrorCode.BadTransactionSignature, - 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 = await takerTransactionFactory.newSignedTransactionAsync({ 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 = await takerTransactionFactory.newSignedTransactionAsync({ 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 = await takerTransactionFactory.newSignedTransactionAsync({ data }); - const returnData = await exchangeInstance - .executeTransaction(transaction, transaction.signature) - .callAsync({ - 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 decodedReturnData = abiEncoder.decodeReturnValues(returnData); - const fillResults = - constants.BATCH_FILL_FN_NAMES.indexOf(fnName) !== -1 - ? decodedReturnData.fillResults[0] - : decodedReturnData.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 = await takerTransactionFactory.newSignedTransactionAsync({ 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 = await takerTransactionFactory.newSignedTransactionAsync({ data }); - const transactionHashHex = transactionHashUtils.getTransactionHashHex(transaction); - const recursiveData = exchangeInstance - .executeTransaction(transaction, transaction.signature) - .getABIEncodedTransactionData(); - const recursiveTransaction = await takerTransactionFactory.newSignedTransactionAsync({ - data: recursiveData, - }); - const recursiveTransactionHashHex = transactionHashUtils.getTransactionHashHex( - recursiveTransaction, - ); - const noReentrancyError = new ExchangeRevertErrors.TransactionInvalidContextError( - transactionHashHex, - transaction.signerAddress, - ).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 = await takerTransactionFactory.newSignedTransactionAsync({ data }); - const recursiveData = exchangeInstance - .executeTransaction(transaction, constants.NULL_BYTES) - .getABIEncodedTransactionData(); - const recursiveTransaction = await takerTransactionFactory.newSignedTransactionAsync({ - data: 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 ( - [ - ExchangeFunctionName.FillOrderNoThrow, - ExchangeFunctionName.BatchFillOrdersNoThrow, - ExchangeFunctionName.MarketBuyOrdersNoThrow, - ExchangeFunctionName.MarketSellOrdersNoThrow, - ExchangeFunctionName.MarketBuyOrdersFillOrKill, - ExchangeFunctionName.MarketSellOrdersFillOrKill, - ].indexOf(fnName) === -1 - ) { - 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 = await takerTransactionFactory.newSignedTransactionAsync({ 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', () => { - 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 = await takerTransactionFactory.newSignedTransactionAsync({ data }); - const transactionHashHex = transactionHashUtils.getTransactionHashHex(transaction); - const nestedError = new ExchangeRevertErrors.ExchangeInvalidContextError( - ExchangeRevertErrors.ExchangeContextErrorCodes.InvalidMaker, - orderHashUtils.getOrderHashHex(order), - 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()]; - const data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.CancelOrder, orders); - const transaction = await makerTransactionFactory.newSignedTransactionAsync({ 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 = await makerTransactionFactory.newSignedTransactionAsync({ 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 = await takerTransactionFactory.newSignedTransactionAsync({ data }); - const transactionHashHex = transactionHashUtils.getTransactionHashHex(transaction); - const nestedError = new ExchangeRevertErrors.ExchangeInvalidContextError( - ExchangeRevertErrors.ExchangeContextErrorCodes.InvalidMaker, - 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 = await makerTransactionFactory.newSignedTransactionAsync({ 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 = await makerTransactionFactory.newSignedTransactionAsync({ 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', () => { - it('should be successful if signed by maker and called by sender', async () => { - const targetEpoch = constants.ZERO_AMOUNT; - const data = exchangeInstance.cancelOrdersUpTo(targetEpoch).getABIEncodedTransactionData(); - const transaction = await makerTransactionFactory.newSignedTransactionAsync({ 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.orderSenderAddress).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(targetEpoch).getABIEncodedTransactionData(); - const transaction = await makerTransactionFactory.newSignedTransactionAsync({ 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.orderSenderAddress).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(orderHash).getABIEncodedTransactionData(); - const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data }); - let isPreSigned = await exchangeInstance.preSigned(orderHash, takerAddress).callAsync(); - expect(isPreSigned).to.be.eq(false); - await exchangeWrapper.executeTransactionAsync(transaction, senderAddress); - isPreSigned = await exchangeInstance.preSigned(orderHash, takerAddress).callAsync(); - 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(orderHash).getABIEncodedTransactionData(); - const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data }); - transaction.signature = constants.NULL_BYTES; - let isPreSigned = await exchangeInstance.preSigned(orderHash, takerAddress).callAsync(); - expect(isPreSigned).to.be.eq(false); - await exchangeWrapper.executeTransactionAsync(transaction, takerAddress); - isPreSigned = await exchangeInstance.preSigned(orderHash, takerAddress).callAsync(); - expect(isPreSigned).to.be.eq(true); - }); - }); - describe('setSignatureValidatorApproval', () => { - it('should approve a validator for the signer', async () => { - const shouldApprove = true; - const data = exchangeInstance - .setSignatureValidatorApproval(validatorAddress, shouldApprove) - .getABIEncodedTransactionData(); - const transaction = await takerTransactionFactory.newSignedTransactionAsync({ 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.isApproved).to.eq(shouldApprove); - }); - it('should approve a validator for the caller if called with no signature', async () => { - const shouldApprove = true; - const data = exchangeInstance - .setSignatureValidatorApproval(validatorAddress, shouldApprove) - .getABIEncodedTransactionData(); - const transaction = await takerTransactionFactory.newSignedTransactionAsync({ 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.isApproved).to.eq(shouldApprove); - }); - }); - describe('batchExecuteTransactions', () => { - it('should successfully call fillOrder via 2 transactions with different taker signatures', async () => { - const order1 = await orderFactory.newSignedOrderAsync(); - const order2 = await orderFactory.newSignedOrderAsync(); - const data1 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order1]); - const data2 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order2]); - const transaction1 = await takerTransactionFactory.newSignedTransactionAsync({ data: data1 }); - const transaction2 = await taker2TransactionFactory.newSignedTransactionAsync({ data: data2 }); - const transactionReceipt = await exchangeWrapper.batchExecuteTransactionsAsync( - [transaction1, transaction2], - senderAddress, - ); - - const transactionExecutionLogs = transactionReceipt.logs.filter( - log => - (log as LogWithDecodedArgs).event === - 'TransactionExecution', - ); - expect(transactionExecutionLogs.length).to.eq(2); - - const execution1LogArgs = (transactionExecutionLogs[0] as LogWithDecodedArgs< - ExchangeTransactionExecutionEventArgs - >).args; - expect(execution1LogArgs.transactionHash).to.equal( - transactionHashUtils.getTransactionHashHex(transaction1), - ); - - const execution2LogArgs = (transactionExecutionLogs[1] as LogWithDecodedArgs< - ExchangeTransactionExecutionEventArgs - >).args; - expect(execution2LogArgs.transactionHash).to.equal( - transactionHashUtils.getTransactionHashHex(transaction2), - ); - - const fillLogs = transactionReceipt.logs.filter( - log => (log as LogWithDecodedArgs).event === 'Fill', - ); - expect(fillLogs.length).to.eq(2); - - const fill1LogArgs = (fillLogs[0] as LogWithDecodedArgs).args; - expect(fill1LogArgs.makerAddress).to.eq(makerAddress); - expect(fill1LogArgs.takerAddress).to.eq(takerAddress); - expect(fill1LogArgs.senderAddress).to.eq(senderAddress); - expect(fill1LogArgs.feeRecipientAddress).to.eq(feeRecipientAddress); - expect(fill1LogArgs.makerAssetData).to.eq(order1.makerAssetData); - expect(fill1LogArgs.takerAssetData).to.eq(order1.takerAssetData); - expect(fill1LogArgs.makerAssetFilledAmount).to.bignumber.eq(order1.makerAssetAmount); - expect(fill1LogArgs.takerAssetFilledAmount).to.bignumber.eq(order1.takerAssetAmount); - expect(fill1LogArgs.makerFeePaid).to.bignumber.eq(order1.makerFee); - expect(fill1LogArgs.takerFeePaid).to.bignumber.eq(order1.takerFee); - expect(fill1LogArgs.orderHash).to.eq(orderHashUtils.getOrderHashHex(order1)); - - const fill2LogArgs = (fillLogs[1] as LogWithDecodedArgs).args; - expect(fill2LogArgs.makerAddress).to.eq(makerAddress); - expect(fill2LogArgs.takerAddress).to.eq(taker2Address); - expect(fill2LogArgs.senderAddress).to.eq(senderAddress); - expect(fill2LogArgs.feeRecipientAddress).to.eq(feeRecipientAddress); - expect(fill2LogArgs.makerAssetData).to.eq(order2.makerAssetData); - expect(fill2LogArgs.takerAssetData).to.eq(order2.takerAssetData); - expect(fill2LogArgs.makerAssetFilledAmount).to.bignumber.eq(order2.makerAssetAmount); - expect(fill2LogArgs.takerAssetFilledAmount).to.bignumber.eq(order2.takerAssetAmount); - expect(fill2LogArgs.makerFeePaid).to.bignumber.eq(order2.makerFee); - expect(fill2LogArgs.takerFeePaid).to.bignumber.eq(order2.takerFee); - expect(fill2LogArgs.orderHash).to.eq(orderHashUtils.getOrderHashHex(order2)); - }); - it('should successfully call fillOrder via 2 transactions when called by taker with no signatures', async () => { - const order1 = await orderFactory.newSignedOrderAsync(); - const order2 = await orderFactory.newSignedOrderAsync(); - const data1 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order1]); - const data2 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order2]); - const transaction1 = await takerTransactionFactory.newSignedTransactionAsync({ data: data1 }); - const transaction2 = await takerTransactionFactory.newSignedTransactionAsync({ data: data2 }); - transaction1.signature = constants.NULL_BYTES; - transaction2.signature = constants.NULL_BYTES; - const transactionReceipt = await exchangeWrapper.batchExecuteTransactionsAsync( - [transaction1, transaction2], - takerAddress, - ); - - const transactionExecutionLogs = transactionReceipt.logs.filter( - log => - (log as LogWithDecodedArgs).event === - 'TransactionExecution', - ); - expect(transactionExecutionLogs.length).to.eq(2); - - const execution1LogArgs = (transactionExecutionLogs[0] as LogWithDecodedArgs< - ExchangeTransactionExecutionEventArgs - >).args; - expect(execution1LogArgs.transactionHash).to.equal( - transactionHashUtils.getTransactionHashHex(transaction1), - ); - - const execution2LogArgs = (transactionExecutionLogs[1] as LogWithDecodedArgs< - ExchangeTransactionExecutionEventArgs - >).args; - expect(execution2LogArgs.transactionHash).to.equal( - transactionHashUtils.getTransactionHashHex(transaction2), - ); - - const fillLogs = transactionReceipt.logs.filter( - log => (log as LogWithDecodedArgs).event === 'Fill', - ); - expect(fillLogs.length).to.eq(2); - - const fill1LogArgs = (fillLogs[0] as LogWithDecodedArgs).args; - expect(fill1LogArgs.makerAddress).to.eq(makerAddress); - expect(fill1LogArgs.takerAddress).to.eq(takerAddress); - expect(fill1LogArgs.senderAddress).to.eq(takerAddress); - expect(fill1LogArgs.feeRecipientAddress).to.eq(feeRecipientAddress); - expect(fill1LogArgs.makerAssetData).to.eq(order1.makerAssetData); - expect(fill1LogArgs.takerAssetData).to.eq(order1.takerAssetData); - expect(fill1LogArgs.makerAssetFilledAmount).to.bignumber.eq(order1.makerAssetAmount); - expect(fill1LogArgs.takerAssetFilledAmount).to.bignumber.eq(order1.takerAssetAmount); - expect(fill1LogArgs.makerFeePaid).to.bignumber.eq(order1.makerFee); - expect(fill1LogArgs.takerFeePaid).to.bignumber.eq(order1.takerFee); - expect(fill1LogArgs.orderHash).to.eq(orderHashUtils.getOrderHashHex(order1)); - - const fill2LogArgs = (fillLogs[1] as LogWithDecodedArgs).args; - expect(fill2LogArgs.makerAddress).to.eq(makerAddress); - expect(fill2LogArgs.takerAddress).to.eq(takerAddress); - expect(fill2LogArgs.senderAddress).to.eq(takerAddress); - expect(fill2LogArgs.feeRecipientAddress).to.eq(feeRecipientAddress); - expect(fill2LogArgs.makerAssetData).to.eq(order2.makerAssetData); - expect(fill2LogArgs.takerAssetData).to.eq(order2.takerAssetData); - expect(fill2LogArgs.makerAssetFilledAmount).to.bignumber.eq(order2.makerAssetAmount); - expect(fill2LogArgs.takerAssetFilledAmount).to.bignumber.eq(order2.takerAssetAmount); - expect(fill2LogArgs.makerFeePaid).to.bignumber.eq(order2.makerFee); - expect(fill2LogArgs.takerFeePaid).to.bignumber.eq(order2.takerFee); - expect(fill2LogArgs.orderHash).to.eq(orderHashUtils.getOrderHashHex(order2)); - }); - it('should successfully call fillOrder via 2 transactions when one is signed by taker1 and executeTransaction is called by taker2', async () => { - const order1 = await orderFactory.newSignedOrderAsync(); - const order2 = await orderFactory.newSignedOrderAsync(); - const data1 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order1]); - const data2 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order2]); - const transaction1 = await takerTransactionFactory.newSignedTransactionAsync({ data: data1 }); - const transaction2 = await taker2TransactionFactory.newSignedTransactionAsync({ data: data2 }); - transaction2.signature = constants.NULL_BYTES; - const transactionReceipt = await exchangeWrapper.batchExecuteTransactionsAsync( - [transaction1, transaction2], - taker2Address, - ); - - const transactionExecutionLogs = transactionReceipt.logs.filter( - log => - (log as LogWithDecodedArgs).event === - 'TransactionExecution', - ); - expect(transactionExecutionLogs.length).to.eq(2); - - const execution1LogArgs = (transactionExecutionLogs[0] as LogWithDecodedArgs< - ExchangeTransactionExecutionEventArgs - >).args; - expect(execution1LogArgs.transactionHash).to.equal( - transactionHashUtils.getTransactionHashHex(transaction1), - ); - - const execution2LogArgs = (transactionExecutionLogs[1] as LogWithDecodedArgs< - ExchangeTransactionExecutionEventArgs - >).args; - expect(execution2LogArgs.transactionHash).to.equal( - transactionHashUtils.getTransactionHashHex(transaction2), - ); - - const fillLogs = transactionReceipt.logs.filter( - log => (log as LogWithDecodedArgs).event === 'Fill', - ); - expect(fillLogs.length).to.eq(2); - - const fill1LogArgs = (fillLogs[0] as LogWithDecodedArgs).args; - expect(fill1LogArgs.makerAddress).to.eq(makerAddress); - expect(fill1LogArgs.takerAddress).to.eq(takerAddress); - expect(fill1LogArgs.senderAddress).to.eq(taker2Address); - expect(fill1LogArgs.feeRecipientAddress).to.eq(feeRecipientAddress); - expect(fill1LogArgs.makerAssetData).to.eq(order1.makerAssetData); - expect(fill1LogArgs.takerAssetData).to.eq(order1.takerAssetData); - expect(fill1LogArgs.makerAssetFilledAmount).to.bignumber.eq(order1.makerAssetAmount); - expect(fill1LogArgs.takerAssetFilledAmount).to.bignumber.eq(order1.takerAssetAmount); - expect(fill1LogArgs.makerFeePaid).to.bignumber.eq(order1.makerFee); - expect(fill1LogArgs.takerFeePaid).to.bignumber.eq(order1.takerFee); - expect(fill1LogArgs.orderHash).to.eq(orderHashUtils.getOrderHashHex(order1)); - - const fill2LogArgs = (fillLogs[1] as LogWithDecodedArgs).args; - expect(fill2LogArgs.makerAddress).to.eq(makerAddress); - expect(fill2LogArgs.takerAddress).to.eq(taker2Address); - expect(fill2LogArgs.senderAddress).to.eq(taker2Address); - expect(fill2LogArgs.feeRecipientAddress).to.eq(feeRecipientAddress); - expect(fill2LogArgs.makerAssetData).to.eq(order2.makerAssetData); - expect(fill2LogArgs.takerAssetData).to.eq(order2.takerAssetData); - expect(fill2LogArgs.makerAssetFilledAmount).to.bignumber.eq(order2.makerAssetAmount); - expect(fill2LogArgs.takerAssetFilledAmount).to.bignumber.eq(order2.takerAssetAmount); - expect(fill2LogArgs.makerFeePaid).to.bignumber.eq(order2.makerFee); - expect(fill2LogArgs.takerFeePaid).to.bignumber.eq(order2.takerFee); - expect(fill2LogArgs.orderHash).to.eq(orderHashUtils.getOrderHashHex(order2)); - }); - it('should return the correct data for 2 different fillOrder calls', async () => { - const order1 = await orderFactory.newSignedOrderAsync(); - const order2 = await orderFactory.newSignedOrderAsync(); - const data1 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order1]); - const data2 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order2]); - const transaction1 = await takerTransactionFactory.newSignedTransactionAsync({ data: data1 }); - const transaction2 = await taker2TransactionFactory.newSignedTransactionAsync({ data: data2 }); - const returnData = await exchangeInstance - .batchExecuteTransactions( - [transaction1, transaction2], - [transaction1.signature, transaction2.signature], - ) - .callAsync({ from: senderAddress }); - const abi = artifacts.Exchange.compilerOutput.abi; - const methodAbi = abi.filter( - abiItem => (abiItem as MethodAbi).name === ExchangeFunctionName.FillOrder, - )[0] as MethodAbi; - const abiEncoder = new AbiEncoder.Method(methodAbi); - const fillResults1: FillResults = abiEncoder.decodeReturnValues(returnData[0]).fillResults; - const fillResults2: FillResults = abiEncoder.decodeReturnValues(returnData[1]).fillResults; - expect(fillResults1.makerAssetFilledAmount).to.be.bignumber.eq(order1.makerAssetAmount); - expect(fillResults1.takerAssetFilledAmount).to.be.bignumber.eq(order1.takerAssetAmount); - expect(fillResults1.makerFeePaid).to.be.bignumber.eq(order1.makerFee); - expect(fillResults1.takerFeePaid).to.be.bignumber.eq(order1.takerFee); - expect(fillResults2.makerAssetFilledAmount).to.be.bignumber.eq(order2.makerAssetAmount); - expect(fillResults2.takerAssetFilledAmount).to.be.bignumber.eq(order2.takerAssetAmount); - expect(fillResults2.makerFeePaid).to.be.bignumber.eq(order2.makerFee); - expect(fillResults2.takerFeePaid).to.be.bignumber.eq(order2.takerFee); - }); - it('should successfully call fillOrder and cancelOrder via 2 transactions', async () => { - const order1 = await orderFactory.newSignedOrderAsync(); - const order2 = await orderFactory.newSignedOrderAsync(); - const data1 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order1]); - const data2 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.CancelOrder, [ - order2, - ]); - const transaction1 = await takerTransactionFactory.newSignedTransactionAsync({ data: data1 }); - const transaction2 = await makerTransactionFactory.newSignedTransactionAsync({ data: data2 }); - const transactionReceipt = await exchangeWrapper.batchExecuteTransactionsAsync( - [transaction1, transaction2], - senderAddress, - ); - - const transactionExecutionLogs = transactionReceipt.logs.filter( - log => - (log as LogWithDecodedArgs).event === - 'TransactionExecution', - ); - expect(transactionExecutionLogs.length).to.eq(2); - - const execution1LogArgs = (transactionExecutionLogs[0] as LogWithDecodedArgs< - ExchangeTransactionExecutionEventArgs - >).args; - expect(execution1LogArgs.transactionHash).to.equal( - transactionHashUtils.getTransactionHashHex(transaction1), - ); - - const execution2LogArgs = (transactionExecutionLogs[1] as LogWithDecodedArgs< - ExchangeTransactionExecutionEventArgs - >).args; - expect(execution2LogArgs.transactionHash).to.equal( - transactionHashUtils.getTransactionHashHex(transaction2), - ); - - let fillLogIndex: number = 0; - let cancelLogIndex: number = 0; - const fillLogs = transactionReceipt.logs.filter((log, index) => { - if ((log as LogWithDecodedArgs).event === 'Fill') { - fillLogIndex = index; - return true; - } - return false; - }); - const cancelLogs = transactionReceipt.logs.filter((log, index) => { - if ((log as LogWithDecodedArgs).event === 'Cancel') { - cancelLogIndex = index; - return true; - } - return false; - }); - expect(fillLogs.length).to.eq(1); - expect(cancelLogs.length).to.eq(1); - expect(cancelLogIndex).to.greaterThan(fillLogIndex); - - 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(order1.makerAssetData); - expect(fillLogArgs.takerAssetData).to.eq(order1.takerAssetData); - expect(fillLogArgs.makerAssetFilledAmount).to.bignumber.eq(order1.makerAssetAmount); - expect(fillLogArgs.takerAssetFilledAmount).to.bignumber.eq(order1.takerAssetAmount); - expect(fillLogArgs.makerFeePaid).to.bignumber.eq(order1.makerFee); - expect(fillLogArgs.takerFeePaid).to.bignumber.eq(order1.takerFee); - expect(fillLogArgs.orderHash).to.eq(orderHashUtils.getOrderHashHex(order1)); - - 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(order2.makerAssetData); - expect(cancelLogArgs.takerAssetData).to.eq(order2.takerAssetData); - expect(cancelLogArgs.orderHash).to.eq(orderHashUtils.getOrderHashHex(order2)); - }); - it('should return the correct data for a fillOrder and cancelOrder call', async () => { - const order1 = await orderFactory.newSignedOrderAsync(); - const order2 = await orderFactory.newSignedOrderAsync(); - const data1 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order1]); - const data2 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.CancelOrder, [ - order2, - ]); - const transaction1 = await takerTransactionFactory.newSignedTransactionAsync({ data: data1 }); - const transaction2 = await makerTransactionFactory.newSignedTransactionAsync({ data: data2 }); - const returnData = await exchangeInstance - .batchExecuteTransactions( - [transaction1, transaction2], - [transaction1.signature, transaction2.signature], - ) - .callAsync({ from: senderAddress }); - const abi = artifacts.Exchange.compilerOutput.abi; - const methodAbi = abi.filter( - abiItem => (abiItem as MethodAbi).name === ExchangeFunctionName.FillOrder, - )[0] as MethodAbi; - const abiEncoder = new AbiEncoder.Method(methodAbi); - const fillResults: FillResults = abiEncoder.decodeReturnValues(returnData[0]).fillResults; - expect(fillResults.makerAssetFilledAmount).to.be.bignumber.eq(order1.makerAssetAmount); - expect(fillResults.takerAssetFilledAmount).to.be.bignumber.eq(order1.takerAssetAmount); - expect(fillResults.makerFeePaid).to.be.bignumber.eq(order1.makerFee); - expect(fillResults.takerFeePaid).to.be.bignumber.eq(order1.takerFee); - expect(returnData[1]).to.eq(constants.NULL_BYTES); - }); - it('should revert if a single transaction reverts', async () => { - const order = await orderFactory.newSignedOrderAsync(); - const data1 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.CancelOrder, [order]); - const data2 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order]); - const transaction1 = await makerTransactionFactory.newSignedTransactionAsync({ data: data1 }); - const transaction2 = await takerTransactionFactory.newSignedTransactionAsync({ data: data2 }); - const tx = exchangeWrapper.batchExecuteTransactionsAsync([transaction1, transaction2], senderAddress); - const nestedError = new ExchangeRevertErrors.OrderStatusError( - orderHashUtils.getOrderHashHex(order), - OrderStatus.Cancelled, - ).encode(); - const expectedError = new ExchangeRevertErrors.TransactionExecutionError( - transactionHashUtils.getTransactionHashHex(transaction2), - nestedError, - ); - return expect(tx).to.revertWith(expectedError); - }); - it('should revert if a single transaction is expired', async () => { - const order1 = await orderFactory.newSignedOrderAsync(); - const order2 = await orderFactory.newSignedOrderAsync(); - const data1 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order1]); - const data2 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order2]); - const currentTimestamp = await getLatestBlockTimestampAsync(); - const transaction1 = await takerTransactionFactory.newSignedTransactionAsync({ data: data1 }); - const transaction2 = await taker2TransactionFactory.newSignedTransactionAsync({ - data: data2, - expirationTimeSeconds: new BigNumber(currentTimestamp).minus(10), - }); - const tx = exchangeWrapper.batchExecuteTransactionsAsync([transaction1, transaction2], senderAddress); - const expiredTransactionHash = transactionHashUtils.getTransactionHashHex(transaction2); - const expectedError = new ExchangeRevertErrors.TransactionError( - ExchangeRevertErrors.TransactionErrorCode.Expired, - expiredTransactionHash, - ); - return expect(tx).to.revertWith(expectedError); - }); - }); - }); -}); diff --git a/contracts/integrations/test/coordinator/coordinator_test.ts b/contracts/integrations/test/coordinator/coordinator_test.ts index 3ce9b50669..ec5848b474 100644 --- a/contracts/integrations/test/coordinator/coordinator_test.ts +++ b/contracts/integrations/test/coordinator/coordinator_test.ts @@ -19,7 +19,6 @@ import { } from '@0x/contracts-test-utils'; import { SignedOrder, SignedZeroExTransaction } from '@0x/types'; import { BigNumber } from '@0x/utils'; -import { TransactionReceiptWithDecodedLogs } from 'ethereum-types'; import { Actor } from '../framework/actors/base'; import { FeeRecipient } from '../framework/actors/fee_recipient'; @@ -96,72 +95,9 @@ blockchainTests.resets('Coordinator integration tests', env => { }); after(async () => { - Actor.count = 0; + Actor.reset(); }); - async function simulateFillsAsync( - orders: SignedOrder[], - txReceipt: TransactionReceiptWithDecodedLogs, - msgValue?: BigNumber, - ): Promise { - let remainingValue = msgValue || constants.ZERO_AMOUNT; - const localBalanceStore = LocalBalanceStore.create(balanceStore); - // Transaction gas cost - localBalanceStore.burnGas(txReceipt.from, DeploymentManager.gasPrice.times(txReceipt.gasUsed)); - - for (const order of orders) { - // Taker -> Maker - await localBalanceStore.transferAssetAsync( - taker.address, - maker.address, - order.takerAssetAmount, - order.takerAssetData, - ); - // Maker -> Taker - await localBalanceStore.transferAssetAsync( - maker.address, - taker.address, - order.makerAssetAmount, - order.makerAssetData, - ); - // Taker -> Fee Recipient - await localBalanceStore.transferAssetAsync( - taker.address, - feeRecipient.address, - order.takerFee, - order.takerFeeAssetData, - ); - // Maker -> Fee Recipient - await localBalanceStore.transferAssetAsync( - maker.address, - feeRecipient.address, - order.makerFee, - order.makerFeeAssetData, - ); - - // Protocol fee - if (remainingValue.isGreaterThanOrEqualTo(DeploymentManager.protocolFee)) { - localBalanceStore.sendEth( - txReceipt.from, - deployment.staking.stakingProxy.address, - DeploymentManager.protocolFee, - ); - remainingValue = remainingValue.minus(DeploymentManager.protocolFee); - } else { - await localBalanceStore.transferAssetAsync( - taker.address, - deployment.staking.stakingProxy.address, - DeploymentManager.protocolFee, - deployment.assetDataEncoder - .ERC20Token(deployment.tokens.weth.address) - .getABIEncodedTransactionData(), - ); - } - } - - return localBalanceStore; - } - function expectedFillEvent(order: SignedOrder): ExchangeFillEventArgs { return { makerAddress: order.makerAddress, @@ -191,10 +127,7 @@ blockchainTests.resets('Coordinator integration tests', env => { before(async () => { order = await maker.signOrderAsync(); data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, [order]); - transaction = await taker.signTransactionAsync({ - data, - gasPrice: DeploymentManager.gasPrice, - }); + transaction = await taker.signTransactionAsync({ data }); approval = await feeRecipient.signCoordinatorApprovalAsync(transaction, taker.address); }); @@ -204,7 +137,14 @@ blockchainTests.resets('Coordinator integration tests', env => { .executeTransaction(transaction, taker.address, transaction.signature, [approval.signature]) .awaitTransactionSuccessAsync({ from: taker.address, value: DeploymentManager.protocolFee }); - const expectedBalances = await simulateFillsAsync([order], txReceipt, DeploymentManager.protocolFee); + const expectedBalances = LocalBalanceStore.create(balanceStore); + expectedBalances.simulateFills( + [order], + taker.address, + txReceipt, + deployment, + DeploymentManager.protocolFee, + ); await balanceStore.updateBalancesAsync(); balanceStore.assertEquals(expectedBalances); verifyEvents(txReceipt, [expectedFillEvent(order)], ExchangeEvents.Fill); @@ -215,7 +155,14 @@ blockchainTests.resets('Coordinator integration tests', env => { .executeTransaction(transaction, feeRecipient.address, transaction.signature, []) .awaitTransactionSuccessAsync({ from: feeRecipient.address, value: DeploymentManager.protocolFee }); - const expectedBalances = await simulateFillsAsync([order], txReceipt, DeploymentManager.protocolFee); + const expectedBalances = LocalBalanceStore.create(balanceStore); + expectedBalances.simulateFills( + [order], + taker.address, + txReceipt, + deployment, + DeploymentManager.protocolFee, + ); await balanceStore.updateBalancesAsync(); balanceStore.assertEquals(expectedBalances); verifyEvents(txReceipt, [expectedFillEvent(order)], ExchangeEvents.Fill); @@ -229,9 +176,12 @@ blockchainTests.resets('Coordinator integration tests', env => { value: DeploymentManager.protocolFee.plus(1), }); - const expectedBalances = await simulateFillsAsync( + const expectedBalances = LocalBalanceStore.create(balanceStore); + expectedBalances.simulateFills( [order], + taker.address, txReceipt, + deployment, DeploymentManager.protocolFee.plus(1), ); await balanceStore.updateBalancesAsync(); @@ -244,7 +194,8 @@ blockchainTests.resets('Coordinator integration tests', env => { .executeTransaction(transaction, feeRecipient.address, transaction.signature, []) .awaitTransactionSuccessAsync({ from: feeRecipient.address }); - const expectedBalances = await simulateFillsAsync([order], txReceipt); + const expectedBalances = LocalBalanceStore.create(balanceStore); + expectedBalances.simulateFills([order], taker.address, txReceipt, deployment); await balanceStore.updateBalancesAsync(); balanceStore.assertEquals(expectedBalances); verifyEvents(txReceipt, [expectedFillEvent(order)], ExchangeEvents.Fill); @@ -255,7 +206,8 @@ blockchainTests.resets('Coordinator integration tests', env => { .executeTransaction(transaction, feeRecipient.address, transaction.signature, []) .awaitTransactionSuccessAsync({ from: feeRecipient.address, value: new BigNumber(1) }); - const expectedBalances = await simulateFillsAsync([order], txReceipt, new BigNumber(1)); + const expectedBalances = LocalBalanceStore.create(balanceStore); + expectedBalances.simulateFills([order], taker.address, txReceipt, deployment, new BigNumber(1)); await balanceStore.updateBalancesAsync(); balanceStore.assertEquals(expectedBalances); verifyEvents(txReceipt, [expectedFillEvent(order)], ExchangeEvents.Fill); @@ -309,10 +261,7 @@ blockchainTests.resets('Coordinator integration tests', env => { before(async () => { orders = [await maker.signOrderAsync(), await maker.signOrderAsync()]; data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders); - transaction = await taker.signTransactionAsync({ - data, - gasPrice: DeploymentManager.gasPrice, - }); + transaction = await taker.signTransactionAsync({ data }); approval = await feeRecipient.signCoordinatorApprovalAsync(transaction, taker.address); }); @@ -323,7 +272,8 @@ blockchainTests.resets('Coordinator integration tests', env => { .executeTransaction(transaction, taker.address, transaction.signature, [approval.signature]) .awaitTransactionSuccessAsync({ from: taker.address, value }); - const expectedBalances = await simulateFillsAsync(orders, txReceipt, value); + const expectedBalances = LocalBalanceStore.create(balanceStore); + expectedBalances.simulateFills(orders, taker.address, txReceipt, deployment, value); await balanceStore.updateBalancesAsync(); balanceStore.assertEquals(expectedBalances); verifyEvents(txReceipt, orders.map(order => expectedFillEvent(order)), ExchangeEvents.Fill); @@ -335,7 +285,8 @@ blockchainTests.resets('Coordinator integration tests', env => { .executeTransaction(transaction, feeRecipient.address, transaction.signature, []) .awaitTransactionSuccessAsync({ from: feeRecipient.address, value }); - const expectedBalances = await simulateFillsAsync(orders, txReceipt, value); + const expectedBalances = LocalBalanceStore.create(balanceStore); + expectedBalances.simulateFills(orders, taker.address, txReceipt, deployment, value); await balanceStore.updateBalancesAsync(); balanceStore.assertEquals(expectedBalances); verifyEvents(txReceipt, orders.map(order => expectedFillEvent(order)), ExchangeEvents.Fill); @@ -347,7 +298,8 @@ blockchainTests.resets('Coordinator integration tests', env => { .executeTransaction(transaction, feeRecipient.address, transaction.signature, []) .awaitTransactionSuccessAsync({ from: feeRecipient.address, value }); - const expectedBalances = await simulateFillsAsync(orders, txReceipt, value); + const expectedBalances = LocalBalanceStore.create(balanceStore); + expectedBalances.simulateFills(orders, taker.address, txReceipt, deployment, value); await balanceStore.updateBalancesAsync(); balanceStore.assertEquals(expectedBalances); verifyEvents(txReceipt, orders.map(order => expectedFillEvent(order)), ExchangeEvents.Fill); @@ -400,7 +352,6 @@ blockchainTests.resets('Coordinator integration tests', env => { const data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.CancelOrder, [order]); const transaction = await maker.signTransactionAsync({ data, - gasPrice: DeploymentManager.gasPrice, }); const txReceipt = await coordinator .executeTransaction(transaction, maker.address, transaction.signature, []) @@ -413,7 +364,6 @@ blockchainTests.resets('Coordinator integration tests', env => { const data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.BatchCancelOrders, orders); const transaction = await maker.signTransactionAsync({ data, - gasPrice: DeploymentManager.gasPrice, }); const txReceipt = await coordinator .executeTransaction(transaction, maker.address, transaction.signature, []) @@ -425,7 +375,6 @@ blockchainTests.resets('Coordinator integration tests', env => { const data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.CancelOrdersUpTo, []); const transaction = await maker.signTransactionAsync({ data, - gasPrice: DeploymentManager.gasPrice, }); const txReceipt = await coordinator .executeTransaction(transaction, maker.address, transaction.signature, []) diff --git a/contracts/integrations/test/exchange/batch_match_orders_test.ts b/contracts/integrations/test/exchange/batch_match_orders_test.ts index 8907d33d58..29f368c9db 100644 --- a/contracts/integrations/test/exchange/batch_match_orders_test.ts +++ b/contracts/integrations/test/exchange/batch_match_orders_test.ts @@ -146,7 +146,7 @@ blockchainTests.resets('matchOrders integration tests', env => { }); after(async () => { - Actor.count = 0; + Actor.reset(); }); describe('batchMatchOrders and batchMatchOrdersWithMaximalFill rich errors', async () => { diff --git a/contracts/integrations/test/exchange/exchange_wrapper_test.ts b/contracts/integrations/test/exchange/exchange_wrapper_test.ts index ef057c6539..ed8afb5403 100644 --- a/contracts/integrations/test/exchange/exchange_wrapper_test.ts +++ b/contracts/integrations/test/exchange/exchange_wrapper_test.ts @@ -124,7 +124,7 @@ blockchainTests.resets('Exchange wrappers', env => { }); after(async () => { - Actor.count = 0; + Actor.reset(); }); interface SignedOrderWithValidity { @@ -132,13 +132,9 @@ blockchainTests.resets('Exchange wrappers', env => { isValid: boolean; } - async function simulateFillAsync( - signedOrder: SignedOrder, - expectedFillResults: FillResults, - shouldUseWeth: boolean, - ): Promise { + function simulateFill(signedOrder: SignedOrder, expectedFillResults: FillResults, shouldUseWeth: boolean): void { // taker -> maker - await localBalances.transferAssetAsync( + localBalances.transferAsset( taker.address, maker.address, expectedFillResults.takerAssetFilledAmount, @@ -146,7 +142,7 @@ blockchainTests.resets('Exchange wrappers', env => { ); // maker -> taker - await localBalances.transferAssetAsync( + localBalances.transferAsset( maker.address, taker.address, expectedFillResults.makerAssetFilledAmount, @@ -154,7 +150,7 @@ blockchainTests.resets('Exchange wrappers', env => { ); // maker -> feeRecipient - await localBalances.transferAssetAsync( + localBalances.transferAsset( maker.address, feeRecipient, expectedFillResults.makerFeePaid, @@ -162,7 +158,7 @@ blockchainTests.resets('Exchange wrappers', env => { ); // taker -> feeRecipient - await localBalances.transferAssetAsync( + localBalances.transferAsset( taker.address, feeRecipient, expectedFillResults.takerFeePaid, @@ -171,7 +167,7 @@ blockchainTests.resets('Exchange wrappers', env => { // taker -> protocol fees if (shouldUseWeth) { - await localBalances.transferAssetAsync( + localBalances.transferAsset( taker.address, deployment.staking.stakingProxy.address, expectedFillResults.protocolFeePaid, @@ -343,7 +339,7 @@ blockchainTests.resets('Exchange wrappers', env => { const shouldPayWethFees = DeploymentManager.protocolFee.gt(value); // Simulate filling the order - await simulateFillAsync(signedOrder, expectedFillResults, shouldPayWethFees); + simulateFill(signedOrder, expectedFillResults, shouldPayWethFees); // Ensure that the correct logs were emitted and that the balances are accurate. await assertResultsAsync(receipt, [{ signedOrder, expectedFillResults, shouldPayWethFees }]); @@ -444,7 +440,7 @@ blockchainTests.resets('Exchange wrappers', env => { } fillTestInfo.push({ signedOrder, expectedFillResults, shouldPayWethFees }); - await simulateFillAsync(signedOrder, expectedFillResults, shouldPayWethFees); + simulateFill(signedOrder, expectedFillResults, shouldPayWethFees); } const contractFn = deployment.exchange.batchFillOrders( @@ -506,7 +502,7 @@ blockchainTests.resets('Exchange wrappers', env => { } fillTestInfo.push({ signedOrder, expectedFillResults, shouldPayWethFees }); - await simulateFillAsync(signedOrder, expectedFillResults, shouldPayWethFees); + simulateFill(signedOrder, expectedFillResults, shouldPayWethFees); } const contractFn = deployment.exchange.batchFillOrKillOrders( @@ -600,7 +596,7 @@ blockchainTests.resets('Exchange wrappers', env => { } fillTestInfo.push({ signedOrder, expectedFillResults, shouldPayWethFees }); - await simulateFillAsync(signedOrder, expectedFillResults, shouldPayWethFees); + simulateFill(signedOrder, expectedFillResults, shouldPayWethFees); } else { totalFillResults.push(nullFillResults); } @@ -714,7 +710,7 @@ blockchainTests.resets('Exchange wrappers', env => { takerFillAmount, ); - await simulateFillAsync(signedOrder, expectedFillResults, shouldPayWethFees); + simulateFill(signedOrder, expectedFillResults, shouldPayWethFees); fillTestInfo.push({ signedOrder, expectedFillResults, shouldPayWethFees }); totalFillResults = addFillResults(totalFillResults, expectedFillResults); @@ -912,7 +908,7 @@ blockchainTests.resets('Exchange wrappers', env => { makerAssetBought, ); - await simulateFillAsync(signedOrder, expectedFillResults, shouldPayWethFees); + simulateFill(signedOrder, expectedFillResults, shouldPayWethFees); fillTestInfo.push({ signedOrder, expectedFillResults, shouldPayWethFees }); totalFillResults = addFillResults(totalFillResults, expectedFillResults); diff --git a/contracts/integrations/test/exchange/fill_order_wrapper.ts b/contracts/integrations/test/exchange/fill_order_wrapper.ts index d8d996aac2..9f2b85cf98 100644 --- a/contracts/integrations/test/exchange/fill_order_wrapper.ts +++ b/contracts/integrations/test/exchange/fill_order_wrapper.ts @@ -99,7 +99,7 @@ export class FillOrderWrapper { await this._assertOrderStateAsync(signedOrder, initTakerAssetFilledAmount); // Simulate and execute fill then assert outputs const [fillResults, fillEvent, txReceipt] = await this._fillOrderAsync(signedOrder, from, opts); - const [simulatedFillResults, simulatedFillEvent, simulatedFinalBalanceStore] = await simulateFillOrderAsync( + const [simulatedFillResults, simulatedFillEvent, simulatedFinalBalanceStore] = simulateFillOrder( txReceipt, signedOrder, from, @@ -169,13 +169,13 @@ export class FillOrderWrapper { * @param initBalanceStore Account balances prior to the fill. * @return The expected account balances, fill results, and fill events. */ -async function simulateFillOrderAsync( +function simulateFillOrder( txReceipt: TransactionReceiptWithDecodedLogs, signedOrder: SignedOrder, takerAddress: string, initBalanceStore: BalanceStore, opts: { takerAssetFillAmount?: BigNumber } = {}, -): Promise<[FillResults, FillEventArgs, BalanceStore]> { +): [FillResults, FillEventArgs, BalanceStore] { const balanceStore = LocalBalanceStore.create(initBalanceStore); const takerAssetFillAmount = opts.takerAssetFillAmount !== undefined ? opts.takerAssetFillAmount : signedOrder.takerAssetAmount; @@ -188,28 +188,28 @@ async function simulateFillOrderAsync( ); const fillEvent = FillOrderWrapper.simulateFillEvent(signedOrder, takerAddress, fillResults); // Taker -> Maker - await balanceStore.transferAssetAsync( + balanceStore.transferAsset( takerAddress, signedOrder.makerAddress, fillResults.takerAssetFilledAmount, signedOrder.takerAssetData, ); // Maker -> Taker - await balanceStore.transferAssetAsync( + balanceStore.transferAsset( signedOrder.makerAddress, takerAddress, fillResults.makerAssetFilledAmount, signedOrder.makerAssetData, ); // Taker -> Fee Recipient - await balanceStore.transferAssetAsync( + balanceStore.transferAsset( takerAddress, signedOrder.feeRecipientAddress, fillResults.takerFeePaid, signedOrder.takerFeeAssetData, ); // Maker -> Fee Recipient - await balanceStore.transferAssetAsync( + balanceStore.transferAsset( signedOrder.makerAddress, signedOrder.feeRecipientAddress, fillResults.makerFeePaid, diff --git a/contracts/integrations/test/exchange/fillorder_test.ts b/contracts/integrations/test/exchange/fillorder_test.ts index 7e8bf72457..c32ce49270 100644 --- a/contracts/integrations/test/exchange/fillorder_test.ts +++ b/contracts/integrations/test/exchange/fillorder_test.ts @@ -109,54 +109,9 @@ blockchainTests.resets('fillOrder integration tests', env => { }); after(async () => { - Actor.count = 0; + Actor.reset(); }); - async function simulateFillAsync( - order: SignedOrder, - txReceipt: TransactionReceiptWithDecodedLogs, - msgValue?: BigNumber, - ): Promise { - let remainingValue = msgValue !== undefined ? msgValue : DeploymentManager.protocolFee; - const localBalanceStore = LocalBalanceStore.create(balanceStore); - // Transaction gas cost - localBalanceStore.burnGas(txReceipt.from, DeploymentManager.gasPrice.times(txReceipt.gasUsed)); - - // Taker -> Maker - await localBalanceStore.transferAssetAsync( - taker.address, - maker.address, - order.takerAssetAmount, - order.takerAssetData, - ); - // Maker -> Taker - await localBalanceStore.transferAssetAsync( - maker.address, - taker.address, - order.makerAssetAmount, - order.makerAssetData, - ); - - // Protocol fee - if (remainingValue.isGreaterThanOrEqualTo(DeploymentManager.protocolFee)) { - localBalanceStore.sendEth( - txReceipt.from, - deployment.staking.stakingProxy.address, - DeploymentManager.protocolFee, - ); - remainingValue = remainingValue.minus(DeploymentManager.protocolFee); - } else { - await localBalanceStore.transferAssetAsync( - taker.address, - deployment.staking.stakingProxy.address, - DeploymentManager.protocolFee, - deployment.assetDataEncoder.ERC20Token(deployment.tokens.weth.address).getABIEncodedTransactionData(), - ); - } - - return localBalanceStore; - } - function verifyFillEvents(order: SignedOrder, receipt: TransactionReceiptWithDecodedLogs): void { // Ensure that the fill event was correct. verifyEvents( @@ -207,7 +162,8 @@ blockchainTests.resets('fillOrder integration tests', env => { const receipt = await taker.fillOrderAsync(order, order.takerAssetAmount); // Check balances - const expectedBalances = await simulateFillAsync(order, receipt); + const expectedBalances = LocalBalanceStore.create(balanceStore); + expectedBalances.simulateFills([order], taker.address, receipt, deployment, DeploymentManager.protocolFee); await balanceStore.updateBalancesAsync(); balanceStore.assertEquals(expectedBalances); @@ -233,7 +189,8 @@ blockchainTests.resets('fillOrder integration tests', env => { const receipt = await taker.fillOrderAsync(order, order.takerAssetAmount); // Check balances - const expectedBalances = await simulateFillAsync(order, receipt); + const expectedBalances = LocalBalanceStore.create(balanceStore); + expectedBalances.simulateFills([order], taker.address, receipt, deployment, DeploymentManager.protocolFee); await balanceStore.updateBalancesAsync(); balanceStore.assertEquals(expectedBalances); @@ -310,7 +267,7 @@ blockchainTests.resets('fillOrder integration tests', env => { const [finalizePoolReceipt] = await delegator.finalizePoolsAsync([poolId]); // Check balances - await expectedBalances.transferAssetAsync( + expectedBalances.transferAsset( deployment.staking.stakingProxy.address, operator.address, operatorReward, @@ -371,7 +328,8 @@ blockchainTests.resets('fillOrder integration tests', env => { const order = await maker.signOrderAsync(); const receipt = await taker.fillOrderAsync(order, order.takerAssetAmount, { value: constants.ZERO_AMOUNT }); const rewardsAvailable = DeploymentManager.protocolFee; - const expectedBalances = await simulateFillAsync(order, receipt, constants.ZERO_AMOUNT); + const expectedBalances = LocalBalanceStore.create(balanceStore); + expectedBalances.simulateFills([order], taker.address, receipt, deployment); // End the epoch. This should wrap the staking proxy's ETH balance. const endEpochReceipt = await delegator.endEpochAsync(); @@ -392,7 +350,7 @@ blockchainTests.resets('fillOrder integration tests', env => { const [finalizePoolReceipt] = await delegator.finalizePoolsAsync([poolId]); // Check balances - await expectedBalances.transferAssetAsync( + expectedBalances.transferAsset( deployment.staking.stakingProxy.address, operator.address, operatorReward, diff --git a/contracts/integrations/test/exchange/match_order_tester.ts b/contracts/integrations/test/exchange/match_order_tester.ts index b3decd724d..58768cfa81 100644 --- a/contracts/integrations/test/exchange/match_order_tester.ts +++ b/contracts/integrations/test/exchange/match_order_tester.ts @@ -296,7 +296,7 @@ export class MatchOrderTester { localBalanceStore.burnGas(takerAddress, DeploymentManager.gasPrice.times(transactionReceipt.gasUsed)); // Simulate the fill. - const expectedMatchResults = await this._simulateMatchOrdersAsync( + const expectedMatchResults = this._simulateMatchOrders( orders, takerAddress, toFullMatchTransferAmounts(expectedTransferAmounts), @@ -319,12 +319,12 @@ export class MatchOrderTester { * @param localBalanceStore The balance store to use for the simulation. * @return The new account balances and fill events that occurred during the match. */ - protected async _simulateMatchOrdersAsync( + protected _simulateMatchOrders( orders: MatchedOrders, takerAddress: string, transferAmounts: MatchTransferAmounts, localBalanceStore: LocalBalanceStore, - ): Promise { + ): MatchResults { // prettier-ignore const matchResults = { orders: { @@ -343,7 +343,7 @@ export class MatchOrderTester { }; // Right maker asset -> left maker - await localBalanceStore.transferAssetAsync( + localBalanceStore.transferAsset( orders.rightOrder.makerAddress, orders.leftOrder.makerAddress, transferAmounts.rightMakerAssetBoughtByLeftMakerAmount, @@ -352,7 +352,7 @@ export class MatchOrderTester { if (orders.leftOrder.makerAddress !== orders.leftOrder.feeRecipientAddress) { // Left maker fees - await localBalanceStore.transferAssetAsync( + localBalanceStore.transferAsset( orders.leftOrder.makerAddress, orders.leftOrder.feeRecipientAddress, transferAmounts.leftMakerFeeAssetPaidByLeftMakerAmount, @@ -361,7 +361,7 @@ export class MatchOrderTester { } // Left maker asset -> right maker - await localBalanceStore.transferAssetAsync( + localBalanceStore.transferAsset( orders.leftOrder.makerAddress, orders.rightOrder.makerAddress, transferAmounts.leftMakerAssetBoughtByRightMakerAmount, @@ -370,7 +370,7 @@ export class MatchOrderTester { if (orders.rightOrder.makerAddress !== orders.rightOrder.feeRecipientAddress) { // Right maker fees - await localBalanceStore.transferAssetAsync( + localBalanceStore.transferAsset( orders.rightOrder.makerAddress, orders.rightOrder.feeRecipientAddress, transferAmounts.rightMakerFeeAssetPaidByRightMakerAmount, @@ -379,7 +379,7 @@ export class MatchOrderTester { } // Left taker profit - await localBalanceStore.transferAssetAsync( + localBalanceStore.transferAsset( orders.leftOrder.makerAddress, takerAddress, transferAmounts.leftMakerAssetReceivedByTakerAmount, @@ -387,7 +387,7 @@ export class MatchOrderTester { ); // Right taker profit - await localBalanceStore.transferAssetAsync( + localBalanceStore.transferAsset( orders.rightOrder.makerAddress, takerAddress, transferAmounts.rightMakerAssetReceivedByTakerAmount, @@ -395,7 +395,7 @@ export class MatchOrderTester { ); // Left taker fees - await localBalanceStore.transferAssetAsync( + localBalanceStore.transferAsset( takerAddress, orders.leftOrder.feeRecipientAddress, transferAmounts.leftTakerFeeAssetPaidByTakerAmount, @@ -403,7 +403,7 @@ export class MatchOrderTester { ); // Right taker fees - await localBalanceStore.transferAssetAsync( + localBalanceStore.transferAsset( takerAddress, orders.rightOrder.feeRecipientAddress, transferAmounts.rightTakerFeeAssetPaidByTakerAmount, @@ -424,13 +424,13 @@ export class MatchOrderTester { this._deployment.staking.stakingProxy.address, transferAmounts.rightProtocolFeePaidByTakerInEthAmount, ); - await localBalanceStore.transferAssetAsync( + localBalanceStore.transferAsset( takerAddress, this._deployment.staking.stakingProxy.address, transferAmounts.leftProtocolFeePaidByTakerInWethAmount, wethAssetData, ); - await localBalanceStore.transferAssetAsync( + localBalanceStore.transferAsset( takerAddress, this._deployment.staking.stakingProxy.address, transferAmounts.rightProtocolFeePaidByTakerInWethAmount, @@ -513,7 +513,7 @@ export class MatchOrderTester { // Add the latest match to the batch match results batchMatchResults.matches.push( - await this._simulateMatchOrdersAsync( + this._simulateMatchOrders( matchedOrders, takerAddress, toFullMatchTransferAmounts(transferAmounts[i]), diff --git a/contracts/integrations/test/exchange/match_orders_maximal_fill_test.ts b/contracts/integrations/test/exchange/match_orders_maximal_fill_test.ts index 06497d4ebf..9fa037c208 100644 --- a/contracts/integrations/test/exchange/match_orders_maximal_fill_test.ts +++ b/contracts/integrations/test/exchange/match_orders_maximal_fill_test.ts @@ -163,7 +163,7 @@ blockchainTests.resets('matchOrdersWithMaximalFill integration tests', env => { }); after(async () => { - Actor.count = 0; + Actor.reset(); }); describe('matchOrdersWithMaximalFill', () => { diff --git a/contracts/integrations/test/exchange/match_orders_test.ts b/contracts/integrations/test/exchange/match_orders_test.ts index 6f7b920875..ed2d3785b2 100644 --- a/contracts/integrations/test/exchange/match_orders_test.ts +++ b/contracts/integrations/test/exchange/match_orders_test.ts @@ -163,7 +163,7 @@ blockchainTests.resets('matchOrders integration tests', env => { }); after(async () => { - Actor.count = 0; + Actor.reset(); }); describe('matchOrders', () => { diff --git a/contracts/integrations/test/exchange/transaction_protocol_fee_test.ts b/contracts/integrations/test/exchange/transaction_protocol_fee_test.ts new file mode 100644 index 0000000000..0d6018b2f7 --- /dev/null +++ b/contracts/integrations/test/exchange/transaction_protocol_fee_test.ts @@ -0,0 +1,500 @@ +// tslint:disable: max-file-line-count +import { IAssetDataContract } from '@0x/contracts-asset-proxy'; +import { exchangeDataEncoder, ExchangeRevertErrors } from '@0x/contracts-exchange'; +import { + blockchainTests, + constants, + describe, + ExchangeFunctionName, + expect, + orderHashUtils, + transactionHashUtils, +} from '@0x/contracts-test-utils'; +import { SignedOrder, SignedZeroExTransaction } from '@0x/types'; +import { BigNumber } from '@0x/utils'; + +import { Actor } from '../framework/actors/base'; +import { FeeRecipient } from '../framework/actors/fee_recipient'; +import { Maker } from '../framework/actors/maker'; +import { Taker } from '../framework/actors/taker'; +import { actorAddressesByName } from '../framework/actors/utils'; +import { BlockchainBalanceStore } from '../framework/balances/blockchain_balance_store'; +import { LocalBalanceStore } from '../framework/balances/local_balance_store'; +import { DeploymentManager } from '../framework/deployment_manager'; + +// tslint:disable:no-unnecessary-type-assertion +blockchainTests.resets('Transaction <> protocol fee integration tests', env => { + let deployment: DeploymentManager; + let balanceStore: BlockchainBalanceStore; + + let maker: Maker; + let feeRecipient: FeeRecipient; + let alice: Taker; + let bob: Taker; + let charlie: Taker; + let wethless: Taker; // Used to test revert scenarios + + let order: SignedOrder; // All orders will have the same fields, modulo salt and expiration time + let transactionA: SignedZeroExTransaction; // fillOrder transaction signed by Alice + let transactionB: SignedZeroExTransaction; // fillOrder transaction signed by Bob + let transactionC: SignedZeroExTransaction; // fillOrder transaction signed by Charlie + + before(async () => { + deployment = await DeploymentManager.deployAsync(env, { + numErc20TokensToDeploy: 4, + numErc721TokensToDeploy: 0, + numErc1155TokensToDeploy: 0, + }); + const assetDataEncoder = new IAssetDataContract(constants.NULL_ADDRESS, env.provider); + const [makerToken, takerToken, makerFeeToken, takerFeeToken] = deployment.tokens.erc20; + + alice = new Taker({ name: 'Alice', deployment }); + bob = new Taker({ name: 'Bob', deployment }); + charlie = new Taker({ name: 'Charlie', deployment }); + wethless = new Taker({ name: 'wethless', deployment }); + feeRecipient = new FeeRecipient({ + name: 'Fee recipient', + deployment, + }); + maker = new Maker({ + name: 'Maker', + deployment, + orderConfig: { + feeRecipientAddress: feeRecipient.address, + makerAssetData: assetDataEncoder.ERC20Token(makerToken.address).getABIEncodedTransactionData(), + takerAssetData: assetDataEncoder.ERC20Token(takerToken.address).getABIEncodedTransactionData(), + makerFeeAssetData: assetDataEncoder.ERC20Token(makerFeeToken.address).getABIEncodedTransactionData(), + takerFeeAssetData: assetDataEncoder.ERC20Token(takerFeeToken.address).getABIEncodedTransactionData(), + }, + }); + + for (const taker of [alice, bob, charlie]) { + await taker.configureERC20TokenAsync(takerToken); + await taker.configureERC20TokenAsync(takerFeeToken); + await taker.configureERC20TokenAsync(deployment.tokens.weth, deployment.staking.stakingProxy.address); + } + await wethless.configureERC20TokenAsync(takerToken); + await wethless.configureERC20TokenAsync(takerFeeToken); + await wethless.configureERC20TokenAsync( + deployment.tokens.weth, + deployment.staking.stakingProxy.address, + constants.ZERO_AMOUNT, // wethless taker has approved the proxy, but has no weth + ); + await maker.configureERC20TokenAsync(makerToken); + await maker.configureERC20TokenAsync(makerFeeToken); + + balanceStore = new BlockchainBalanceStore( + { + ...actorAddressesByName([alice, bob, charlie, maker, feeRecipient]), + StakingProxy: deployment.staking.stakingProxy.address, + }, + { erc20: { makerToken, takerToken, makerFeeToken, takerFeeToken, wETH: deployment.tokens.weth } }, + {}, + ); + await balanceStore.updateBalancesAsync(); + + order = await maker.signOrderAsync(); + let data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order]); + transactionA = await alice.signTransactionAsync({ data }); + + order = await maker.signOrderAsync(); + data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order]); + transactionB = await bob.signTransactionAsync({ data }); + + order = await maker.signOrderAsync(); + data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order]); + transactionC = await charlie.signTransactionAsync({ data }); + }); + + after(async () => { + Actor.reset(); + }); + + const REFUND_AMOUNT = new BigNumber(1); + + function protocolFeeError( + failedOrder: SignedOrder, + failedTransaction: SignedZeroExTransaction, + ): ExchangeRevertErrors.TransactionExecutionError { + const nestedError = new ExchangeRevertErrors.PayProtocolFeeError( + orderHashUtils.getOrderHashHex(failedOrder), + DeploymentManager.protocolFee, + maker.address, + wethless.address, + '0x', + ).encode(); + return new ExchangeRevertErrors.TransactionExecutionError( + transactionHashUtils.getTransactionHashHex(failedTransaction), + nestedError, + ); + } + + describe('executeTransaction', () => { + const ETH_FEE_WITH_REFUND = DeploymentManager.protocolFee.plus(REFUND_AMOUNT); + + let expectedBalances: LocalBalanceStore; + beforeEach(async () => { + await balanceStore.updateBalancesAsync(); + expectedBalances = LocalBalanceStore.create(balanceStore); + }); + afterEach(async () => { + await balanceStore.updateBalancesAsync(); + balanceStore.assertEquals(expectedBalances); + }); + + describe('Simple', () => { + it('Alice executeTransaction => Alice fillOrder; protocol fee in ETH', async () => { + const txReceipt = await deployment.exchange + .executeTransaction(transactionA, transactionA.signature) + .awaitTransactionSuccessAsync({ from: alice.address, value: ETH_FEE_WITH_REFUND }); + expectedBalances.simulateFills([order], alice.address, txReceipt, deployment, ETH_FEE_WITH_REFUND); + }); + it('Alice executeTransaction => Bob fillOrder; protocol fee in ETH', async () => { + const txReceipt = await deployment.exchange + .executeTransaction(transactionB, transactionB.signature) + .awaitTransactionSuccessAsync({ from: alice.address, value: ETH_FEE_WITH_REFUND }); + expectedBalances.simulateFills([order], bob.address, txReceipt, deployment, ETH_FEE_WITH_REFUND); + }); + it('Alice executeTransaction => Alice fillOrder; protocol fee in wETH', async () => { + const txReceipt = await deployment.exchange + .executeTransaction(transactionA, transactionA.signature) + .awaitTransactionSuccessAsync({ from: alice.address, value: REFUND_AMOUNT }); + expectedBalances.simulateFills([order], alice.address, txReceipt, deployment, REFUND_AMOUNT); + }); + it('Alice executeTransaction => Bob fillOrder; protocol fee in wETH', async () => { + const txReceipt = await deployment.exchange + .executeTransaction(transactionB, transactionB.signature) + .awaitTransactionSuccessAsync({ from: alice.address, value: REFUND_AMOUNT }); + expectedBalances.simulateFills([order], bob.address, txReceipt, deployment, REFUND_AMOUNT); + }); + it('Alice executeTransaction => wETH-less taker fillOrder; reverts because protocol fee cannot be paid', async () => { + const data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order]); + const transaction = await wethless.signTransactionAsync({ data }); + const tx = deployment.exchange + .executeTransaction(transaction, transaction.signature) + .awaitTransactionSuccessAsync({ from: alice.address, value: REFUND_AMOUNT }); + return expect(tx).to.revertWith(protocolFeeError(order, transaction)); + }); + it('Alice executeTransaction => Alice batchFillOrders; mixed protocol fees', async () => { + const orders = [order, await maker.signOrderAsync()]; + const data = exchangeDataEncoder.encodeOrdersToExchangeData( + ExchangeFunctionName.BatchFillOrders, + orders, + ); + const batchFillTransaction = await alice.signTransactionAsync({ data }); + const txReceipt = await deployment.exchange + .executeTransaction(batchFillTransaction, batchFillTransaction.signature) + .awaitTransactionSuccessAsync({ from: alice.address, value: ETH_FEE_WITH_REFUND }); + expectedBalances.simulateFills(orders, alice.address, txReceipt, deployment, ETH_FEE_WITH_REFUND); + }); + it('Alice executeTransaction => Bob batchFillOrders; mixed protocol fees', async () => { + const orders = [order, await maker.signOrderAsync()]; + const data = exchangeDataEncoder.encodeOrdersToExchangeData( + ExchangeFunctionName.BatchFillOrders, + orders, + ); + const batchFillTransaction = await bob.signTransactionAsync({ data }); + const txReceipt = await deployment.exchange + .executeTransaction(batchFillTransaction, batchFillTransaction.signature) + .awaitTransactionSuccessAsync({ from: alice.address, value: ETH_FEE_WITH_REFUND }); + expectedBalances.simulateFills(orders, bob.address, txReceipt, deployment, ETH_FEE_WITH_REFUND); + }); + }); + describe('Nested', () => { + it('Alice executeTransaction => Alice executeTransaction => Alice fillOrder; protocol fee in ETH', async () => { + const recursiveData = deployment.exchange + .executeTransaction(transactionA, transactionA.signature) + .getABIEncodedTransactionData(); + const recursiveTransaction = await alice.signTransactionAsync({ data: recursiveData }); + const txReceipt = await deployment.exchange + .executeTransaction(recursiveTransaction, recursiveTransaction.signature) + .awaitTransactionSuccessAsync({ from: alice.address, value: ETH_FEE_WITH_REFUND }); + expectedBalances.simulateFills([order], alice.address, txReceipt, deployment, ETH_FEE_WITH_REFUND); + }); + it('Alice executeTransaction => Alice executeTransaction => Bob fillOrder; protocol fee in ETH', async () => { + const recursiveData = deployment.exchange + .executeTransaction(transactionB, transactionB.signature) + .getABIEncodedTransactionData(); + const recursiveTransaction = await alice.signTransactionAsync({ data: recursiveData }); + const txReceipt = await deployment.exchange + .executeTransaction(recursiveTransaction, recursiveTransaction.signature) + .awaitTransactionSuccessAsync({ from: alice.address, value: ETH_FEE_WITH_REFUND }); + expectedBalances.simulateFills([order], bob.address, txReceipt, deployment, ETH_FEE_WITH_REFUND); + }); + it('Alice executeTransaction => Alice executeTransaction => Alice fillOrder; protocol fee in wETH', async () => { + const recursiveData = deployment.exchange + .executeTransaction(transactionA, transactionA.signature) + .getABIEncodedTransactionData(); + const recursiveTransaction = await alice.signTransactionAsync({ data: recursiveData }); + const txReceipt = await deployment.exchange + .executeTransaction(recursiveTransaction, recursiveTransaction.signature) + .awaitTransactionSuccessAsync({ from: alice.address, value: REFUND_AMOUNT }); + expectedBalances.simulateFills([order], alice.address, txReceipt, deployment, REFUND_AMOUNT); + }); + it('Alice executeTransaction => Alice executeTransaction => Bob fillOrder; protocol fee in wETH', async () => { + const recursiveData = deployment.exchange + .executeTransaction(transactionB, transactionB.signature) + .getABIEncodedTransactionData(); + const recursiveTransaction = await alice.signTransactionAsync({ data: recursiveData }); + const txReceipt = await deployment.exchange + .executeTransaction(recursiveTransaction, recursiveTransaction.signature) + .awaitTransactionSuccessAsync({ from: alice.address, value: REFUND_AMOUNT }); + expectedBalances.simulateFills([order], bob.address, txReceipt, deployment, REFUND_AMOUNT); + }); + it('Alice executeTransaction => Alice executeTransaction => wETH-less taker fillOrder; reverts because protocol fee cannot be paid', async () => { + const data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order]); + const transaction = await wethless.signTransactionAsync({ data }); + const recursiveData = deployment.exchange + .executeTransaction(transaction, transaction.signature) + .getABIEncodedTransactionData(); + const recursiveTransaction = await alice.signTransactionAsync({ data: recursiveData }); + const tx = deployment.exchange + .executeTransaction(recursiveTransaction, recursiveTransaction.signature) + .awaitTransactionSuccessAsync({ from: alice.address, value: REFUND_AMOUNT }); + const expectedError = new ExchangeRevertErrors.TransactionExecutionError( + transactionHashUtils.getTransactionHashHex(recursiveTransaction), + protocolFeeError(order, transaction).encode(), + ); + return expect(tx).to.revertWith(expectedError); + }); + }); + }); + describe('batchExecuteTransactions', () => { + let expectedBalances: LocalBalanceStore; + beforeEach(async () => { + await balanceStore.updateBalancesAsync(); + expectedBalances = LocalBalanceStore.create(balanceStore); + }); + afterEach(async () => { + await balanceStore.updateBalancesAsync(); + balanceStore.assertEquals(expectedBalances); + }); + + describe('Simple', () => { + // All orders' protocol fees paid in ETH by sender + const ETH_FEES_WITH_REFUND = DeploymentManager.protocolFee.times(3).plus(REFUND_AMOUNT); + // First order's protocol fee paid in ETH by sender, the other two paid in WETH by their respective takers + const MIXED_FEES_WITH_REFUND = DeploymentManager.protocolFee.times(1).plus(REFUND_AMOUNT); + + it('Alice batchExecuteTransactions => Alice fillOrder, Bob fillOrder, Charlie fillOrder; protocol fees in ETH', async () => { + const transactions = [transactionA, transactionB, transactionC]; + const signatures = transactions.map(tx => tx.signature); + const txReceipt = await deployment.exchange + .batchExecuteTransactions(transactions, signatures) + .awaitTransactionSuccessAsync({ from: alice.address, value: ETH_FEES_WITH_REFUND }); + expectedBalances.simulateFills( + [order, order, order], + [alice.address, bob.address, charlie.address], + txReceipt, + deployment, + ETH_FEES_WITH_REFUND, + ); + }); + it('Alice batchExecuteTransactions => Bob fillOrder, Alice fillOrder, Charlie fillOrder; protocol fees in ETH', async () => { + const transactions = [transactionB, transactionA, transactionC]; + const signatures = transactions.map(tx => tx.signature); + const txReceipt = await deployment.exchange + .batchExecuteTransactions(transactions, signatures) + .awaitTransactionSuccessAsync({ from: alice.address, value: ETH_FEES_WITH_REFUND }); + expectedBalances.simulateFills( + [order, order, order], + [bob.address, alice.address, charlie.address], + txReceipt, + deployment, + ETH_FEES_WITH_REFUND, + ); + }); + it('Alice batchExecuteTransactions => Bob fillOrder, Charlie fillOrder, Alice fillOrder; protocol fees in ETH', async () => { + const transactions = [transactionB, transactionC, transactionA]; + const signatures = transactions.map(tx => tx.signature); + const txReceipt = await deployment.exchange + .batchExecuteTransactions(transactions, signatures) + .awaitTransactionSuccessAsync({ from: alice.address, value: ETH_FEES_WITH_REFUND }); + expectedBalances.simulateFills( + [order, order, order], + [bob.address, charlie.address, alice.address], + txReceipt, + deployment, + ETH_FEES_WITH_REFUND, + ); + }); + it('Alice batchExecuteTransactions => Alice fillOrder, Bob fillOrder, Charlie fillOrder; protocol fees in wETH', async () => { + const transactions = [transactionA, transactionB, transactionC]; + const signatures = transactions.map(tx => tx.signature); + const txReceipt = await deployment.exchange + .batchExecuteTransactions(transactions, signatures) + .awaitTransactionSuccessAsync({ from: alice.address, value: REFUND_AMOUNT }); + expectedBalances.simulateFills( + [order, order, order], + [alice.address, bob.address, charlie.address], + txReceipt, + deployment, + REFUND_AMOUNT, + ); + }); + it('Alice batchExecuteTransactions => Bob fillOrder, Alice fillOrder, Charlie fillOrder; protocol fees in wETH', async () => { + const transactions = [transactionB, transactionA, transactionC]; + const signatures = transactions.map(tx => tx.signature); + const txReceipt = await deployment.exchange + .batchExecuteTransactions(transactions, signatures) + .awaitTransactionSuccessAsync({ from: alice.address, value: REFUND_AMOUNT }); + expectedBalances.simulateFills( + [order, order, order], + [bob.address, alice.address, charlie.address], + txReceipt, + deployment, + REFUND_AMOUNT, + ); + }); + it('Alice batchExecuteTransactions => Bob fillOrder, Charlie fillOrder, Alice fillOrder; protocol fees in wETH', async () => { + const transactions = [transactionB, transactionC, transactionA]; + const signatures = transactions.map(tx => tx.signature); + const txReceipt = await deployment.exchange + .batchExecuteTransactions(transactions, signatures) + .awaitTransactionSuccessAsync({ from: alice.address, value: REFUND_AMOUNT }); + expectedBalances.simulateFills( + [order, order, order], + [bob.address, charlie.address, alice.address], + txReceipt, + deployment, + REFUND_AMOUNT, + ); + }); + it('Alice batchExecuteTransactions => Alice fillOrder, Bob fillOrder, Charlie fillOrder; mixed protocol fees', async () => { + const transactions = [transactionA, transactionB, transactionC]; + const signatures = transactions.map(tx => tx.signature); + const txReceipt = await deployment.exchange + .batchExecuteTransactions(transactions, signatures) + .awaitTransactionSuccessAsync({ from: alice.address, value: MIXED_FEES_WITH_REFUND }); + expectedBalances.simulateFills( + [order, order, order], + [alice.address, bob.address, charlie.address], + txReceipt, + deployment, + MIXED_FEES_WITH_REFUND, + ); + }); + it('Alice batchExecuteTransactions => Bob fillOrder, Alice fillOrder, Charlie fillOrder; mixed protocol fees', async () => { + const transactions = [transactionB, transactionA, transactionC]; + const signatures = transactions.map(tx => tx.signature); + const txReceipt = await deployment.exchange + .batchExecuteTransactions(transactions, signatures) + .awaitTransactionSuccessAsync({ from: alice.address, value: MIXED_FEES_WITH_REFUND }); + expectedBalances.simulateFills( + [order, order, order], + [bob.address, alice.address, charlie.address], + txReceipt, + deployment, + MIXED_FEES_WITH_REFUND, + ); + }); + it('Alice batchExecuteTransactions => Bob fillOrder, Charlie fillOrder, Alice fillOrder; mixed protocol fees', async () => { + const transactions = [transactionB, transactionC, transactionA]; + const signatures = transactions.map(tx => tx.signature); + const txReceipt = await deployment.exchange + .batchExecuteTransactions(transactions, signatures) + .awaitTransactionSuccessAsync({ from: alice.address, value: MIXED_FEES_WITH_REFUND }); + expectedBalances.simulateFills( + [order, order, order], + [bob.address, charlie.address, alice.address], + txReceipt, + deployment, + MIXED_FEES_WITH_REFUND, + ); + }); + it('Alice batchExecuteTransactions => Alice fillOrder, Bob fillOrder, wETH-less taker fillOrder; reverts because protocol fee cannot be paid', async () => { + const data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order]); + const failTransaction = await wethless.signTransactionAsync({ data }); + const transactions = [transactionA, transactionB, failTransaction]; + const signatures = transactions.map(transaction => transaction.signature); + const tx = deployment.exchange + .batchExecuteTransactions(transactions, signatures) + .awaitTransactionSuccessAsync({ from: alice.address, value: MIXED_FEES_WITH_REFUND }); + return expect(tx).to.revertWith(protocolFeeError(order, failTransaction)); + }); + }); + describe('Nested', () => { + // First two orders' protocol fees paid in ETH by sender, the others paid in WETH by their respective takers + const MIXED_FEES_WITH_REFUND = DeploymentManager.protocolFee.times(2.5); + + // Alice batchExecuteTransactions => Alice fillOrder, Bob fillOrder, Charlie fillOrder + let nestedTransaction: SignedZeroExTransaction; + // Second fillOrder transaction signed by Bob + let transactionB2: SignedZeroExTransaction; + // Second fillOrder transaction signed by Charlie + let transactionC2: SignedZeroExTransaction; + + before(async () => { + let newOrder = await maker.signOrderAsync(); + let data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [newOrder]); + transactionB2 = await bob.signTransactionAsync({ data }); + + newOrder = await maker.signOrderAsync(); + data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [newOrder]); + transactionC2 = await charlie.signTransactionAsync({ data }); + + const transactions = [transactionA, transactionB, transactionC]; + const signatures = transactions.map(tx => tx.signature); + const recursiveData = await deployment.exchange + .batchExecuteTransactions(transactions, signatures) + .getABIEncodedTransactionData(); + nestedTransaction = await alice.signTransactionAsync({ data: recursiveData }); + }); + + it('Alice executeTransaction => nested batchExecuteTransactions; mixed protocol fees', async () => { + const txReceipt = await deployment.exchange + .executeTransaction(nestedTransaction, nestedTransaction.signature) + .awaitTransactionSuccessAsync({ from: alice.address, value: MIXED_FEES_WITH_REFUND }); + expectedBalances.simulateFills( + [order, order, order], + [alice.address, bob.address, charlie.address], + txReceipt, + deployment, + MIXED_FEES_WITH_REFUND, + ); + }); + it('Alice batchExecuteTransactions => nested batchExecuteTransactions, Bob fillOrder, Charlie fillOrder; mixed protocol fees', async () => { + const transactions = [nestedTransaction, transactionB2, transactionC2]; + const signatures = transactions.map(tx => tx.signature); + const txReceipt = await deployment.exchange + .batchExecuteTransactions(transactions, signatures) + .awaitTransactionSuccessAsync({ from: alice.address, value: MIXED_FEES_WITH_REFUND }); + expectedBalances.simulateFills( + [order, order, order, order, order], + [alice.address, bob.address, charlie.address, bob.address, charlie.address], + txReceipt, + deployment, + MIXED_FEES_WITH_REFUND, + ); + }); + it('Alice batchExecuteTransactions => Bob fillOrder, nested batchExecuteTransactions, Charlie fillOrder; mixed protocol fees', async () => { + const transactions = [transactionB2, nestedTransaction, transactionC2]; + const signatures = transactions.map(tx => tx.signature); + const txReceipt = await deployment.exchange + .batchExecuteTransactions(transactions, signatures) + .awaitTransactionSuccessAsync({ from: alice.address, value: MIXED_FEES_WITH_REFUND }); + expectedBalances.simulateFills( + [order, order, order, order, order], + [bob.address, alice.address, bob.address, charlie.address, charlie.address], + txReceipt, + deployment, + MIXED_FEES_WITH_REFUND, + ); + }); + it('Alice batchExecuteTransactions => Bob fillOrder, Charlie fillOrder, nested batchExecuteTransactions; mixed protocol fees', async () => { + const transactions = [transactionB2, transactionC2, nestedTransaction]; + const signatures = transactions.map(tx => tx.signature); + const txReceipt = await deployment.exchange + .batchExecuteTransactions(transactions, signatures) + .awaitTransactionSuccessAsync({ from: alice.address, value: MIXED_FEES_WITH_REFUND }); + expectedBalances.simulateFills( + [order, order, order, order, order], + [bob.address, charlie.address, alice.address, bob.address, charlie.address], + txReceipt, + deployment, + MIXED_FEES_WITH_REFUND, + ); + }); + }); + }); +}); diff --git a/contracts/integrations/test/exchange/transaction_test.ts b/contracts/integrations/test/exchange/transaction_test.ts new file mode 100644 index 0000000000..fd65e85d51 --- /dev/null +++ b/contracts/integrations/test/exchange/transaction_test.ts @@ -0,0 +1,809 @@ +// tslint:disable: max-file-line-count +import { IAssetDataContract } from '@0x/contracts-asset-proxy'; +import { + ExchangeCancelEventArgs, + ExchangeCancelUpToEventArgs, + exchangeDataEncoder, + ExchangeEvents, + ExchangeFillEventArgs, + ExchangeRevertErrors, + ExchangeSignatureValidatorApprovalEventArgs, + ExchangeTransactionExecutionEventArgs, +} from '@0x/contracts-exchange'; +import { ReferenceFunctions } from '@0x/contracts-exchange-libs'; +import { + blockchainTests, + constants, + describe, + ExchangeFunctionName, + expect, + getLatestBlockTimestampAsync, + hexConcat, + hexRandom, + orderHashUtils, + randomAddress, + transactionHashUtils, + verifyEventsFromLogs, +} from '@0x/contracts-test-utils'; +import { FillResults, OrderStatus, SignatureType, SignedOrder } from '@0x/types'; +import { BigNumber } from '@0x/utils'; +import { LogWithDecodedArgs } from 'ethereum-types'; + +import { Actor } from '../framework/actors/base'; +import { FeeRecipient } from '../framework/actors/fee_recipient'; +import { Maker } from '../framework/actors/maker'; +import { Taker } from '../framework/actors/taker'; +import { DeploymentManager } from '../framework/deployment_manager'; + +// tslint:disable:no-unnecessary-type-assertion +blockchainTests.resets('Transaction integration tests', env => { + let deployment: DeploymentManager; + + let maker: Maker; + let takers: [Taker, Taker]; + let feeRecipient: FeeRecipient; + let sender: Actor; + + before(async () => { + deployment = await DeploymentManager.deployAsync(env, { + numErc20TokensToDeploy: 4, + numErc721TokensToDeploy: 0, + numErc1155TokensToDeploy: 0, + }); + const assetDataEncoder = new IAssetDataContract(constants.NULL_ADDRESS, env.provider); + const [makerToken, takerToken, makerFeeToken, takerFeeToken] = deployment.tokens.erc20; + + takers = [new Taker({ name: 'Taker 1', deployment }), new Taker({ name: 'Taker 2', deployment })]; + feeRecipient = new FeeRecipient({ + name: 'Fee recipient', + deployment, + }); + maker = new Maker({ + name: 'Maker', + deployment, + orderConfig: { + feeRecipientAddress: feeRecipient.address, + makerAssetData: assetDataEncoder.ERC20Token(makerToken.address).getABIEncodedTransactionData(), + takerAssetData: assetDataEncoder.ERC20Token(takerToken.address).getABIEncodedTransactionData(), + makerFeeAssetData: assetDataEncoder.ERC20Token(makerFeeToken.address).getABIEncodedTransactionData(), + takerFeeAssetData: assetDataEncoder.ERC20Token(takerFeeToken.address).getABIEncodedTransactionData(), + }, + }); + sender = new Actor({ name: 'Transaction sender', deployment }); + + for (const taker of takers) { + await taker.configureERC20TokenAsync(takerToken); + await taker.configureERC20TokenAsync(takerFeeToken); + await taker.configureERC20TokenAsync(deployment.tokens.weth, deployment.staking.stakingProxy.address); + } + await maker.configureERC20TokenAsync(makerToken); + await maker.configureERC20TokenAsync(makerFeeToken); + }); + + after(async () => { + Actor.reset(); + }); + + function defaultFillEvent(order: SignedOrder): ExchangeFillEventArgs { + return { + makerAddress: maker.address, + feeRecipientAddress: feeRecipient.address, + makerAssetData: order.makerAssetData, + takerAssetData: order.takerAssetData, + makerFeeAssetData: order.makerFeeAssetData, + takerFeeAssetData: order.takerFeeAssetData, + orderHash: orderHashUtils.getOrderHashHex(order), + takerAddress: takers[0].address, + senderAddress: sender.address, + makerAssetFilledAmount: order.makerAssetAmount, + takerAssetFilledAmount: order.takerAssetAmount, + makerFeePaid: order.makerFee, + takerFeePaid: order.takerFee, + protocolFeePaid: DeploymentManager.protocolFee, + }; + } + + function defaultCancelEvent(order: SignedOrder): ExchangeCancelEventArgs { + return { + makerAddress: maker.address, + feeRecipientAddress: feeRecipient.address, + makerAssetData: order.makerAssetData, + takerAssetData: order.takerAssetData, + senderAddress: sender.address, + orderHash: orderHashUtils.getOrderHashHex(order), + }; + } + + describe('executeTransaction', () => { + describe('general functionality', () => { + it('should log the correct transactionHash if successfully executed', async () => { + const order = await maker.signOrderAsync(); + const data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order]); + const transaction = await takers[0].signTransactionAsync({ data }); + const transactionReceipt = await deployment.exchange + .executeTransaction(transaction, transaction.signature) + .awaitTransactionSuccessAsync({ from: sender.address }); + verifyEventsFromLogs( + transactionReceipt.logs, + [{ transactionHash: transactionHashUtils.getTransactionHashHex(transaction) }], + ExchangeEvents.TransactionExecution, + ); + }); + it('should revert if the transaction is expired', async () => { + const order = await maker.signOrderAsync(); + const data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order]); + const currentTimestamp = await getLatestBlockTimestampAsync(); + const transaction = await takers[0].signTransactionAsync({ + data, + expirationTimeSeconds: new BigNumber(currentTimestamp).minus(10), + }); + const transactionHashHex = transactionHashUtils.getTransactionHashHex(transaction); + const expectedError = new ExchangeRevertErrors.TransactionError( + ExchangeRevertErrors.TransactionErrorCode.Expired, + transactionHashHex, + ); + const tx = deployment.exchange + .executeTransaction(transaction, transaction.signature) + .awaitTransactionSuccessAsync({ from: sender.address }); + return expect(tx).to.revertWith(expectedError); + }); + it('should revert if the actual gasPrice is greater than expected', async () => { + const order = await maker.signOrderAsync(); + const data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order]); + const transaction = await takers[0].signTransactionAsync({ data }); + const transactionHashHex = transactionHashUtils.getTransactionHashHex(transaction); + const actualGasPrice = transaction.gasPrice.plus(1); + const expectedError = new ExchangeRevertErrors.TransactionGasPriceError( + transactionHashHex, + actualGasPrice, + transaction.gasPrice, + ); + const tx = deployment.exchange + .executeTransaction(transaction, transaction.signature) + .awaitTransactionSuccessAsync({ gasPrice: actualGasPrice, from: sender.address }); + return expect(tx).to.revertWith(expectedError); + }); + it('should revert if the actual gasPrice is less than expected', async () => { + const order = await maker.signOrderAsync(); + const data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order]); + const transaction = await takers[0].signTransactionAsync({ data }); + const transactionHashHex = transactionHashUtils.getTransactionHashHex(transaction); + const actualGasPrice = transaction.gasPrice.minus(1); + const expectedError = new ExchangeRevertErrors.TransactionGasPriceError( + transactionHashHex, + actualGasPrice, + transaction.gasPrice, + ); + const tx = deployment.exchange + .executeTransaction(transaction, transaction.signature) + .awaitTransactionSuccessAsync({ gasPrice: actualGasPrice, from: sender.address }); + return expect(tx).to.revertWith(expectedError); + }); + }); + describe('fill methods', () => { + for (const fnName of [ + ...constants.SINGLE_FILL_FN_NAMES, + ...constants.BATCH_FILL_FN_NAMES, + ...constants.MARKET_FILL_FN_NAMES, + ]) { + it(`${fnName} should revert if signature is invalid and not called by signer`, async () => { + const order = await maker.signOrderAsync(); + const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, [order]); + const transaction = await takers[0].signTransactionAsync({ data }); + transaction.signature = hexConcat(hexRandom(65), SignatureType.EthSign); + const transactionHashHex = transactionHashUtils.getTransactionHashHex(transaction); + const expectedError = new ExchangeRevertErrors.SignatureError( + ExchangeRevertErrors.SignatureErrorCode.BadTransactionSignature, + transactionHashHex, + transaction.signerAddress, + transaction.signature, + ); + const tx = deployment.exchange + .executeTransaction(transaction, transaction.signature) + .awaitTransactionSuccessAsync({ from: sender.address }); + return expect(tx).to.revertWith(expectedError); + }); + it(`${fnName} should be successful if signed by taker and called by sender`, async () => { + const order = await maker.signOrderAsync(); + const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, [order]); + const transaction = await takers[0].signTransactionAsync({ data }); + const transactionReceipt = await deployment.exchange + .executeTransaction(transaction, transaction.signature) + .awaitTransactionSuccessAsync({ from: sender.address }); + verifyEventsFromLogs( + transactionReceipt.logs, + [defaultFillEvent(order)], + ExchangeEvents.Fill, + ); + }); + it(`${fnName} should be successful if called by taker without a transaction signature`, async () => { + const order = await maker.signOrderAsync(); + const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, [order]); + const transaction = await takers[0].signTransactionAsync({ data }); + const transactionReceipt = await deployment.exchange + .executeTransaction(transaction, constants.NULL_BYTES) + .awaitTransactionSuccessAsync({ from: takers[0].address }); + verifyEventsFromLogs( + transactionReceipt.logs, + [{ ...defaultFillEvent(order), senderAddress: takers[0].address }], + ExchangeEvents.Fill, + ); + }); + it(`${fnName} should return the correct data if successful`, async () => { + const order = await maker.signOrderAsync(); + const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, [order]); + const transaction = await takers[0].signTransactionAsync({ data }); + const returnData = await deployment.exchange + .executeTransaction(transaction, transaction.signature) + .callAsync({ from: sender.address }); + + const decodedReturnData = deployment.exchange.getABIDecodedReturnData(fnName, returnData); + const fillResults = Array.isArray(decodedReturnData) ? decodedReturnData[0] : decodedReturnData; + + expect(fillResults).to.deep.equal( + ReferenceFunctions.calculateFillResults( + order, + order.takerAssetAmount, + DeploymentManager.protocolFeeMultiplier, + DeploymentManager.gasPrice, + ), + ); + }); + it(`${fnName} should revert if transaction has already been executed`, async () => { + const order = await maker.signOrderAsync(); + const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, [order]); + const transaction = await takers[0].signTransactionAsync({ data }); + await deployment.exchange + .executeTransaction(transaction, transaction.signature) + .awaitTransactionSuccessAsync({ from: sender.address }); + const transactionHashHex = transactionHashUtils.getTransactionHashHex(transaction); + const expectedError = new ExchangeRevertErrors.TransactionError( + ExchangeRevertErrors.TransactionErrorCode.AlreadyExecuted, + transactionHashHex, + ); + const tx = deployment.exchange + .executeTransaction(transaction, transaction.signature) + .awaitTransactionSuccessAsync({ from: sender.address }); + return expect(tx).to.revertWith(expectedError); + }); + it(`${fnName} should revert and rethrow error if executeTransaction is called recursively with a signature`, async () => { + const order = await maker.signOrderAsync(); + const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, [order]); + const transaction = await takers[0].signTransactionAsync({ data }); + const transactionHashHex = transactionHashUtils.getTransactionHashHex(transaction); + const recursiveData = deployment.exchange + .executeTransaction(transaction, transaction.signature) + .getABIEncodedTransactionData(); + const recursiveTransaction = await takers[0].signTransactionAsync({ + data: recursiveData, + }); + const recursiveTransactionHashHex = transactionHashUtils.getTransactionHashHex( + recursiveTransaction, + ); + const noReentrancyError = new ExchangeRevertErrors.TransactionInvalidContextError( + transactionHashHex, + transaction.signerAddress, + ).encode(); + const expectedError = new ExchangeRevertErrors.TransactionExecutionError( + recursiveTransactionHashHex, + noReentrancyError, + ); + const tx = deployment.exchange + .executeTransaction(recursiveTransaction, recursiveTransaction.signature) + .awaitTransactionSuccessAsync({ from: sender.address }); + return expect(tx).to.revertWith(expectedError); + }); + it(`${fnName} should be successful if executeTransaction is called recursively by taker without a signature`, async () => { + const order = await maker.signOrderAsync(); + const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, [order]); + const transaction = await takers[0].signTransactionAsync({ data }); + const recursiveData = deployment.exchange + .executeTransaction(transaction, constants.NULL_BYTES) + .getABIEncodedTransactionData(); + const recursiveTransaction = await takers[0].signTransactionAsync({ + data: recursiveData, + }); + const transactionReceipt = await deployment.exchange + .executeTransaction(recursiveTransaction, recursiveTransaction.signature) + .awaitTransactionSuccessAsync({ from: takers[0].address }); + verifyEventsFromLogs( + transactionReceipt.logs, + [{ ...defaultFillEvent(order), senderAddress: takers[0].address }], + ExchangeEvents.Fill, + ); + }); + if ( + [ + ExchangeFunctionName.FillOrderNoThrow, + ExchangeFunctionName.BatchFillOrdersNoThrow, + ExchangeFunctionName.MarketBuyOrdersNoThrow, + ExchangeFunctionName.MarketSellOrdersNoThrow, + ExchangeFunctionName.MarketBuyOrdersFillOrKill, + ExchangeFunctionName.MarketSellOrdersFillOrKill, + ].indexOf(fnName) === -1 + ) { + it(`${fnName} should revert and rethrow error if the underlying function reverts`, async () => { + const order = await maker.signOrderAsync(); + order.signature = constants.NULL_BYTES; + const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, [order]); + const transaction = await takers[0].signTransactionAsync({ 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 = deployment.exchange + .executeTransaction(transaction, transaction.signature) + .awaitTransactionSuccessAsync({ from: sender.address }); + return expect(tx).to.revertWith(expectedError); + }); + } + } + }); + describe('cancelOrder', () => { + it('should revert if not signed by or called by maker', async () => { + const order = await maker.signOrderAsync(); + const data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.CancelOrder, [order]); + const transaction = await takers[0].signTransactionAsync({ data }); + const transactionHashHex = transactionHashUtils.getTransactionHashHex(transaction); + const nestedError = new ExchangeRevertErrors.ExchangeInvalidContextError( + ExchangeRevertErrors.ExchangeContextErrorCodes.InvalidMaker, + orderHashUtils.getOrderHashHex(order), + takers[0].address, + ).encode(); + const expectedError = new ExchangeRevertErrors.TransactionExecutionError( + transactionHashHex, + nestedError, + ); + const tx = deployment.exchange + .executeTransaction(transaction, transaction.signature) + .awaitTransactionSuccessAsync({ from: sender.address }); + return expect(tx).to.revertWith(expectedError); + }); + it('should be successful if signed by maker and called by sender', async () => { + const order = await maker.signOrderAsync(); + const data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.CancelOrder, [order]); + const transaction = await maker.signTransactionAsync({ data }); + const transactionReceipt = await deployment.exchange + .executeTransaction(transaction, transaction.signature) + .awaitTransactionSuccessAsync({ from: sender.address }); + verifyEventsFromLogs( + transactionReceipt.logs, + [defaultCancelEvent(order)], + ExchangeEvents.Cancel, + ); + }); + it('should be successful if called by maker without a signature', async () => { + const order = await maker.signOrderAsync(); + const data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.CancelOrder, [order]); + const transaction = await maker.signTransactionAsync({ data }); + const transactionReceipt = await deployment.exchange + .executeTransaction(transaction, constants.NULL_BYTES) + .awaitTransactionSuccessAsync({ from: maker.address }); + verifyEventsFromLogs( + transactionReceipt.logs, + [{ ...defaultCancelEvent(order), senderAddress: maker.address }], + ExchangeEvents.Cancel, + ); + }); + }); + describe('batchCancelOrders', () => { + it('should revert if not signed by or called by maker', async () => { + const orders = [await maker.signOrderAsync(), await maker.signOrderAsync()]; + const data = exchangeDataEncoder.encodeOrdersToExchangeData( + ExchangeFunctionName.BatchCancelOrders, + orders, + ); + const transaction = await takers[0].signTransactionAsync({ data }); + const transactionHashHex = transactionHashUtils.getTransactionHashHex(transaction); + const nestedError = new ExchangeRevertErrors.ExchangeInvalidContextError( + ExchangeRevertErrors.ExchangeContextErrorCodes.InvalidMaker, + orderHashUtils.getOrderHashHex(orders[0]), + takers[0].address, + ).encode(); + const expectedError = new ExchangeRevertErrors.TransactionExecutionError( + transactionHashHex, + nestedError, + ); + const tx = deployment.exchange + .executeTransaction(transaction, transaction.signature) + .awaitTransactionSuccessAsync({ from: sender.address }); + return expect(tx).to.revertWith(expectedError); + }); + it('should be successful if signed by maker and called by sender', async () => { + const orders = [await maker.signOrderAsync(), await maker.signOrderAsync()]; + const data = exchangeDataEncoder.encodeOrdersToExchangeData( + ExchangeFunctionName.BatchCancelOrders, + orders, + ); + const transaction = await maker.signTransactionAsync({ data }); + const transactionReceipt = await deployment.exchange + .executeTransaction(transaction, transaction.signature) + .awaitTransactionSuccessAsync({ from: sender.address }); + verifyEventsFromLogs( + transactionReceipt.logs, + [defaultCancelEvent(orders[0]), defaultCancelEvent(orders[1])], + ExchangeEvents.Cancel, + ); + }); + it('should be successful if called by maker without a signature', async () => { + const orders = [await maker.signOrderAsync(), await maker.signOrderAsync()]; + const data = exchangeDataEncoder.encodeOrdersToExchangeData( + ExchangeFunctionName.BatchCancelOrders, + orders, + ); + const transaction = await maker.signTransactionAsync({ data }); + transaction.signature = constants.NULL_BYTES; + const transactionReceipt = await deployment.exchange + .executeTransaction(transaction, transaction.signature) + .awaitTransactionSuccessAsync({ from: maker.address }); + verifyEventsFromLogs( + transactionReceipt.logs, + [ + { ...defaultCancelEvent(orders[0]), senderAddress: maker.address }, + { ...defaultCancelEvent(orders[1]), senderAddress: maker.address }, + ], + ExchangeEvents.Cancel, + ); + }); + }); + describe('cancelOrdersUpTo', () => { + it('should be successful if signed by maker and called by sender', async () => { + const targetEpoch = constants.ZERO_AMOUNT; + const data = deployment.exchange.cancelOrdersUpTo(targetEpoch).getABIEncodedTransactionData(); + const transaction = await maker.signTransactionAsync({ data }); + const transactionReceipt = await deployment.exchange + .executeTransaction(transaction, transaction.signature) + .awaitTransactionSuccessAsync({ from: sender.address }); + verifyEventsFromLogs( + transactionReceipt.logs, + [ + { + makerAddress: maker.address, + orderSenderAddress: sender.address, + orderEpoch: targetEpoch.plus(1), + }, + ], + ExchangeEvents.CancelUpTo, + ); + }); + it('should be successful if called by maker without a signature', async () => { + const targetEpoch = constants.ZERO_AMOUNT; + const data = deployment.exchange.cancelOrdersUpTo(targetEpoch).getABIEncodedTransactionData(); + const transaction = await maker.signTransactionAsync({ data }); + const transactionReceipt = await deployment.exchange + .executeTransaction(transaction, constants.NULL_BYTES) + .awaitTransactionSuccessAsync({ from: maker.address }); + verifyEventsFromLogs( + transactionReceipt.logs, + [ + { + makerAddress: maker.address, + orderSenderAddress: constants.NULL_ADDRESS, + orderEpoch: targetEpoch.plus(1), + }, + ], + ExchangeEvents.CancelUpTo, + ); + }); + }); + describe('preSign', () => { + it('should preSign a hash for the signer', async () => { + const order = await maker.signOrderAsync(); + const orderHash = orderHashUtils.getOrderHashHex(order); + const data = deployment.exchange.preSign(orderHash).getABIEncodedTransactionData(); + const transaction = await takers[0].signTransactionAsync({ data }); + let isPreSigned = await deployment.exchange.preSigned(orderHash, takers[0].address).callAsync(); + expect(isPreSigned).to.be.eq(false); + await deployment.exchange + .executeTransaction(transaction, transaction.signature) + .awaitTransactionSuccessAsync({ from: sender.address }); + isPreSigned = await deployment.exchange.preSigned(orderHash, takers[0].address).callAsync(); + expect(isPreSigned).to.be.eq(true); + }); + it('should preSign a hash for the caller if called without a signature', async () => { + const order = await maker.signOrderAsync(); + const orderHash = orderHashUtils.getOrderHashHex(order); + const data = deployment.exchange.preSign(orderHash).getABIEncodedTransactionData(); + const transaction = await takers[0].signTransactionAsync({ data }); + let isPreSigned = await deployment.exchange.preSigned(orderHash, takers[0].address).callAsync(); + expect(isPreSigned).to.be.eq(false); + await deployment.exchange + .executeTransaction(transaction, constants.NULL_BYTES) + .awaitTransactionSuccessAsync({ from: takers[0].address }); + isPreSigned = await deployment.exchange.preSigned(orderHash, takers[0].address).callAsync(); + expect(isPreSigned).to.be.eq(true); + }); + }); + describe('setSignatureValidatorApproval', () => { + it('should approve a validator for the signer', async () => { + const validatorAddress = randomAddress(); + const shouldApprove = true; + const data = deployment.exchange + .setSignatureValidatorApproval(validatorAddress, shouldApprove) + .getABIEncodedTransactionData(); + const transaction = await takers[0].signTransactionAsync({ data }); + const transactionReceipt = await deployment.exchange + .executeTransaction(transaction, transaction.signature) + .awaitTransactionSuccessAsync({ from: sender.address }); + verifyEventsFromLogs( + transactionReceipt.logs, + [ + { + signerAddress: takers[0].address, + validatorAddress, + isApproved: shouldApprove, + }, + ], + ExchangeEvents.SignatureValidatorApproval, + ); + }); + it('should approve a validator for the caller if called with no signature', async () => { + const validatorAddress = randomAddress(); + const shouldApprove = true; + const data = deployment.exchange + .setSignatureValidatorApproval(validatorAddress, shouldApprove) + .getABIEncodedTransactionData(); + const transaction = await takers[0].signTransactionAsync({ data }); + const transactionReceipt = await deployment.exchange + .executeTransaction(transaction, constants.NULL_BYTES) + .awaitTransactionSuccessAsync({ from: takers[0].address }); + verifyEventsFromLogs( + transactionReceipt.logs, + [ + { + signerAddress: takers[0].address, + validatorAddress, + isApproved: shouldApprove, + }, + ], + ExchangeEvents.SignatureValidatorApproval, + ); + }); + }); + }); + describe('batchExecuteTransactions', () => { + it('should successfully call fillOrder via 2 transactions with different taker signatures', async () => { + const order1 = await maker.signOrderAsync(); + const order2 = await maker.signOrderAsync(); + const data1 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order1]); + const data2 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order2]); + const transaction1 = await takers[0].signTransactionAsync({ data: data1 }); + const transaction2 = await takers[1].signTransactionAsync({ data: data2 }); + const transactionReceipt = await deployment.exchange + .batchExecuteTransactions( + [transaction1, transaction2], + [transaction1.signature, transaction2.signature], + ) + .awaitTransactionSuccessAsync({ from: sender.address }); + verifyEventsFromLogs( + transactionReceipt.logs, + [ + { transactionHash: transactionHashUtils.getTransactionHashHex(transaction1) }, + { transactionHash: transactionHashUtils.getTransactionHashHex(transaction2) }, + ], + ExchangeEvents.TransactionExecution, + ); + verifyEventsFromLogs( + transactionReceipt.logs, + [defaultFillEvent(order1), { ...defaultFillEvent(order2), takerAddress: takers[1].address }], + ExchangeEvents.Fill, + ); + }); + it('should successfully call fillOrder via 2 transactions when called by taker with no signatures', async () => { + const order1 = await maker.signOrderAsync(); + const order2 = await maker.signOrderAsync(); + const data1 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order1]); + const data2 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order2]); + const transaction1 = await takers[0].signTransactionAsync({ data: data1 }); + const transaction2 = await takers[0].signTransactionAsync({ data: data2 }); + transaction1.signature = constants.NULL_BYTES; + transaction2.signature = constants.NULL_BYTES; + const transactionReceipt = await deployment.exchange + .batchExecuteTransactions( + [transaction1, transaction2], + [transaction1.signature, transaction2.signature], + ) + .awaitTransactionSuccessAsync({ from: takers[0].address }); + + verifyEventsFromLogs( + transactionReceipt.logs, + [ + { transactionHash: transactionHashUtils.getTransactionHashHex(transaction1) }, + { transactionHash: transactionHashUtils.getTransactionHashHex(transaction2) }, + ], + ExchangeEvents.TransactionExecution, + ); + verifyEventsFromLogs( + transactionReceipt.logs, + [ + { ...defaultFillEvent(order1), senderAddress: takers[0].address }, + { ...defaultFillEvent(order2), senderAddress: takers[0].address }, + ], + ExchangeEvents.Fill, + ); + }); + it('should successfully call fillOrder via 2 transactions when one is signed by taker1 and executeTransaction is called by taker2', async () => { + const order1 = await maker.signOrderAsync(); + const order2 = await maker.signOrderAsync(); + const data1 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order1]); + const data2 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order2]); + const transaction1 = await takers[0].signTransactionAsync({ data: data1 }); + const transaction2 = await takers[1].signTransactionAsync({ data: data2 }); + const transactionReceipt = await deployment.exchange + .batchExecuteTransactions([transaction1, transaction2], [transaction1.signature, constants.NULL_BYTES]) + .awaitTransactionSuccessAsync({ from: takers[1].address }); + + verifyEventsFromLogs( + transactionReceipt.logs, + [ + { transactionHash: transactionHashUtils.getTransactionHashHex(transaction1) }, + { transactionHash: transactionHashUtils.getTransactionHashHex(transaction2) }, + ], + ExchangeEvents.TransactionExecution, + ); + verifyEventsFromLogs( + transactionReceipt.logs, + [ + { ...defaultFillEvent(order1), senderAddress: takers[1].address }, + { + ...defaultFillEvent(order2), + takerAddress: takers[1].address, + senderAddress: takers[1].address, + }, + ], + ExchangeEvents.Fill, + ); + }); + it('should return the correct data for 2 different fillOrder calls', async () => { + const order1 = await maker.signOrderAsync(); + const order2 = await maker.signOrderAsync(); + const data1 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order1]); + const data2 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order2]); + const transaction1 = await takers[0].signTransactionAsync({ data: data1 }); + const transaction2 = await takers[1].signTransactionAsync({ data: data2 }); + const returnData = await deployment.exchange + .batchExecuteTransactions( + [transaction1, transaction2], + [transaction1.signature, transaction2.signature], + ) + .callAsync({ from: sender.address }); + const fillResults1: FillResults = deployment.exchange.getABIDecodedReturnData('fillOrder', returnData[0]); + const fillResults2: FillResults = deployment.exchange.getABIDecodedReturnData('fillOrder', returnData[1]); + expect(fillResults1).to.deep.equal( + ReferenceFunctions.calculateFillResults( + order1, + order1.takerAssetAmount, + DeploymentManager.protocolFeeMultiplier, + DeploymentManager.gasPrice, + ), + ); + expect(fillResults2).to.deep.equal( + ReferenceFunctions.calculateFillResults( + order2, + order2.takerAssetAmount, + DeploymentManager.protocolFeeMultiplier, + DeploymentManager.gasPrice, + ), + ); + }); + it('should successfully call fillOrder and cancelOrder via 2 transactions', async () => { + const order1 = await maker.signOrderAsync(); + const order2 = await maker.signOrderAsync(); + const data1 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order1]); + const data2 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.CancelOrder, [order2]); + const transaction1 = await takers[0].signTransactionAsync({ data: data1 }); + const transaction2 = await maker.signTransactionAsync({ data: data2 }); + const transactionReceipt = await deployment.exchange + .batchExecuteTransactions( + [transaction1, transaction2], + [transaction1.signature, transaction2.signature], + ) + .awaitTransactionSuccessAsync({ from: sender.address }); + + verifyEventsFromLogs( + transactionReceipt.logs, + [ + { transactionHash: transactionHashUtils.getTransactionHashHex(transaction1) }, + { transactionHash: transactionHashUtils.getTransactionHashHex(transaction2) }, + ], + ExchangeEvents.TransactionExecution, + ); + + const fillLogIndex = transactionReceipt.logs.findIndex( + log => (log as LogWithDecodedArgs).event === 'Fill', + ); + const cancelLogIndex = transactionReceipt.logs.findIndex( + log => (log as LogWithDecodedArgs).event === 'Cancel', + ); + expect(cancelLogIndex).to.greaterThan(fillLogIndex); + + verifyEventsFromLogs( + transactionReceipt.logs, + [defaultFillEvent(order1)], + ExchangeEvents.Fill, + ); + verifyEventsFromLogs( + transactionReceipt.logs, + [defaultCancelEvent(order2)], + ExchangeEvents.Cancel, + ); + }); + it('should return the correct data for a fillOrder and cancelOrder call', async () => { + const order1 = await maker.signOrderAsync(); + const order2 = await maker.signOrderAsync(); + const data1 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order1]); + const data2 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.CancelOrder, [order2]); + const transaction1 = await takers[0].signTransactionAsync({ data: data1 }); + const transaction2 = await maker.signTransactionAsync({ data: data2 }); + const returnData = await deployment.exchange + .batchExecuteTransactions( + [transaction1, transaction2], + [transaction1.signature, transaction2.signature], + ) + .callAsync({ from: sender.address }); + const fillResults: FillResults = deployment.exchange.getABIDecodedReturnData('fillOrder', returnData[0]); + expect(fillResults).to.deep.equal( + ReferenceFunctions.calculateFillResults( + order1, + order1.takerAssetAmount, + DeploymentManager.protocolFeeMultiplier, + DeploymentManager.gasPrice, + ), + ); + expect(returnData[1]).to.eq(constants.NULL_BYTES); + }); + it('should revert if a single transaction reverts', async () => { + const order = await maker.signOrderAsync(); + const data1 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.CancelOrder, [order]); + const data2 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order]); + const transaction1 = await maker.signTransactionAsync({ data: data1 }); + const transaction2 = await takers[0].signTransactionAsync({ data: data2 }); + const tx = deployment.exchange + .batchExecuteTransactions( + [transaction1, transaction2], + [transaction1.signature, transaction2.signature], + ) + .awaitTransactionSuccessAsync({ from: sender.address }); + const nestedError = new ExchangeRevertErrors.OrderStatusError( + orderHashUtils.getOrderHashHex(order), + OrderStatus.Cancelled, + ).encode(); + const expectedError = new ExchangeRevertErrors.TransactionExecutionError( + transactionHashUtils.getTransactionHashHex(transaction2), + nestedError, + ); + return expect(tx).to.revertWith(expectedError); + }); + it('should revert if a single transaction is expired', async () => { + const order1 = await maker.signOrderAsync(); + const order2 = await maker.signOrderAsync(); + const data1 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order1]); + const data2 = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.FillOrder, [order2]); + const currentTimestamp = await getLatestBlockTimestampAsync(); + const transaction1 = await takers[0].signTransactionAsync({ data: data1 }); + const transaction2 = await takers[1].signTransactionAsync({ + data: data2, + expirationTimeSeconds: new BigNumber(currentTimestamp).minus(10), + }); + const tx = deployment.exchange + .batchExecuteTransactions( + [transaction1, transaction2], + [transaction1.signature, transaction2.signature], + ) + .awaitTransactionSuccessAsync({ from: sender.address }); + const expiredTransactionHash = transactionHashUtils.getTransactionHashHex(transaction2); + const expectedError = new ExchangeRevertErrors.TransactionError( + ExchangeRevertErrors.TransactionErrorCode.Expired, + expiredTransactionHash, + ); + return expect(tx).to.revertWith(expectedError); + }); + }); +}); diff --git a/contracts/integrations/test/forwarder/bridge_test.ts b/contracts/integrations/test/forwarder/bridge_test.ts index f5ac2ce510..d3216d27e7 100644 --- a/contracts/integrations/test/forwarder/bridge_test.ts +++ b/contracts/integrations/test/forwarder/bridge_test.ts @@ -156,7 +156,7 @@ blockchainTests.resets('Forwarder <> ERC20Bridge integration tests', env => { }); after(async () => { - Actor.count = 0; + Actor.reset(); }); describe('marketSellOrdersWithEth', () => { diff --git a/contracts/integrations/test/forwarder/forwarder_test.ts b/contracts/integrations/test/forwarder/forwarder_test.ts index 3c414da4a3..a6b556d04a 100644 --- a/contracts/integrations/test/forwarder/forwarder_test.ts +++ b/contracts/integrations/test/forwarder/forwarder_test.ts @@ -106,7 +106,7 @@ blockchainTests('Forwarder integration tests', env => { }); after(async () => { - Actor.count = 0; + Actor.reset(); }); blockchainTests.resets('constructor', () => { @@ -511,20 +511,10 @@ blockchainTests('Forwarder integration tests', env => { // Compute expected balances const expectedBalances = LocalBalanceStore.create(balanceStore); - await expectedBalances.transferAssetAsync( - maker.address, - taker.address, - makerAssetFillAmount, - makerAssetData, - ); + expectedBalances.transferAsset(maker.address, taker.address, makerAssetFillAmount, makerAssetData); expectedBalances.wrapEth(taker.address, deployment.tokens.weth.address, ethValue); - await expectedBalances.transferAssetAsync( - taker.address, - maker.address, - primaryTakerAssetFillAmount, - wethAssetData, - ); - await expectedBalances.transferAssetAsync( + expectedBalances.transferAsset(taker.address, maker.address, primaryTakerAssetFillAmount, wethAssetData); + expectedBalances.transferAsset( taker.address, deployment.staking.stakingProxy.address, DeploymentManager.protocolFee, @@ -568,24 +558,14 @@ blockchainTests('Forwarder integration tests', env => { // Compute expected balances const expectedBalances = LocalBalanceStore.create(balanceStore); - await expectedBalances.transferAssetAsync( - maker.address, - taker.address, - makerAssetFillAmount, - makerAssetData, - ); + expectedBalances.transferAsset(maker.address, taker.address, makerAssetFillAmount, makerAssetData); expectedBalances.wrapEth( taker.address, deployment.tokens.weth.address, takerAssetFillAmount.plus(DeploymentManager.protocolFee), ); - await expectedBalances.transferAssetAsync( - taker.address, - maker.address, - takerAssetFillAmount, - wethAssetData, - ); - await expectedBalances.transferAssetAsync( + expectedBalances.transferAsset(taker.address, maker.address, takerAssetFillAmount, wethAssetData); + expectedBalances.transferAsset( taker.address, deployment.staking.stakingProxy.address, DeploymentManager.protocolFee, diff --git a/contracts/integrations/test/forwarder/forwarder_test_factory.ts b/contracts/integrations/test/forwarder/forwarder_test_factory.ts index 7d77641d4f..809255f3dd 100644 --- a/contracts/integrations/test/forwarder/forwarder_test_factory.ts +++ b/contracts/integrations/test/forwarder/forwarder_test_factory.ts @@ -207,7 +207,7 @@ export class ForwarderTestFactory { continue; } - const { wethSpentAmount, makerAssetAcquiredAmount } = await this._simulateSingleFillAsync( + const { wethSpentAmount, makerAssetAcquiredAmount } = this._simulateSingleFill( balances, order, ordersInfoBefore[i].orderTakerAssetFilledAmount, @@ -232,13 +232,13 @@ export class ForwarderTestFactory { return { ...currentTotal, balances }; } - private async _simulateSingleFillAsync( + private _simulateSingleFill( balances: LocalBalanceStore, order: SignedOrder, takerAssetFilled: BigNumber, fillFraction: number, bridgeExcessBuyAmount: BigNumber, - ): Promise { + ): ForwarderFillState { let { makerAssetAmount, takerAssetAmount, makerFee, takerFee } = order; makerAssetAmount = makerAssetAmount.times(fillFraction).integerValue(BigNumber.ROUND_CEIL); takerAssetAmount = takerAssetAmount.times(fillFraction).integerValue(BigNumber.ROUND_CEIL); @@ -272,43 +272,23 @@ export class ForwarderTestFactory { balances.wrapEth(this._forwarder.address, this._deployment.tokens.weth.address, wethSpentAmount); // (In reality this is done all at once, but we simulate it order by order) - // Maker -> Forwarder - await balances.transferAssetAsync( - order.makerAddress, - this._forwarder.address, - makerAssetAmount, - order.makerAssetData, - ); - // Maker -> Order fee recipient - await balances.transferAssetAsync( - order.makerAddress, - order.feeRecipientAddress, - makerFee, - order.makerFeeAssetData, - ); // Forwarder -> Maker - await balances.transferAssetAsync( - this._forwarder.address, - order.makerAddress, - takerAssetAmount, - order.takerAssetData, - ); + balances.transferAsset(this._forwarder.address, order.makerAddress, takerAssetAmount, order.takerAssetData); + // Maker -> Forwarder + balances.transferAsset(order.makerAddress, this._forwarder.address, makerAssetAmount, order.makerAssetData); // Forwarder -> Order fee recipient - await balances.transferAssetAsync( - this._forwarder.address, - order.feeRecipientAddress, - takerFee, - order.takerFeeAssetData, - ); + balances.transferAsset(this._forwarder.address, order.feeRecipientAddress, takerFee, order.takerFeeAssetData); + // Maker -> Order fee recipient + balances.transferAsset(order.makerAddress, order.feeRecipientAddress, makerFee, order.makerFeeAssetData); // Forwarder pays the protocol fee in WETH - await balances.transferAssetAsync( + balances.transferAsset( this._forwarder.address, this._deployment.staking.stakingProxy.address, DeploymentManager.protocolFee, order.takerAssetData, ); // Forwarder gives acquired maker asset to taker - await balances.transferAssetAsync( + balances.transferAsset( this._forwarder.address, this._taker.address, makerAssetAcquiredAmount, diff --git a/contracts/integrations/test/framework/actors/base.ts b/contracts/integrations/test/framework/actors/base.ts index 30766a9c7a..c73d15ec1c 100644 --- a/contracts/integrations/test/framework/actors/base.ts +++ b/contracts/integrations/test/framework/actors/base.ts @@ -31,6 +31,10 @@ export class Actor { } = {}; protected readonly _transactionFactory: TransactionFactory; + public static reset(): void { + Actor.count = 0; + } + constructor(config: ActorConfig) { Actor.count++; @@ -142,6 +146,12 @@ export class Actor { customTransactionParams: Partial, signatureType: SignatureType = SignatureType.EthSign, ): Promise { - return this._transactionFactory.newSignedTransactionAsync(customTransactionParams, signatureType); + return this._transactionFactory.newSignedTransactionAsync( + { + gasPrice: DeploymentManager.gasPrice, + ...customTransactionParams, + }, + signatureType, + ); } } diff --git a/contracts/integrations/test/framework/actors/maker.ts b/contracts/integrations/test/framework/actors/maker.ts index e9ebab6006..e701e29306 100644 --- a/contracts/integrations/test/framework/actors/maker.ts +++ b/contracts/integrations/test/framework/actors/maker.ts @@ -1,4 +1,4 @@ -import { constants, OrderFactory, orderUtils } from '@0x/contracts-test-utils'; +import { constants, OrderFactory } from '@0x/contracts-test-utils'; import { Order, SignedOrder } from '@0x/types'; import { TransactionReceiptWithDecodedLogs } from 'ethereum-types'; @@ -58,8 +58,7 @@ export function MakerMixin(Base: TBase): TBase & Cons * Cancels one of the maker's orders. */ public async cancelOrderAsync(order: SignedOrder): Promise { - const params = orderUtils.createCancel(order); - return this.actor.deployment.exchange.cancelOrder(params.order).awaitTransactionSuccessAsync({ + return this.actor.deployment.exchange.cancelOrder(order).awaitTransactionSuccessAsync({ from: this.actor.address, }); } diff --git a/contracts/integrations/test/framework/assertions/stake.ts b/contracts/integrations/test/framework/assertions/stake.ts index 9783a97092..2affd8a194 100644 --- a/contracts/integrations/test/framework/assertions/stake.ts +++ b/contracts/integrations/test/framework/assertions/stake.ts @@ -37,7 +37,7 @@ export function validStakeAssertion( before: async (amount: BigNumber, txData: Partial) => { // Simulates the transfer of ZRX from staker to vault const expectedBalances = LocalBalanceStore.create(balanceStore); - await expectedBalances.transferAssetAsync( + expectedBalances.transferAsset( txData.from as string, zrxVault.address, amount, diff --git a/contracts/integrations/test/framework/assertions/unstake.ts b/contracts/integrations/test/framework/assertions/unstake.ts index 5c3e26c193..5644ba5e39 100644 --- a/contracts/integrations/test/framework/assertions/unstake.ts +++ b/contracts/integrations/test/framework/assertions/unstake.ts @@ -37,7 +37,7 @@ export function validUnstakeAssertion( before: async (amount: BigNumber, txData: Partial) => { // Simulates the transfer of ZRX from vault to staker const expectedBalances = LocalBalanceStore.create(balanceStore); - await expectedBalances.transferAssetAsync( + expectedBalances.transferAsset( zrxVault.address, txData.from as string, amount, diff --git a/contracts/integrations/test/framework/balances/local_balance_store.ts b/contracts/integrations/test/framework/balances/local_balance_store.ts index 235e418f72..2828408cce 100644 --- a/contracts/integrations/test/framework/balances/local_balance_store.ts +++ b/contracts/integrations/test/framework/balances/local_balance_store.ts @@ -1,9 +1,13 @@ import { IAssetDataContract } from '@0x/contracts-asset-proxy'; +import { ReferenceFunctions } from '@0x/contracts-exchange-libs'; import { constants, hexSlice, Numberish, provider } from '@0x/contracts-test-utils'; -import { AssetProxyId } from '@0x/types'; +import { AssetProxyId, SignedOrder } from '@0x/types'; import { BigNumber } from '@0x/utils'; +import { TransactionReceiptWithDecodedLogs } from 'ethereum-types'; import * as _ from 'lodash'; +import { DeploymentManager } from '../deployment_manager'; + import { BalanceStore } from './balance_store'; import { TokenContractsByName, TokenOwnersByName } from './types'; @@ -74,12 +78,7 @@ export class LocalBalanceStore extends BalanceStore { * @param amount Amount of asset(s) to transfer * @param assetData Asset data of assets being transferred. */ - public async transferAssetAsync( - fromAddress: string, - toAddress: string, - amount: BigNumber, - assetData: string, - ): Promise { + public transferAsset(fromAddress: string, toAddress: string, amount: BigNumber, assetData: string): void { if (fromAddress === toAddress || amount.isZero()) { return; } @@ -174,7 +173,7 @@ export class LocalBalanceStore extends BalanceStore { >('MultiAsset', assetData); for (const [i, amt] of amounts.entries()) { const nestedAmount = amount.times(amt); - await this.transferAssetAsync(fromAddress, toAddress, nestedAmount, nestedAssetData[i]); + this.transferAsset(fromAddress, toAddress, nestedAmount, nestedAssetData[i]); } break; } @@ -185,4 +184,71 @@ export class LocalBalanceStore extends BalanceStore { throw new Error(`Unhandled asset proxy ID: ${assetProxyId}`); } } + + public simulateFills( + orders: SignedOrder[], + takerAddresses: string[] | string, + txReceipt: TransactionReceiptWithDecodedLogs, + deployment: DeploymentManager, + msgValue: BigNumber = constants.ZERO_AMOUNT, + takerAssetFillAmounts?: BigNumber[], + ): void { + let remainingValue = msgValue; + // Transaction gas cost + this.burnGas(txReceipt.from, DeploymentManager.gasPrice.times(txReceipt.gasUsed)); + + for (const [index, order] of orders.entries()) { + const takerAddress = Array.isArray(takerAddresses) ? takerAddresses[index] : takerAddresses; + const fillResults = ReferenceFunctions.calculateFillResults( + order, + takerAssetFillAmounts ? takerAssetFillAmounts[index] : order.takerAssetAmount, + DeploymentManager.protocolFeeMultiplier, + DeploymentManager.gasPrice, + ); + + // Taker -> Maker + this.transferAsset( + takerAddress, + order.makerAddress, + fillResults.takerAssetFilledAmount, + order.takerAssetData, + ); + // Maker -> Taker + this.transferAsset( + order.makerAddress, + takerAddress, + fillResults.makerAssetFilledAmount, + order.makerAssetData, + ); + // Taker -> Fee Recipient + this.transferAsset( + takerAddress, + order.feeRecipientAddress, + fillResults.takerFeePaid, + order.takerFeeAssetData, + ); + // Maker -> Fee Recipient + this.transferAsset( + order.makerAddress, + order.feeRecipientAddress, + fillResults.makerFeePaid, + order.makerFeeAssetData, + ); + + // Protocol fee + if (remainingValue.isGreaterThanOrEqualTo(fillResults.protocolFeePaid)) { + this.sendEth(txReceipt.from, deployment.staking.stakingProxy.address, fillResults.protocolFeePaid); + remainingValue = remainingValue.minus(fillResults.protocolFeePaid); + } else { + this.transferAsset( + takerAddress, + deployment.staking.stakingProxy.address, + fillResults.protocolFeePaid, + deployment.assetDataEncoder + .ERC20Token(deployment.tokens.weth.address) + .getABIEncodedTransactionData(), + ); + } + } + } } diff --git a/contracts/integrations/test/fuzz_tests/pool_management_test.ts b/contracts/integrations/test/fuzz_tests/pool_management_test.ts index 51dd018257..69b69eb912 100644 --- a/contracts/integrations/test/fuzz_tests/pool_management_test.ts +++ b/contracts/integrations/test/fuzz_tests/pool_management_test.ts @@ -30,7 +30,7 @@ export class PoolManagementSimulation extends Simulation { blockchainTests.skip('Pool management fuzz test', env => { after(async () => { - Actor.count = 0; + Actor.reset(); }); it('fuzz', async () => { diff --git a/contracts/integrations/test/fuzz_tests/stake_management_test.ts b/contracts/integrations/test/fuzz_tests/stake_management_test.ts index 2bcebe9171..7bf770dc8d 100644 --- a/contracts/integrations/test/fuzz_tests/stake_management_test.ts +++ b/contracts/integrations/test/fuzz_tests/stake_management_test.ts @@ -34,7 +34,7 @@ export class StakeManagementSimulation extends Simulation { blockchainTests.skip('Stake management fuzz test', env => { after(async () => { - Actor.count = 0; + Actor.reset(); }); it('fuzz', async () => { diff --git a/packages/base-contract/src/utils.ts b/packages/base-contract/src/utils.ts index 0de13ea2c0..91f5b9579e 100644 --- a/packages/base-contract/src/utils.ts +++ b/packages/base-contract/src/utils.ts @@ -1,3 +1,4 @@ +import { AbiEncoder } from '@0x/utils'; import { DataItem, MethodAbi } from 'ethereum-types'; // tslint:disable-next-line:completed-docs @@ -29,21 +30,11 @@ export function formatABIDataItem(abi: DataItem, value: any, formatter: (type: s } } -function dataItemsToABIString(dataItems: DataItem[]): string { - const types = dataItems.map(item => { - if (item.components) { - return `(${dataItemsToABIString(item.components)})`; - } else { - return item.type; - } - }); - return `${types.join(',')}`; -} /** * Takes a MethodAbi and returns a function signature for ABI encoding/decoding * @return a function signature as a string, e.g. 'functionName(uint256, bytes[])' */ export function methodAbiToFunctionSignature(methodAbi: MethodAbi): string { - const inputs = dataItemsToABIString(methodAbi.inputs); - return `${methodAbi.name}(${inputs})`; + const method = AbiEncoder.createMethod(methodAbi.name, methodAbi.inputs); + return method.getSignature(); }