diff options
author | Amir Bandeali <abandeali1@gmail.com> | 2019-01-21 13:21:44 +0800 |
---|---|---|
committer | Amir Bandeali <abandeali1@gmail.com> | 2019-01-22 13:41:21 +0800 |
commit | 4a4c26a2e3a64f52f602c998095cf5d164ec0276 (patch) | |
tree | 5b3a50f7c6ec3cdb52e80cf779a5e94ee58d331b /contracts/protocol/test/exchange | |
parent | e2fe907de0e326e826e8ef3010efac8ec0136c74 (diff) | |
download | dexon-0x-contracts-4a4c26a2e3a64f52f602c998095cf5d164ec0276.tar dexon-0x-contracts-4a4c26a2e3a64f52f602c998095cf5d164ec0276.tar.gz dexon-0x-contracts-4a4c26a2e3a64f52f602c998095cf5d164ec0276.tar.bz2 dexon-0x-contracts-4a4c26a2e3a64f52f602c998095cf5d164ec0276.tar.lz dexon-0x-contracts-4a4c26a2e3a64f52f602c998095cf5d164ec0276.tar.xz dexon-0x-contracts-4a4c26a2e3a64f52f602c998095cf5d164ec0276.tar.zst dexon-0x-contracts-4a4c26a2e3a64f52f602c998095cf5d164ec0276.zip |
Split protocol package into exchange, asset-proxy, and multisig
Diffstat (limited to 'contracts/protocol/test/exchange')
-rw-r--r-- | contracts/protocol/test/exchange/core.ts | 1157 | ||||
-rw-r--r-- | contracts/protocol/test/exchange/dispatcher.ts | 284 | ||||
-rw-r--r-- | contracts/protocol/test/exchange/fill_order.ts | 314 | ||||
-rw-r--r-- | contracts/protocol/test/exchange/internal.ts | 472 | ||||
-rw-r--r-- | contracts/protocol/test/exchange/match_orders.ts | 1284 | ||||
-rw-r--r-- | contracts/protocol/test/exchange/signature_validator.ts | 526 | ||||
-rw-r--r-- | contracts/protocol/test/exchange/transactions.ts | 466 | ||||
-rw-r--r-- | contracts/protocol/test/exchange/wrapper.ts | 1461 |
8 files changed, 0 insertions, 5964 deletions
diff --git a/contracts/protocol/test/exchange/core.ts b/contracts/protocol/test/exchange/core.ts deleted file mode 100644 index f88be1c94..000000000 --- a/contracts/protocol/test/exchange/core.ts +++ /dev/null @@ -1,1157 +0,0 @@ -import { - chaiSetup, - constants, - ERC20BalancesByOwner, - expectTransactionFailedAsync, - getLatestBlockTimestampAsync, - increaseTimeAndMineBlockAsync, - OrderFactory, - OrderStatus, - provider, - txDefaults, - web3Wrapper, -} from '@0x/contracts-test-utils'; -import { - artifacts as tokensArtifacts, - DummyERC20TokenContract, - DummyERC20TokenTransferEventArgs, - DummyERC721TokenContract, - DummyNoReturnERC20TokenContract, - ReentrantERC20TokenContract, -} from '@0x/contracts-tokens'; -import { BlockchainLifecycle } from '@0x/dev-utils'; -import { assetDataUtils, orderHashUtils } from '@0x/order-utils'; -import { RevertReason, SignatureType, SignedOrder } from '@0x/types'; -import { BigNumber } from '@0x/utils'; -import { Web3Wrapper } from '@0x/web3-wrapper'; -import * as chai from 'chai'; -import { LogWithDecodedArgs } from 'ethereum-types'; -import ethUtil = require('ethereumjs-util'); -import * as _ from 'lodash'; - -import { ERC20ProxyContract } from '../../generated-wrappers/erc20_proxy'; -import { ERC721ProxyContract } from '../../generated-wrappers/erc721_proxy'; -import { ExchangeCancelEventArgs, ExchangeContract } from '../../generated-wrappers/exchange'; -import { MultiAssetProxyContract } from '../../generated-wrappers/multi_asset_proxy'; -import { TestStaticCallReceiverContract } from '../../generated-wrappers/test_static_call_receiver'; -import { artifacts } from '../../src/artifacts'; -import { ERC20Wrapper } from '../utils/erc20_wrapper'; -import { ERC721Wrapper } from '../utils/erc721_wrapper'; -import { ExchangeWrapper } from '../utils/exchange_wrapper'; - -chaiSetup.configure(); -const expect = chai.expect; -const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); -// tslint:disable:no-unnecessary-type-assertion -describe('Exchange core', () => { - let makerAddress: string; - let owner: string; - let takerAddress: string; - let feeRecipientAddress: string; - - let erc20TokenA: DummyERC20TokenContract; - let erc20TokenB: DummyERC20TokenContract; - let zrxToken: DummyERC20TokenContract; - let erc721Token: DummyERC721TokenContract; - let noReturnErc20Token: DummyNoReturnERC20TokenContract; - let reentrantErc20Token: ReentrantERC20TokenContract; - let exchange: ExchangeContract; - let erc20Proxy: ERC20ProxyContract; - let erc721Proxy: ERC721ProxyContract; - let multiAssetProxy: MultiAssetProxyContract; - let maliciousWallet: TestStaticCallReceiverContract; - let maliciousValidator: TestStaticCallReceiverContract; - - let signedOrder: SignedOrder; - let erc20Balances: ERC20BalancesByOwner; - let exchangeWrapper: ExchangeWrapper; - let erc20Wrapper: ERC20Wrapper; - let erc721Wrapper: ERC721Wrapper; - let orderFactory: OrderFactory; - - let erc721MakerAssetIds: BigNumber[]; - let erc721TakerAssetIds: BigNumber[]; - - let defaultMakerAssetAddress: string; - let defaultTakerAssetAddress: string; - - before(async () => { - await blockchainLifecycle.startAsync(); - }); - after(async () => { - await blockchainLifecycle.revertAsync(); - }); - before(async () => { - const accounts = await web3Wrapper.getAvailableAddressesAsync(); - const usedAddresses = ([owner, makerAddress, takerAddress, feeRecipientAddress] = _.slice(accounts, 0, 4)); - - erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner); - erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner); - - // Deploy AssetProxies, Exchange, tokens, and malicious contracts - erc20Proxy = await erc20Wrapper.deployProxyAsync(); - erc721Proxy = await erc721Wrapper.deployProxyAsync(); - multiAssetProxy = await MultiAssetProxyContract.deployFrom0xArtifactAsync( - artifacts.MultiAssetProxy, - provider, - txDefaults, - ); - const numDummyErc20ToDeploy = 3; - [erc20TokenA, erc20TokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync( - numDummyErc20ToDeploy, - constants.DUMMY_TOKEN_DECIMALS, - ); - [erc721Token] = await erc721Wrapper.deployDummyTokensAsync(); - exchange = await ExchangeContract.deployFrom0xArtifactAsync( - artifacts.Exchange, - provider, - txDefaults, - assetDataUtils.encodeERC20AssetData(zrxToken.address), - ); - maliciousWallet = maliciousValidator = await TestStaticCallReceiverContract.deployFrom0xArtifactAsync( - artifacts.TestStaticCallReceiver, - provider, - txDefaults, - ); - reentrantErc20Token = await ReentrantERC20TokenContract.deployFrom0xArtifactAsync( - tokensArtifacts.ReentrantERC20Token, - provider, - txDefaults, - exchange.address, - ); - - // Configure ERC20Proxy - await web3Wrapper.awaitTransactionSuccessAsync( - await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(exchange.address, { - from: owner, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(multiAssetProxy.address, { - from: owner, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - - // Configure ERC721Proxy - await web3Wrapper.awaitTransactionSuccessAsync( - await erc721Proxy.addAuthorizedAddress.sendTransactionAsync(exchange.address, { - from: owner, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await erc721Proxy.addAuthorizedAddress.sendTransactionAsync(multiAssetProxy.address, { - from: owner, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - - // Configure MultiAssetProxy - await web3Wrapper.awaitTransactionSuccessAsync( - await multiAssetProxy.addAuthorizedAddress.sendTransactionAsync(exchange.address, { - from: owner, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await multiAssetProxy.registerAssetProxy.sendTransactionAsync(erc20Proxy.address, { - from: owner, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await multiAssetProxy.registerAssetProxy.sendTransactionAsync(erc721Proxy.address, { - from: owner, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - - // Configure Exchange - exchangeWrapper = new ExchangeWrapper(exchange, provider); - await exchangeWrapper.registerAssetProxyAsync(erc20Proxy.address, owner); - await exchangeWrapper.registerAssetProxyAsync(erc721Proxy.address, owner); - await exchangeWrapper.registerAssetProxyAsync(multiAssetProxy.address, owner); - - // Configure ERC20 tokens - await erc20Wrapper.setBalancesAndAllowancesAsync(); - - // Configure ERC721 tokens - await erc721Wrapper.setBalancesAndAllowancesAsync(); - const erc721Balances = await erc721Wrapper.getBalancesAsync(); - erc721MakerAssetIds = erc721Balances[makerAddress][erc721Token.address]; - erc721TakerAssetIds = erc721Balances[takerAddress][erc721Token.address]; - - // Configure order defaults - defaultMakerAssetAddress = erc20TokenA.address; - defaultTakerAssetAddress = erc20TokenB.address; - const defaultOrderParams = { - ...constants.STATIC_ORDER_PARAMS, - exchangeAddress: exchange.address, - makerAddress, - feeRecipientAddress, - makerAssetData: assetDataUtils.encodeERC20AssetData(defaultMakerAssetAddress), - takerAssetData: assetDataUtils.encodeERC20AssetData(defaultTakerAssetAddress), - }; - const privateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddress)]; - orderFactory = new OrderFactory(privateKey, defaultOrderParams); - }); - beforeEach(async () => { - await blockchainLifecycle.startAsync(); - }); - afterEach(async () => { - await blockchainLifecycle.revertAsync(); - }); - describe('fillOrder', () => { - beforeEach(async () => { - erc20Balances = await erc20Wrapper.getBalancesAsync(); - signedOrder = await orderFactory.newSignedOrderAsync(); - }); - - const reentrancyTest = (functionNames: string[]) => { - _.forEach(functionNames, async (functionName: string, functionId: number) => { - const description = `should not allow fillOrder to reenter the Exchange contract via ${functionName}`; - it(description, async () => { - signedOrder = await orderFactory.newSignedOrderAsync({ - makerAssetData: assetDataUtils.encodeERC20AssetData(reentrantErc20Token.address), - }); - await web3Wrapper.awaitTransactionSuccessAsync( - await reentrantErc20Token.setCurrentFunction.sendTransactionAsync(functionId), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await expectTransactionFailedAsync( - exchangeWrapper.fillOrderAsync(signedOrder, takerAddress), - RevertReason.TransferFailed, - ); - }); - }); - }; - describe('fillOrder reentrancy tests', () => reentrancyTest(constants.FUNCTIONS_WITH_MUTEX)); - - it('should throw if signature is invalid', async () => { - signedOrder = await orderFactory.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - }); - - const v = ethUtil.toBuffer(signedOrder.signature.slice(0, 4)); - const invalidR = ethUtil.sha3('invalidR'); - const invalidS = ethUtil.sha3('invalidS'); - const signatureType = ethUtil.toBuffer(`0x${signedOrder.signature.slice(-2)}`); - const invalidSigBuff = Buffer.concat([v, invalidR, invalidS, signatureType]); - const invalidSigHex = `0x${invalidSigBuff.toString('hex')}`; - signedOrder.signature = invalidSigHex; - return expectTransactionFailedAsync( - exchangeWrapper.fillOrderAsync(signedOrder, takerAddress), - RevertReason.InvalidOrderSignature, - ); - }); - - it('should throw if no value is filled', async () => { - signedOrder = await orderFactory.newSignedOrderAsync(); - await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress); - return expectTransactionFailedAsync( - exchangeWrapper.fillOrderAsync(signedOrder, takerAddress), - RevertReason.OrderUnfillable, - ); - }); - - it('should revert if `isValidSignature` tries to update state when SignatureType=Wallet', async () => { - const maliciousMakerAddress = maliciousWallet.address; - await web3Wrapper.awaitTransactionSuccessAsync( - await erc20TokenA.setBalance.sendTransactionAsync( - maliciousMakerAddress, - constants.INITIAL_ERC20_BALANCE, - ), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await maliciousWallet.approveERC20.sendTransactionAsync( - erc20TokenA.address, - erc20Proxy.address, - constants.INITIAL_ERC20_ALLOWANCE, - ), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - signedOrder = await orderFactory.newSignedOrderAsync({ - makerAddress: maliciousMakerAddress, - makerFee: constants.ZERO_AMOUNT, - }); - signedOrder.signature = `0x0${SignatureType.Wallet}`; - await expectTransactionFailedAsync( - exchangeWrapper.fillOrderAsync(signedOrder, takerAddress), - RevertReason.WalletError, - ); - }); - - it('should revert if `isValidSignature` tries to update state when SignatureType=Validator', async () => { - const isApproved = true; - await web3Wrapper.awaitTransactionSuccessAsync( - await exchange.setSignatureValidatorApproval.sendTransactionAsync( - maliciousValidator.address, - isApproved, - { from: makerAddress }, - ), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - signedOrder.signature = `${maliciousValidator.address}0${SignatureType.Validator}`; - await expectTransactionFailedAsync( - exchangeWrapper.fillOrderAsync(signedOrder, takerAddress), - RevertReason.ValidatorError, - ); - }); - - it('should not emit transfer events for transfers where from == to', async () => { - const txReceipt = await exchangeWrapper.fillOrderAsync(signedOrder, makerAddress); - const logs = txReceipt.logs; - const transferLogs = _.filter( - logs, - log => (log as LogWithDecodedArgs<DummyERC20TokenTransferEventArgs>).event === 'Transfer', - ); - expect(transferLogs.length).to.be.equal(2); - expect((transferLogs[0] as LogWithDecodedArgs<DummyERC20TokenTransferEventArgs>).address).to.be.equal( - zrxToken.address, - ); - expect((transferLogs[0] as LogWithDecodedArgs<DummyERC20TokenTransferEventArgs>).args._from).to.be.equal( - makerAddress, - ); - expect((transferLogs[0] as LogWithDecodedArgs<DummyERC20TokenTransferEventArgs>).args._to).to.be.equal( - feeRecipientAddress, - ); - expect( - (transferLogs[0] as LogWithDecodedArgs<DummyERC20TokenTransferEventArgs>).args._value, - ).to.be.bignumber.equal(signedOrder.makerFee); - expect((transferLogs[1] as LogWithDecodedArgs<DummyERC20TokenTransferEventArgs>).address).to.be.equal( - zrxToken.address, - ); - expect((transferLogs[1] as LogWithDecodedArgs<DummyERC20TokenTransferEventArgs>).args._from).to.be.equal( - makerAddress, - ); - expect((transferLogs[1] as LogWithDecodedArgs<DummyERC20TokenTransferEventArgs>).args._to).to.be.equal( - feeRecipientAddress, - ); - expect( - (transferLogs[1] as LogWithDecodedArgs<DummyERC20TokenTransferEventArgs>).args._value, - ).to.be.bignumber.equal(signedOrder.takerFee); - }); - }); - - describe('Testing exchange of ERC20 tokens with no return values', () => { - before(async () => { - noReturnErc20Token = await DummyNoReturnERC20TokenContract.deployFrom0xArtifactAsync( - tokensArtifacts.DummyNoReturnERC20Token, - provider, - txDefaults, - constants.DUMMY_TOKEN_NAME, - constants.DUMMY_TOKEN_SYMBOL, - constants.DUMMY_TOKEN_DECIMALS, - constants.DUMMY_TOKEN_TOTAL_SUPPLY, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await noReturnErc20Token.setBalance.sendTransactionAsync(makerAddress, constants.INITIAL_ERC20_BALANCE), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await noReturnErc20Token.approve.sendTransactionAsync( - erc20Proxy.address, - constants.INITIAL_ERC20_ALLOWANCE, - { from: makerAddress }, - ), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - }); - it('should transfer the correct amounts when makerAssetAmount === takerAssetAmount', async () => { - signedOrder = await orderFactory.newSignedOrderAsync({ - makerAssetData: assetDataUtils.encodeERC20AssetData(noReturnErc20Token.address), - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18), - }); - - const initialMakerBalanceA = await noReturnErc20Token.balanceOf.callAsync(makerAddress); - const initialMakerBalanceB = await erc20TokenB.balanceOf.callAsync(makerAddress); - const initialMakerZrxBalance = await zrxToken.balanceOf.callAsync(makerAddress); - const initialTakerBalanceA = await noReturnErc20Token.balanceOf.callAsync(takerAddress); - const initialTakerBalanceB = await erc20TokenB.balanceOf.callAsync(takerAddress); - const initialTakerZrxBalance = await zrxToken.balanceOf.callAsync(takerAddress); - const initialFeeRecipientZrxBalance = await zrxToken.balanceOf.callAsync(feeRecipientAddress); - - await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress); - - const finalMakerBalanceA = await noReturnErc20Token.balanceOf.callAsync(makerAddress); - const finalMakerBalanceB = await erc20TokenB.balanceOf.callAsync(makerAddress); - const finalMakerZrxBalance = await zrxToken.balanceOf.callAsync(makerAddress); - const finalTakerBalanceA = await noReturnErc20Token.balanceOf.callAsync(takerAddress); - const finalTakerBalanceB = await erc20TokenB.balanceOf.callAsync(takerAddress); - const finalTakerZrxBalance = await zrxToken.balanceOf.callAsync(takerAddress); - const finalFeeRecipientZrxBalance = await zrxToken.balanceOf.callAsync(feeRecipientAddress); - - expect(finalMakerBalanceA).to.be.bignumber.equal(initialMakerBalanceA.minus(signedOrder.makerAssetAmount)); - expect(finalMakerBalanceB).to.be.bignumber.equal(initialMakerBalanceB.plus(signedOrder.takerAssetAmount)); - expect(finalTakerBalanceA).to.be.bignumber.equal(initialTakerBalanceA.plus(signedOrder.makerAssetAmount)); - expect(finalTakerBalanceB).to.be.bignumber.equal(initialTakerBalanceB.minus(signedOrder.takerAssetAmount)); - expect(finalMakerZrxBalance).to.be.bignumber.equal(initialMakerZrxBalance.minus(signedOrder.makerFee)); - expect(finalTakerZrxBalance).to.be.bignumber.equal(initialTakerZrxBalance.minus(signedOrder.takerFee)); - expect(finalFeeRecipientZrxBalance).to.be.bignumber.equal( - initialFeeRecipientZrxBalance.plus(signedOrder.makerFee.plus(signedOrder.takerFee)), - ); - }); - it('should transfer the correct amounts when makerAssetAmount > takerAssetAmount', async () => { - signedOrder = await orderFactory.newSignedOrderAsync({ - makerAssetData: assetDataUtils.encodeERC20AssetData(noReturnErc20Token.address), - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(200), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18), - }); - - const initialMakerBalanceA = await noReturnErc20Token.balanceOf.callAsync(makerAddress); - const initialMakerBalanceB = await erc20TokenB.balanceOf.callAsync(makerAddress); - const initialMakerZrxBalance = await zrxToken.balanceOf.callAsync(makerAddress); - const initialTakerBalanceA = await noReturnErc20Token.balanceOf.callAsync(takerAddress); - const initialTakerBalanceB = await erc20TokenB.balanceOf.callAsync(takerAddress); - const initialTakerZrxBalance = await zrxToken.balanceOf.callAsync(takerAddress); - const initialFeeRecipientZrxBalance = await zrxToken.balanceOf.callAsync(feeRecipientAddress); - - await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress); - - const finalMakerBalanceA = await noReturnErc20Token.balanceOf.callAsync(makerAddress); - const finalMakerBalanceB = await erc20TokenB.balanceOf.callAsync(makerAddress); - const finalMakerZrxBalance = await zrxToken.balanceOf.callAsync(makerAddress); - const finalTakerBalanceA = await noReturnErc20Token.balanceOf.callAsync(takerAddress); - const finalTakerBalanceB = await erc20TokenB.balanceOf.callAsync(takerAddress); - const finalTakerZrxBalance = await zrxToken.balanceOf.callAsync(takerAddress); - const finalFeeRecipientZrxBalance = await zrxToken.balanceOf.callAsync(feeRecipientAddress); - - expect(finalMakerBalanceA).to.be.bignumber.equal(initialMakerBalanceA.minus(signedOrder.makerAssetAmount)); - expect(finalMakerBalanceB).to.be.bignumber.equal(initialMakerBalanceB.plus(signedOrder.takerAssetAmount)); - expect(finalTakerBalanceA).to.be.bignumber.equal(initialTakerBalanceA.plus(signedOrder.makerAssetAmount)); - expect(finalTakerBalanceB).to.be.bignumber.equal(initialTakerBalanceB.minus(signedOrder.takerAssetAmount)); - expect(finalMakerZrxBalance).to.be.bignumber.equal(initialMakerZrxBalance.minus(signedOrder.makerFee)); - expect(finalTakerZrxBalance).to.be.bignumber.equal(initialTakerZrxBalance.minus(signedOrder.takerFee)); - expect(finalFeeRecipientZrxBalance).to.be.bignumber.equal( - initialFeeRecipientZrxBalance.plus(signedOrder.makerFee.plus(signedOrder.takerFee)), - ); - }); - it('should transfer the correct amounts when makerAssetAmount < takerAssetAmount', async () => { - signedOrder = await orderFactory.newSignedOrderAsync({ - makerAssetData: assetDataUtils.encodeERC20AssetData(noReturnErc20Token.address), - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(200), 18), - }); - - const initialMakerBalanceA = await noReturnErc20Token.balanceOf.callAsync(makerAddress); - const initialMakerBalanceB = await erc20TokenB.balanceOf.callAsync(makerAddress); - const initialMakerZrxBalance = await zrxToken.balanceOf.callAsync(makerAddress); - const initialTakerBalanceA = await noReturnErc20Token.balanceOf.callAsync(takerAddress); - const initialTakerBalanceB = await erc20TokenB.balanceOf.callAsync(takerAddress); - const initialTakerZrxBalance = await zrxToken.balanceOf.callAsync(takerAddress); - const initialFeeRecipientZrxBalance = await zrxToken.balanceOf.callAsync(feeRecipientAddress); - - await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress); - - const finalMakerBalanceA = await noReturnErc20Token.balanceOf.callAsync(makerAddress); - const finalMakerBalanceB = await erc20TokenB.balanceOf.callAsync(makerAddress); - const finalMakerZrxBalance = await zrxToken.balanceOf.callAsync(makerAddress); - const finalTakerBalanceA = await noReturnErc20Token.balanceOf.callAsync(takerAddress); - const finalTakerBalanceB = await erc20TokenB.balanceOf.callAsync(takerAddress); - const finalTakerZrxBalance = await zrxToken.balanceOf.callAsync(takerAddress); - const finalFeeRecipientZrxBalance = await zrxToken.balanceOf.callAsync(feeRecipientAddress); - - expect(finalMakerBalanceA).to.be.bignumber.equal(initialMakerBalanceA.minus(signedOrder.makerAssetAmount)); - expect(finalMakerBalanceB).to.be.bignumber.equal(initialMakerBalanceB.plus(signedOrder.takerAssetAmount)); - expect(finalTakerBalanceA).to.be.bignumber.equal(initialTakerBalanceA.plus(signedOrder.makerAssetAmount)); - expect(finalTakerBalanceB).to.be.bignumber.equal(initialTakerBalanceB.minus(signedOrder.takerAssetAmount)); - expect(finalMakerZrxBalance).to.be.bignumber.equal(initialMakerZrxBalance.minus(signedOrder.makerFee)); - expect(finalTakerZrxBalance).to.be.bignumber.equal(initialTakerZrxBalance.minus(signedOrder.takerFee)); - expect(finalFeeRecipientZrxBalance).to.be.bignumber.equal( - initialFeeRecipientZrxBalance.plus(signedOrder.makerFee.plus(signedOrder.takerFee)), - ); - }); - }); - - describe('cancelOrder', () => { - beforeEach(async () => { - erc20Balances = await erc20Wrapper.getBalancesAsync(); - signedOrder = await orderFactory.newSignedOrderAsync(); - }); - - it('should throw if not sent by maker', async () => { - return expectTransactionFailedAsync( - exchangeWrapper.cancelOrderAsync(signedOrder, takerAddress), - RevertReason.InvalidMaker, - ); - }); - - it('should throw if makerAssetAmount is 0', async () => { - signedOrder = await orderFactory.newSignedOrderAsync({ - makerAssetAmount: new BigNumber(0), - }); - - return expectTransactionFailedAsync( - exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress), - RevertReason.OrderUnfillable, - ); - }); - - it('should throw if takerAssetAmount is 0', async () => { - signedOrder = await orderFactory.newSignedOrderAsync({ - takerAssetAmount: new BigNumber(0), - }); - - return expectTransactionFailedAsync( - exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress), - RevertReason.OrderUnfillable, - ); - }); - - it('should be able to cancel a full order', async () => { - await exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress); - return expectTransactionFailedAsync( - exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { - takerAssetFillAmount: signedOrder.takerAssetAmount.div(2), - }), - RevertReason.OrderUnfillable, - ); - }); - - it('should log 1 event with correct arguments', async () => { - const res = await exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress); - expect(res.logs).to.have.length(1); - - const log = res.logs[0] as LogWithDecodedArgs<ExchangeCancelEventArgs>; - const logArgs = log.args; - - expect(signedOrder.makerAddress).to.be.equal(logArgs.makerAddress); - expect(signedOrder.makerAddress).to.be.equal(logArgs.senderAddress); - expect(signedOrder.feeRecipientAddress).to.be.equal(logArgs.feeRecipientAddress); - expect(signedOrder.makerAssetData).to.be.equal(logArgs.makerAssetData); - expect(signedOrder.takerAssetData).to.be.equal(logArgs.takerAssetData); - expect(orderHashUtils.getOrderHashHex(signedOrder)).to.be.equal(logArgs.orderHash); - }); - - it('should throw if already cancelled', async () => { - await exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress); - return expectTransactionFailedAsync( - exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress), - RevertReason.OrderUnfillable, - ); - }); - - it('should throw if order is expired', async () => { - const currentTimestamp = await getLatestBlockTimestampAsync(); - signedOrder = await orderFactory.newSignedOrderAsync({ - expirationTimeSeconds: new BigNumber(currentTimestamp).minus(10), - }); - return expectTransactionFailedAsync( - exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress), - RevertReason.OrderUnfillable, - ); - }); - - it('should throw if rounding error is greater than 0.1%', async () => { - signedOrder = await orderFactory.newSignedOrderAsync({ - makerAssetAmount: new BigNumber(1001), - takerAssetAmount: new BigNumber(3), - }); - - const fillTakerAssetAmount1 = new BigNumber(2); - await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { - takerAssetFillAmount: fillTakerAssetAmount1, - }); - - const fillTakerAssetAmount2 = new BigNumber(1); - return expectTransactionFailedAsync( - exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { - takerAssetFillAmount: fillTakerAssetAmount2, - }), - RevertReason.RoundingError, - ); - }); - }); - - describe('cancelOrdersUpTo', () => { - it('should fail to set orderEpoch less than current orderEpoch', async () => { - const orderEpoch = new BigNumber(1); - await exchangeWrapper.cancelOrdersUpToAsync(orderEpoch, makerAddress); - const lesserOrderEpoch = new BigNumber(0); - return expectTransactionFailedAsync( - exchangeWrapper.cancelOrdersUpToAsync(lesserOrderEpoch, makerAddress), - RevertReason.InvalidNewOrderEpoch, - ); - }); - - it('should fail to set orderEpoch equal to existing orderEpoch', async () => { - const orderEpoch = new BigNumber(1); - await exchangeWrapper.cancelOrdersUpToAsync(orderEpoch, makerAddress); - return expectTransactionFailedAsync( - exchangeWrapper.cancelOrdersUpToAsync(orderEpoch, makerAddress), - RevertReason.InvalidNewOrderEpoch, - ); - }); - - it('should cancel only orders with a orderEpoch less than existing orderEpoch', async () => { - // Cancel all transactions with a orderEpoch less than 1 - const orderEpoch = new BigNumber(1); - await exchangeWrapper.cancelOrdersUpToAsync(orderEpoch, makerAddress); - - // Create 3 orders with orderEpoch values: 0,1,2,3 - // Since we cancelled with orderEpoch=1, orders with orderEpoch<=1 will not be processed - erc20Balances = await erc20Wrapper.getBalancesAsync(); - const signedOrders = [ - await orderFactory.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(9), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(9), 18), - salt: new BigNumber(0), - }), - await orderFactory.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(79), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(79), 18), - salt: new BigNumber(1), - }), - await orderFactory.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(979), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(979), 18), - salt: new BigNumber(2), - }), - await orderFactory.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(7979), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(7979), 18), - salt: new BigNumber(3), - }), - ]; - await exchangeWrapper.batchFillOrdersNoThrowAsync(signedOrders, takerAddress, { - // HACK(albrow): We need to hardcode the gas estimate here because - // the Geth gas estimator doesn't work with the way we use - // delegatecall and swallow errors. - gas: 600000, - }); - - const newBalances = await erc20Wrapper.getBalancesAsync(); - const fillMakerAssetAmount = signedOrders[2].makerAssetAmount.plus(signedOrders[3].makerAssetAmount); - const fillTakerAssetAmount = signedOrders[2].takerAssetAmount.plus(signedOrders[3].takerAssetAmount); - const makerFee = signedOrders[2].makerFee.plus(signedOrders[3].makerFee); - const takerFee = signedOrders[2].takerFee.plus(signedOrders[3].takerFee); - expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultMakerAssetAddress].minus(fillMakerAssetAmount), - ); - expect(newBalances[makerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultTakerAssetAddress].plus(fillTakerAssetAmount), - ); - expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[makerAddress][zrxToken.address].minus(makerFee), - ); - expect(newBalances[takerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultTakerAssetAddress].minus(fillTakerAssetAmount), - ); - expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultMakerAssetAddress].plus(fillMakerAssetAmount), - ); - expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[takerAddress][zrxToken.address].minus(takerFee), - ); - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].plus(makerFee.plus(takerFee)), - ); - }); - }); - - describe('Testing Exchange of ERC721 Tokens', () => { - it('should throw when maker does not own the token with id makerAssetId', async () => { - // Construct Exchange parameters - const makerAssetId = erc721TakerAssetIds[0]; - const takerAssetId = erc721TakerAssetIds[1]; - signedOrder = await orderFactory.newSignedOrderAsync({ - makerAssetAmount: new BigNumber(1), - takerAssetAmount: new BigNumber(1), - makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId), - takerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, takerAssetId), - }); - // Verify pre-conditions - const initialOwnerMakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId); - expect(initialOwnerMakerAsset).to.be.bignumber.not.equal(makerAddress); - const initialOwnerTakerAsset = await erc721Token.ownerOf.callAsync(takerAssetId); - expect(initialOwnerTakerAsset).to.be.bignumber.equal(takerAddress); - // Call Exchange - const takerAssetFillAmount = signedOrder.takerAssetAmount; - return expectTransactionFailedAsync( - exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount }), - RevertReason.TransferFailed, - ); - }); - - it('should throw when taker does not own the token with id takerAssetId', async () => { - // Construct Exchange parameters - const makerAssetId = erc721MakerAssetIds[0]; - const takerAssetId = erc721MakerAssetIds[1]; - signedOrder = await orderFactory.newSignedOrderAsync({ - makerAssetAmount: new BigNumber(1), - takerAssetAmount: new BigNumber(1), - makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId), - takerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, takerAssetId), - }); - // Verify pre-conditions - const initialOwnerMakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId); - expect(initialOwnerMakerAsset).to.be.bignumber.equal(makerAddress); - const initialOwnerTakerAsset = await erc721Token.ownerOf.callAsync(takerAssetId); - expect(initialOwnerTakerAsset).to.be.bignumber.not.equal(takerAddress); - // Call Exchange - const takerAssetFillAmount = signedOrder.takerAssetAmount; - return expectTransactionFailedAsync( - exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount }), - RevertReason.TransferFailed, - ); - }); - - it('should throw when makerAssetAmount is greater than 1', async () => { - // Construct Exchange parameters - const makerAssetId = erc721MakerAssetIds[0]; - const takerAssetId = erc721TakerAssetIds[0]; - signedOrder = await orderFactory.newSignedOrderAsync({ - makerAssetAmount: new BigNumber(2), - takerAssetAmount: new BigNumber(1), - makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId), - takerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, takerAssetId), - }); - // Verify pre-conditions - const initialOwnerMakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId); - expect(initialOwnerMakerAsset).to.be.bignumber.equal(makerAddress); - const initialOwnerTakerAsset = await erc721Token.ownerOf.callAsync(takerAssetId); - expect(initialOwnerTakerAsset).to.be.bignumber.equal(takerAddress); - // Call Exchange - const takerAssetFillAmount = signedOrder.takerAssetAmount; - return expectTransactionFailedAsync( - exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount }), - RevertReason.InvalidAmount, - ); - }); - - it('should throw when takerAssetAmount is greater than 1', async () => { - // Construct Exchange parameters - const makerAssetId = erc721MakerAssetIds[0]; - const takerAssetId = erc721TakerAssetIds[0]; - signedOrder = await orderFactory.newSignedOrderAsync({ - makerAssetAmount: new BigNumber(1), - takerAssetAmount: new BigNumber(500), - makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId), - takerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, takerAssetId), - }); - // Verify pre-conditions - const initialOwnerMakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId); - expect(initialOwnerMakerAsset).to.be.bignumber.equal(makerAddress); - const initialOwnerTakerAsset = await erc721Token.ownerOf.callAsync(takerAssetId); - expect(initialOwnerTakerAsset).to.be.bignumber.equal(takerAddress); - // Call Exchange - const takerAssetFillAmount = signedOrder.takerAssetAmount; - return expectTransactionFailedAsync( - exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount }), - RevertReason.InvalidAmount, - ); - }); - - it('should throw on partial fill', async () => { - // Construct Exchange parameters - const makerAssetId = erc721MakerAssetIds[0]; - signedOrder = await orderFactory.newSignedOrderAsync({ - makerAssetAmount: new BigNumber(1), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18), - makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId), - takerAssetData: assetDataUtils.encodeERC20AssetData(defaultTakerAssetAddress), - }); - // Call Exchange - const takerAssetFillAmount = signedOrder.takerAssetAmount.div(2); - return expectTransactionFailedAsync( - exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount }), - RevertReason.RoundingError, - ); - }); - }); - - describe('Testing exchange of multiple assets', () => { - it('should allow multiple assets to be exchanged for a single asset', async () => { - const makerAmounts = [new BigNumber(10), new BigNumber(20)]; - const makerNestedAssetData = [ - assetDataUtils.encodeERC20AssetData(erc20TokenA.address), - assetDataUtils.encodeERC20AssetData(erc20TokenB.address), - ]; - const makerAssetData = assetDataUtils.encodeMultiAssetData(makerAmounts, makerNestedAssetData); - const makerAssetAmount = new BigNumber(1); - const takerAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address); - const takerAssetAmount = new BigNumber(10); - signedOrder = await orderFactory.newSignedOrderAsync({ - makerAssetData, - takerAssetData, - makerAssetAmount, - takerAssetAmount, - makerFee: constants.ZERO_AMOUNT, - takerFee: constants.ZERO_AMOUNT, - }); - - const initialMakerBalanceA = await erc20TokenA.balanceOf.callAsync(makerAddress); - const initialMakerBalanceB = await erc20TokenB.balanceOf.callAsync(makerAddress); - const initialMakerZrxBalance = await zrxToken.balanceOf.callAsync(makerAddress); - const initialTakerBalanceA = await erc20TokenA.balanceOf.callAsync(takerAddress); - const initialTakerBalanceB = await erc20TokenB.balanceOf.callAsync(takerAddress); - const initialTakerZrxBalance = await zrxToken.balanceOf.callAsync(takerAddress); - - await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress); - - const finalMakerBalanceA = await erc20TokenA.balanceOf.callAsync(makerAddress); - const finalMakerBalanceB = await erc20TokenB.balanceOf.callAsync(makerAddress); - const finalMakerZrxBalance = await zrxToken.balanceOf.callAsync(makerAddress); - const finalTakerBalanceA = await erc20TokenA.balanceOf.callAsync(takerAddress); - const finalTakerBalanceB = await erc20TokenB.balanceOf.callAsync(takerAddress); - const finalTakerZrxBalance = await zrxToken.balanceOf.callAsync(takerAddress); - - expect(finalMakerBalanceA).to.be.bignumber.equal( - initialMakerBalanceA.minus(makerAmounts[0].times(makerAssetAmount)), - ); - expect(finalMakerBalanceB).to.be.bignumber.equal( - initialMakerBalanceB.minus(makerAmounts[1].times(makerAssetAmount)), - ); - expect(finalMakerZrxBalance).to.be.bignumber.equal(initialMakerZrxBalance.plus(takerAssetAmount)); - expect(finalTakerBalanceA).to.be.bignumber.equal( - initialTakerBalanceA.plus(makerAmounts[0].times(makerAssetAmount)), - ); - expect(finalTakerBalanceB).to.be.bignumber.equal( - initialTakerBalanceB.plus(makerAmounts[1].times(makerAssetAmount)), - ); - expect(finalTakerZrxBalance).to.be.bignumber.equal(initialTakerZrxBalance.minus(takerAssetAmount)); - }); - it('should allow multiple assets to be exchanged for multiple assets', async () => { - const makerAmounts = [new BigNumber(10), new BigNumber(20)]; - const makerNestedAssetData = [ - assetDataUtils.encodeERC20AssetData(erc20TokenA.address), - assetDataUtils.encodeERC20AssetData(erc20TokenB.address), - ]; - const makerAssetData = assetDataUtils.encodeMultiAssetData(makerAmounts, makerNestedAssetData); - const makerAssetAmount = new BigNumber(1); - const takerAmounts = [new BigNumber(10), new BigNumber(1)]; - const takerAssetId = erc721TakerAssetIds[0]; - const takerNestedAssetData = [ - assetDataUtils.encodeERC20AssetData(zrxToken.address), - assetDataUtils.encodeERC721AssetData(erc721Token.address, takerAssetId), - ]; - const takerAssetData = assetDataUtils.encodeMultiAssetData(takerAmounts, takerNestedAssetData); - const takerAssetAmount = new BigNumber(1); - signedOrder = await orderFactory.newSignedOrderAsync({ - makerAssetData, - takerAssetData, - makerAssetAmount, - takerAssetAmount, - makerFee: constants.ZERO_AMOUNT, - takerFee: constants.ZERO_AMOUNT, - }); - - const initialMakerBalanceA = await erc20TokenA.balanceOf.callAsync(makerAddress); - const initialMakerBalanceB = await erc20TokenB.balanceOf.callAsync(makerAddress); - const initialMakerZrxBalance = await zrxToken.balanceOf.callAsync(makerAddress); - const initialTakerBalanceA = await erc20TokenA.balanceOf.callAsync(takerAddress); - const initialTakerBalanceB = await erc20TokenB.balanceOf.callAsync(takerAddress); - const initialTakerZrxBalance = await zrxToken.balanceOf.callAsync(takerAddress); - const initialOwnerTakerAsset = await erc721Token.ownerOf.callAsync(takerAssetId); - expect(initialOwnerTakerAsset).to.be.bignumber.equal(takerAddress); - - await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress); - - const finalMakerBalanceA = await erc20TokenA.balanceOf.callAsync(makerAddress); - const finalMakerBalanceB = await erc20TokenB.balanceOf.callAsync(makerAddress); - const finalMakerZrxBalance = await zrxToken.balanceOf.callAsync(makerAddress); - const finalTakerBalanceA = await erc20TokenA.balanceOf.callAsync(takerAddress); - const finalTakerBalanceB = await erc20TokenB.balanceOf.callAsync(takerAddress); - const finalTakerZrxBalance = await zrxToken.balanceOf.callAsync(takerAddress); - const finalOwnerTakerAsset = await erc721Token.ownerOf.callAsync(takerAssetId); - - expect(finalMakerBalanceA).to.be.bignumber.equal( - initialMakerBalanceA.minus(makerAmounts[0].times(makerAssetAmount)), - ); - expect(finalMakerBalanceB).to.be.bignumber.equal( - initialMakerBalanceB.minus(makerAmounts[1].times(makerAssetAmount)), - ); - expect(finalMakerZrxBalance).to.be.bignumber.equal( - initialMakerZrxBalance.plus(takerAmounts[0].times(takerAssetAmount)), - ); - expect(finalTakerBalanceA).to.be.bignumber.equal( - initialTakerBalanceA.plus(makerAmounts[0].times(makerAssetAmount)), - ); - expect(finalTakerBalanceB).to.be.bignumber.equal( - initialTakerBalanceB.plus(makerAmounts[1].times(makerAssetAmount)), - ); - expect(finalTakerZrxBalance).to.be.bignumber.equal( - initialTakerZrxBalance.minus(takerAmounts[0].times(takerAssetAmount)), - ); - expect(finalOwnerTakerAsset).to.be.equal(makerAddress); - }); - it('should allow an order selling multiple assets to be partially filled', async () => { - const makerAmounts = [new BigNumber(10), new BigNumber(20)]; - const makerNestedAssetData = [ - assetDataUtils.encodeERC20AssetData(erc20TokenA.address), - assetDataUtils.encodeERC20AssetData(erc20TokenB.address), - ]; - const makerAssetData = assetDataUtils.encodeMultiAssetData(makerAmounts, makerNestedAssetData); - const makerAssetAmount = new BigNumber(30); - const takerAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address); - const takerAssetAmount = new BigNumber(10); - signedOrder = await orderFactory.newSignedOrderAsync({ - makerAssetData, - takerAssetData, - makerAssetAmount, - takerAssetAmount, - makerFee: constants.ZERO_AMOUNT, - takerFee: constants.ZERO_AMOUNT, - }); - - const initialMakerBalanceA = await erc20TokenA.balanceOf.callAsync(makerAddress); - const initialMakerBalanceB = await erc20TokenB.balanceOf.callAsync(makerAddress); - const initialMakerZrxBalance = await zrxToken.balanceOf.callAsync(makerAddress); - const initialTakerBalanceA = await erc20TokenA.balanceOf.callAsync(takerAddress); - const initialTakerBalanceB = await erc20TokenB.balanceOf.callAsync(takerAddress); - const initialTakerZrxBalance = await zrxToken.balanceOf.callAsync(takerAddress); - - const takerAssetFillAmount = takerAssetAmount.dividedToIntegerBy(2); - await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { - takerAssetFillAmount, - }); - - const finalMakerBalanceA = await erc20TokenA.balanceOf.callAsync(makerAddress); - const finalMakerBalanceB = await erc20TokenB.balanceOf.callAsync(makerAddress); - const finalMakerZrxBalance = await zrxToken.balanceOf.callAsync(makerAddress); - const finalTakerBalanceA = await erc20TokenA.balanceOf.callAsync(takerAddress); - const finalTakerBalanceB = await erc20TokenB.balanceOf.callAsync(takerAddress); - const finalTakerZrxBalance = await zrxToken.balanceOf.callAsync(takerAddress); - - expect(finalMakerBalanceA).to.be.bignumber.equal( - initialMakerBalanceA.minus( - makerAmounts[0].times( - makerAssetAmount.times(takerAssetFillAmount).dividedToIntegerBy(takerAssetAmount), - ), - ), - ); - expect(finalMakerBalanceB).to.be.bignumber.equal( - initialMakerBalanceB.minus( - makerAmounts[1].times( - makerAssetAmount.times(takerAssetFillAmount).dividedToIntegerBy(takerAssetAmount), - ), - ), - ); - expect(finalMakerZrxBalance).to.be.bignumber.equal( - initialMakerZrxBalance.plus( - takerAssetAmount.times(takerAssetFillAmount).dividedToIntegerBy(takerAssetAmount), - ), - ); - expect(finalTakerBalanceA).to.be.bignumber.equal( - initialTakerBalanceA.plus( - makerAmounts[0].times( - makerAssetAmount.times(takerAssetFillAmount).dividedToIntegerBy(takerAssetAmount), - ), - ), - ); - expect(finalTakerBalanceB).to.be.bignumber.equal( - initialTakerBalanceB.plus( - makerAmounts[1].times( - makerAssetAmount.times(takerAssetFillAmount).dividedToIntegerBy(takerAssetAmount), - ), - ), - ); - expect(finalTakerZrxBalance).to.be.bignumber.equal( - initialTakerZrxBalance.minus( - takerAssetAmount.times(takerAssetFillAmount).dividedToIntegerBy(takerAssetAmount), - ), - ); - }); - it('should allow an order buying multiple assets to be partially filled', async () => { - const takerAmounts = [new BigNumber(10), new BigNumber(20)]; - const takerNestedAssetData = [ - assetDataUtils.encodeERC20AssetData(erc20TokenA.address), - assetDataUtils.encodeERC20AssetData(erc20TokenB.address), - ]; - const takerAssetData = assetDataUtils.encodeMultiAssetData(takerAmounts, takerNestedAssetData); - const takerAssetAmount = new BigNumber(30); - const makerAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address); - const makerAssetAmount = new BigNumber(10); - signedOrder = await orderFactory.newSignedOrderAsync({ - makerAssetData, - takerAssetData, - makerAssetAmount, - takerAssetAmount, - makerFee: constants.ZERO_AMOUNT, - takerFee: constants.ZERO_AMOUNT, - }); - - const initialMakerBalanceA = await erc20TokenA.balanceOf.callAsync(makerAddress); - const initialMakerBalanceB = await erc20TokenB.balanceOf.callAsync(makerAddress); - const initialMakerZrxBalance = await zrxToken.balanceOf.callAsync(makerAddress); - const initialTakerBalanceA = await erc20TokenA.balanceOf.callAsync(takerAddress); - const initialTakerBalanceB = await erc20TokenB.balanceOf.callAsync(takerAddress); - const initialTakerZrxBalance = await zrxToken.balanceOf.callAsync(takerAddress); - - const takerAssetFillAmount = takerAssetAmount.dividedToIntegerBy(2); - await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { - takerAssetFillAmount, - }); - - const finalMakerBalanceA = await erc20TokenA.balanceOf.callAsync(makerAddress); - const finalMakerBalanceB = await erc20TokenB.balanceOf.callAsync(makerAddress); - const finalMakerZrxBalance = await zrxToken.balanceOf.callAsync(makerAddress); - const finalTakerBalanceA = await erc20TokenA.balanceOf.callAsync(takerAddress); - const finalTakerBalanceB = await erc20TokenB.balanceOf.callAsync(takerAddress); - const finalTakerZrxBalance = await zrxToken.balanceOf.callAsync(takerAddress); - - expect(finalMakerBalanceA).to.be.bignumber.equal( - initialMakerBalanceA.plus( - takerAmounts[0].times( - takerAssetAmount.times(takerAssetFillAmount).dividedToIntegerBy(takerAssetAmount), - ), - ), - ); - expect(finalMakerBalanceB).to.be.bignumber.equal( - initialMakerBalanceB.plus( - takerAmounts[1].times( - takerAssetAmount.times(takerAssetFillAmount).dividedToIntegerBy(takerAssetAmount), - ), - ), - ); - expect(finalMakerZrxBalance).to.be.bignumber.equal( - initialMakerZrxBalance.minus( - makerAssetAmount.times(takerAssetFillAmount).dividedToIntegerBy(takerAssetAmount), - ), - ); - expect(finalTakerBalanceA).to.be.bignumber.equal( - initialTakerBalanceA.minus( - takerAmounts[0].times( - takerAssetAmount.times(takerAssetFillAmount).dividedToIntegerBy(takerAssetAmount), - ), - ), - ); - expect(finalTakerBalanceB).to.be.bignumber.equal( - initialTakerBalanceB.minus( - takerAmounts[1].times( - takerAssetAmount.times(takerAssetFillAmount).dividedToIntegerBy(takerAssetAmount), - ), - ), - ); - expect(finalTakerZrxBalance).to.be.bignumber.equal( - initialTakerZrxBalance.plus( - makerAssetAmount.times(takerAssetFillAmount).dividedToIntegerBy(takerAssetAmount), - ), - ); - }); - }); - - describe('getOrderInfo', () => { - beforeEach(async () => { - signedOrder = await orderFactory.newSignedOrderAsync(); - }); - it('should return the correct orderInfo for an unfilled valid order', async () => { - const orderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrder); - const expectedOrderHash = orderHashUtils.getOrderHashHex(signedOrder); - const expectedTakerAssetFilledAmount = new BigNumber(0); - const expectedOrderStatus = OrderStatus.Fillable; - expect(orderInfo.orderHash).to.be.equal(expectedOrderHash); - expect(orderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal(expectedTakerAssetFilledAmount); - expect(orderInfo.orderStatus).to.equal(expectedOrderStatus); - }); - it('should return the correct orderInfo for a fully filled order', async () => { - await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress); - const orderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrder); - const expectedOrderHash = orderHashUtils.getOrderHashHex(signedOrder); - const expectedTakerAssetFilledAmount = signedOrder.takerAssetAmount; - const expectedOrderStatus = OrderStatus.FullyFilled; - expect(orderInfo.orderHash).to.be.equal(expectedOrderHash); - expect(orderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal(expectedTakerAssetFilledAmount); - expect(orderInfo.orderStatus).to.equal(expectedOrderStatus); - }); - it('should return the correct orderInfo for a partially filled order', async () => { - const takerAssetFillAmount = signedOrder.takerAssetAmount.div(2); - await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount }); - const orderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrder); - const expectedOrderHash = orderHashUtils.getOrderHashHex(signedOrder); - const expectedTakerAssetFilledAmount = takerAssetFillAmount; - const expectedOrderStatus = OrderStatus.Fillable; - expect(orderInfo.orderHash).to.be.equal(expectedOrderHash); - expect(orderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal(expectedTakerAssetFilledAmount); - expect(orderInfo.orderStatus).to.equal(expectedOrderStatus); - }); - it('should return the correct orderInfo for a cancelled and unfilled order', async () => { - await exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress); - const orderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrder); - const expectedOrderHash = orderHashUtils.getOrderHashHex(signedOrder); - const expectedTakerAssetFilledAmount = new BigNumber(0); - const expectedOrderStatus = OrderStatus.Cancelled; - expect(orderInfo.orderHash).to.be.equal(expectedOrderHash); - expect(orderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal(expectedTakerAssetFilledAmount); - expect(orderInfo.orderStatus).to.equal(expectedOrderStatus); - }); - it('should return the correct orderInfo for a cancelled and partially filled order', async () => { - const takerAssetFillAmount = signedOrder.takerAssetAmount.div(2); - await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount }); - await exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress); - const orderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrder); - const expectedOrderHash = orderHashUtils.getOrderHashHex(signedOrder); - const expectedTakerAssetFilledAmount = takerAssetFillAmount; - const expectedOrderStatus = OrderStatus.Cancelled; - expect(orderInfo.orderHash).to.be.equal(expectedOrderHash); - expect(orderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal(expectedTakerAssetFilledAmount); - expect(orderInfo.orderStatus).to.equal(expectedOrderStatus); - }); - it('should return the correct orderInfo for an expired and unfilled order', async () => { - const currentTimestamp = await getLatestBlockTimestampAsync(); - const timeUntilExpiration = signedOrder.expirationTimeSeconds.minus(currentTimestamp).toNumber(); - await increaseTimeAndMineBlockAsync(timeUntilExpiration); - const orderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrder); - const expectedOrderHash = orderHashUtils.getOrderHashHex(signedOrder); - const expectedTakerAssetFilledAmount = new BigNumber(0); - const expectedOrderStatus = OrderStatus.Expired; - expect(orderInfo.orderHash).to.be.equal(expectedOrderHash); - expect(orderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal(expectedTakerAssetFilledAmount); - expect(orderInfo.orderStatus).to.equal(expectedOrderStatus); - }); - it('should return the correct orderInfo for an expired and partially filled order', async () => { - const takerAssetFillAmount = signedOrder.takerAssetAmount.div(2); - await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount }); - const currentTimestamp = await getLatestBlockTimestampAsync(); - const timeUntilExpiration = signedOrder.expirationTimeSeconds.minus(currentTimestamp).toNumber(); - await increaseTimeAndMineBlockAsync(timeUntilExpiration); - const orderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrder); - const expectedOrderHash = orderHashUtils.getOrderHashHex(signedOrder); - const expectedTakerAssetFilledAmount = takerAssetFillAmount; - const expectedOrderStatus = OrderStatus.Expired; - expect(orderInfo.orderHash).to.be.equal(expectedOrderHash); - expect(orderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal(expectedTakerAssetFilledAmount); - expect(orderInfo.orderStatus).to.equal(expectedOrderStatus); - }); - it('should return the correct orderInfo for an expired and fully filled order', async () => { - await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress); - const currentTimestamp = await getLatestBlockTimestampAsync(); - const timeUntilExpiration = signedOrder.expirationTimeSeconds.minus(currentTimestamp).toNumber(); - await increaseTimeAndMineBlockAsync(timeUntilExpiration); - const orderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrder); - const expectedOrderHash = orderHashUtils.getOrderHashHex(signedOrder); - const expectedTakerAssetFilledAmount = signedOrder.takerAssetAmount; - // FULLY_FILLED takes precedence over EXPIRED - const expectedOrderStatus = OrderStatus.FullyFilled; - expect(orderInfo.orderHash).to.be.equal(expectedOrderHash); - expect(orderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal(expectedTakerAssetFilledAmount); - expect(orderInfo.orderStatus).to.equal(expectedOrderStatus); - }); - it('should return the correct orderInfo for an order with a makerAssetAmount of 0', async () => { - signedOrder = await orderFactory.newSignedOrderAsync({ makerAssetAmount: new BigNumber(0) }); - const orderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrder); - const expectedOrderHash = orderHashUtils.getOrderHashHex(signedOrder); - const expectedTakerAssetFilledAmount = new BigNumber(0); - const expectedOrderStatus = OrderStatus.InvalidMakerAssetAmount; - expect(orderInfo.orderHash).to.be.equal(expectedOrderHash); - expect(orderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal(expectedTakerAssetFilledAmount); - expect(orderInfo.orderStatus).to.equal(expectedOrderStatus); - }); - it('should return the correct orderInfo for an order with a takerAssetAmount of 0', async () => { - signedOrder = await orderFactory.newSignedOrderAsync({ takerAssetAmount: new BigNumber(0) }); - const orderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrder); - const expectedOrderHash = orderHashUtils.getOrderHashHex(signedOrder); - const expectedTakerAssetFilledAmount = new BigNumber(0); - const expectedOrderStatus = OrderStatus.InvalidTakerAssetAmount; - expect(orderInfo.orderHash).to.be.equal(expectedOrderHash); - expect(orderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal(expectedTakerAssetFilledAmount); - expect(orderInfo.orderStatus).to.equal(expectedOrderStatus); - }); - }); -}); -// tslint:disable:max-file-line-count -// tslint:enable:no-unnecessary-type-assertion diff --git a/contracts/protocol/test/exchange/dispatcher.ts b/contracts/protocol/test/exchange/dispatcher.ts deleted file mode 100644 index eca1b38f1..000000000 --- a/contracts/protocol/test/exchange/dispatcher.ts +++ /dev/null @@ -1,284 +0,0 @@ -import { - chaiSetup, - constants, - expectTransactionFailedAsync, - LogDecoder, - provider, - txDefaults, - web3Wrapper, -} from '@0x/contracts-test-utils'; -import { DummyERC20TokenContract } from '@0x/contracts-tokens'; -import { BlockchainLifecycle } from '@0x/dev-utils'; -import { assetDataUtils } from '@0x/order-utils'; -import { AssetProxyId, RevertReason } from '@0x/types'; -import { BigNumber } from '@0x/utils'; -import * as chai from 'chai'; -import { LogWithDecodedArgs } from 'ethereum-types'; -import * as _ from 'lodash'; - -import { ERC20ProxyContract } from '../../generated-wrappers/erc20_proxy'; -import { ERC721ProxyContract } from '../../generated-wrappers/erc721_proxy'; -import { - TestAssetProxyDispatcherAssetProxyRegisteredEventArgs, - TestAssetProxyDispatcherContract, -} from '../../generated-wrappers/test_asset_proxy_dispatcher'; -import { artifacts } from '../../src/artifacts'; -import { ERC20Wrapper } from '../utils/erc20_wrapper'; -import { ERC721Wrapper } from '../utils/erc721_wrapper'; - -chaiSetup.configure(); -const expect = chai.expect; -const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); -// tslint:disable:no-unnecessary-type-assertion -describe('AssetProxyDispatcher', () => { - let owner: string; - let notOwner: string; - let makerAddress: string; - let takerAddress: string; - - let zrxToken: DummyERC20TokenContract; - let erc20Proxy: ERC20ProxyContract; - let erc721Proxy: ERC721ProxyContract; - let assetProxyDispatcher: TestAssetProxyDispatcherContract; - - let erc20Wrapper: ERC20Wrapper; - let erc721Wrapper: ERC721Wrapper; - - before(async () => { - await blockchainLifecycle.startAsync(); - }); - after(async () => { - await blockchainLifecycle.revertAsync(); - }); - before(async () => { - // Setup accounts & addresses - const accounts = await web3Wrapper.getAvailableAddressesAsync(); - const usedAddresses = ([owner, notOwner, makerAddress, takerAddress] = _.slice(accounts, 0, 4)); - - erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner); - erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner); - - const numDummyErc20ToDeploy = 1; - [zrxToken] = await erc20Wrapper.deployDummyTokensAsync(numDummyErc20ToDeploy, constants.DUMMY_TOKEN_DECIMALS); - erc20Proxy = await erc20Wrapper.deployProxyAsync(); - await erc20Wrapper.setBalancesAndAllowancesAsync(); - - erc721Proxy = await erc721Wrapper.deployProxyAsync(); - - assetProxyDispatcher = await TestAssetProxyDispatcherContract.deployFrom0xArtifactAsync( - artifacts.TestAssetProxyDispatcher, - provider, - txDefaults, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(assetProxyDispatcher.address, { - from: owner, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await erc721Proxy.addAuthorizedAddress.sendTransactionAsync(assetProxyDispatcher.address, { - from: owner, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - }); - beforeEach(async () => { - await blockchainLifecycle.startAsync(); - }); - afterEach(async () => { - await blockchainLifecycle.revertAsync(); - }); - describe('registerAssetProxy', () => { - it('should record proxy upon registration', async () => { - await web3Wrapper.awaitTransactionSuccessAsync( - await assetProxyDispatcher.registerAssetProxy.sendTransactionAsync(erc20Proxy.address, { from: owner }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - const proxyAddress = await assetProxyDispatcher.getAssetProxy.callAsync(AssetProxyId.ERC20); - expect(proxyAddress).to.be.equal(erc20Proxy.address); - }); - - it('should be able to record multiple proxies', async () => { - // Record first proxy - await web3Wrapper.awaitTransactionSuccessAsync( - await assetProxyDispatcher.registerAssetProxy.sendTransactionAsync(erc20Proxy.address, { from: owner }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - let proxyAddress = await assetProxyDispatcher.getAssetProxy.callAsync(AssetProxyId.ERC20); - expect(proxyAddress).to.be.equal(erc20Proxy.address); - // Record another proxy - await web3Wrapper.awaitTransactionSuccessAsync( - await assetProxyDispatcher.registerAssetProxy.sendTransactionAsync(erc721Proxy.address, { - from: owner, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - proxyAddress = await assetProxyDispatcher.getAssetProxy.callAsync(AssetProxyId.ERC721); - expect(proxyAddress).to.be.equal(erc721Proxy.address); - }); - - it('should throw if a proxy with the same id is already registered', async () => { - // Initial registration - await web3Wrapper.awaitTransactionSuccessAsync( - await assetProxyDispatcher.registerAssetProxy.sendTransactionAsync(erc20Proxy.address, { from: owner }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - const proxyAddress = await assetProxyDispatcher.getAssetProxy.callAsync(AssetProxyId.ERC20); - expect(proxyAddress).to.be.equal(erc20Proxy.address); - // Deploy a new version of the ERC20 Transfer Proxy contract - const newErc20TransferProxy = await ERC20ProxyContract.deployFrom0xArtifactAsync( - artifacts.ERC20Proxy, - provider, - txDefaults, - ); - // Register new ERC20 Transfer Proxy contract - return expectTransactionFailedAsync( - assetProxyDispatcher.registerAssetProxy.sendTransactionAsync(newErc20TransferProxy.address, { - from: owner, - }), - RevertReason.AssetProxyAlreadyExists, - ); - }); - - it('should throw if requesting address is not owner', async () => { - return expectTransactionFailedAsync( - assetProxyDispatcher.registerAssetProxy.sendTransactionAsync(erc20Proxy.address, { from: notOwner }), - RevertReason.OnlyContractOwner, - ); - }); - - it('should log an event with correct arguments when an asset proxy is registered', async () => { - const logDecoder = new LogDecoder(web3Wrapper, artifacts); - const txReceipt = await logDecoder.getTxWithDecodedLogsAsync( - await assetProxyDispatcher.registerAssetProxy.sendTransactionAsync(erc20Proxy.address, { from: owner }), - ); - const logs = txReceipt.logs; - const log = logs[0] as LogWithDecodedArgs<TestAssetProxyDispatcherAssetProxyRegisteredEventArgs>; - expect(log.args.id).to.equal(AssetProxyId.ERC20); - expect(log.args.assetProxy).to.equal(erc20Proxy.address); - }); - }); - - describe('getAssetProxy', () => { - it('should return correct address of registered proxy', async () => { - await web3Wrapper.awaitTransactionSuccessAsync( - await assetProxyDispatcher.registerAssetProxy.sendTransactionAsync(erc20Proxy.address, { from: owner }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - const proxyAddress = await assetProxyDispatcher.getAssetProxy.callAsync(AssetProxyId.ERC20); - expect(proxyAddress).to.be.equal(erc20Proxy.address); - }); - - it('should return NULL address if requesting non-existent proxy', async () => { - const proxyAddress = await assetProxyDispatcher.getAssetProxy.callAsync(AssetProxyId.ERC20); - expect(proxyAddress).to.be.equal(constants.NULL_ADDRESS); - }); - }); - - describe('dispatchTransferFrom', () => { - it('should dispatch transfer to registered proxy', async () => { - // Register ERC20 proxy - await web3Wrapper.awaitTransactionSuccessAsync( - await assetProxyDispatcher.registerAssetProxy.sendTransactionAsync(erc20Proxy.address, { from: owner }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - // Construct metadata for ERC20 proxy - const encodedAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address); - - // Perform a transfer from makerAddress to takerAddress - const erc20Balances = await erc20Wrapper.getBalancesAsync(); - const amount = new BigNumber(10); - await web3Wrapper.awaitTransactionSuccessAsync( - await assetProxyDispatcher.publicDispatchTransferFrom.sendTransactionAsync( - encodedAssetData, - makerAddress, - takerAddress, - amount, - { from: owner }, - ), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - // Verify transfer was successful - const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[makerAddress][zrxToken.address].minus(amount), - ); - expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[takerAddress][zrxToken.address].plus(amount), - ); - }); - - it('should not dispatch a transfer if amount == 0', async () => { - // Register ERC20 proxy - await web3Wrapper.awaitTransactionSuccessAsync( - await assetProxyDispatcher.registerAssetProxy.sendTransactionAsync(erc20Proxy.address, { from: owner }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - // Construct metadata for ERC20 proxy - const encodedAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address); - - // Perform a transfer from makerAddress to takerAddress - const erc20Balances = await erc20Wrapper.getBalancesAsync(); - const amount = constants.ZERO_AMOUNT; - const txReceipt = await web3Wrapper.awaitTransactionSuccessAsync( - await assetProxyDispatcher.publicDispatchTransferFrom.sendTransactionAsync( - encodedAssetData, - makerAddress, - takerAddress, - amount, - { from: owner }, - ), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - expect(txReceipt.logs.length).to.be.equal(0); - const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(newBalances).to.deep.equal(erc20Balances); - }); - - it('should not dispatch a transfer if from == to', async () => { - // Register ERC20 proxy - await web3Wrapper.awaitTransactionSuccessAsync( - await assetProxyDispatcher.registerAssetProxy.sendTransactionAsync(erc20Proxy.address, { from: owner }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - // Construct metadata for ERC20 proxy - const encodedAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address); - - // Perform a transfer from makerAddress to takerAddress - const erc20Balances = await erc20Wrapper.getBalancesAsync(); - const amount = new BigNumber(10); - const txReceipt = await web3Wrapper.awaitTransactionSuccessAsync( - await assetProxyDispatcher.publicDispatchTransferFrom.sendTransactionAsync( - encodedAssetData, - makerAddress, - makerAddress, - amount, - { from: owner }, - ), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - expect(txReceipt.logs.length).to.be.equal(0); - const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(newBalances).to.deep.equal(erc20Balances); - }); - - it('should throw if dispatching to unregistered proxy', async () => { - // Construct metadata for ERC20 proxy - const encodedAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address); - // Perform a transfer from makerAddress to takerAddress - const amount = new BigNumber(10); - return expectTransactionFailedAsync( - assetProxyDispatcher.publicDispatchTransferFrom.sendTransactionAsync( - encodedAssetData, - makerAddress, - takerAddress, - amount, - { from: owner }, - ), - RevertReason.AssetProxyDoesNotExist, - ); - }); - }); -}); -// tslint:enable:no-unnecessary-type-assertion diff --git a/contracts/protocol/test/exchange/fill_order.ts b/contracts/protocol/test/exchange/fill_order.ts deleted file mode 100644 index 2bdbe4855..000000000 --- a/contracts/protocol/test/exchange/fill_order.ts +++ /dev/null @@ -1,314 +0,0 @@ -import { - AllowanceAmountScenario, - AssetDataScenario, - BalanceAmountScenario, - chaiSetup, - ExpirationTimeSecondsScenario, - FeeRecipientAddressScenario, - FillScenario, - OrderAssetAmountScenario, - provider, - TakerAssetFillAmountScenario, - TakerScenario, - txDefaults, - web3Wrapper, -} from '@0x/contracts-test-utils'; -import { BlockchainLifecycle } from '@0x/dev-utils'; -import * as _ from 'lodash'; - -import { - FillOrderCombinatorialUtils, - fillOrderCombinatorialUtilsFactoryAsync, -} from '../utils/fill_order_combinatorial_utils'; - -chaiSetup.configure(); -const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); - -const defaultFillScenario = { - orderScenario: { - takerScenario: TakerScenario.Unspecified, - feeRecipientScenario: FeeRecipientAddressScenario.EthUserAddress, - makerAssetAmountScenario: OrderAssetAmountScenario.Large, - takerAssetAmountScenario: OrderAssetAmountScenario.Large, - makerFeeScenario: OrderAssetAmountScenario.Large, - takerFeeScenario: OrderAssetAmountScenario.Large, - expirationTimeSecondsScenario: ExpirationTimeSecondsScenario.InFuture, - makerAssetDataScenario: AssetDataScenario.ERC20NonZRXEighteenDecimals, - takerAssetDataScenario: AssetDataScenario.ERC20NonZRXEighteenDecimals, - }, - takerAssetFillAmountScenario: TakerAssetFillAmountScenario.LessThanRemainingFillableTakerAssetAmount, - makerStateScenario: { - traderAssetBalance: BalanceAmountScenario.Higher, - traderAssetAllowance: AllowanceAmountScenario.Higher, - zrxFeeBalance: BalanceAmountScenario.Higher, - zrxFeeAllowance: AllowanceAmountScenario.Higher, - }, - takerStateScenario: { - traderAssetBalance: BalanceAmountScenario.Higher, - traderAssetAllowance: AllowanceAmountScenario.Higher, - zrxFeeBalance: BalanceAmountScenario.Higher, - zrxFeeAllowance: AllowanceAmountScenario.Higher, - }, -}; - -describe('FillOrder Tests', () => { - let fillOrderCombinatorialUtils: FillOrderCombinatorialUtils; - - before(async () => { - await blockchainLifecycle.startAsync(); - fillOrderCombinatorialUtils = await fillOrderCombinatorialUtilsFactoryAsync(web3Wrapper, txDefaults); - }); - after(async () => { - await blockchainLifecycle.revertAsync(); - }); - beforeEach(async () => { - await blockchainLifecycle.startAsync(); - }); - afterEach(async () => { - await blockchainLifecycle.revertAsync(); - }); - describe('fillOrder', () => { - const test = (fillScenarios: FillScenario[]) => { - _.forEach(fillScenarios, fillScenario => { - const description = `Combinatorial OrderFill: ${JSON.stringify(fillScenario)}`; - it(description, async () => { - await fillOrderCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); - }); - }); - }; - - const allFillScenarios = FillOrderCombinatorialUtils.generateFillOrderCombinations(); - describe('Combinatorially generated fills orders', () => test(allFillScenarios)); - - it('should transfer the correct amounts when makerAssetAmount === takerAssetAmount', async () => { - const fillScenario = { - ...defaultFillScenario, - }; - await fillOrderCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); - }); - it('should transfer the correct amounts when makerAssetAmount > takerAssetAmount', async () => { - const fillScenario = { - ...defaultFillScenario, - orderScenario: { - ...defaultFillScenario.orderScenario, - takerAssetAmountScenario: OrderAssetAmountScenario.Small, - }, - }; - await fillOrderCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); - }); - it('should transfer the correct amounts when makerAssetAmount < takerAssetAmount', async () => { - const fillScenario = { - ...defaultFillScenario, - orderScenario: { - ...defaultFillScenario.orderScenario, - makerAssetAmountScenario: OrderAssetAmountScenario.Small, - }, - }; - await fillOrderCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); - }); - it('should transfer the correct amounts when makerAssetAmount < takerAssetAmount with zero decimals', async () => { - const fillScenario = { - ...defaultFillScenario, - orderScenario: { - ...defaultFillScenario.orderScenario, - makerAssetAmountScenario: OrderAssetAmountScenario.Small, - makerAssetDataScenario: AssetDataScenario.ERC20ZeroDecimals, - }, - }; - await fillOrderCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); - }); - it('should transfer the correct amounts when taker is specified and order is claimed by taker', async () => { - const fillScenario = { - ...defaultFillScenario, - orderScenario: { - ...defaultFillScenario.orderScenario, - takerScenario: TakerScenario.CorrectlySpecified, - }, - }; - await fillOrderCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); - }); - it('should fill remaining value if takerAssetFillAmount > remaining takerAssetAmount', async () => { - const fillScenario = { - ...defaultFillScenario, - takerAssetFillAmountScenario: TakerAssetFillAmountScenario.GreaterThanRemainingFillableTakerAssetAmount, - }; - await fillOrderCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); - }); - it('should throw when taker is specified and order is claimed by other', async () => { - const fillScenario = { - ...defaultFillScenario, - orderScenario: { - ...defaultFillScenario.orderScenario, - takerScenario: TakerScenario.IncorrectlySpecified, - }, - }; - await fillOrderCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); - }); - - it('should throw if makerAssetAmount is 0', async () => { - const fillScenario = { - ...defaultFillScenario, - orderScenario: { - ...defaultFillScenario.orderScenario, - makerAssetAmountScenario: OrderAssetAmountScenario.Zero, - }, - takerAssetFillAmountScenario: TakerAssetFillAmountScenario.GreaterThanRemainingFillableTakerAssetAmount, - }; - await fillOrderCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); - }); - - it('should throw if takerAssetAmount is 0', async () => { - const fillScenario = { - ...defaultFillScenario, - orderScenario: { - ...defaultFillScenario.orderScenario, - takerAssetAmountScenario: OrderAssetAmountScenario.Zero, - }, - takerAssetFillAmountScenario: TakerAssetFillAmountScenario.GreaterThanRemainingFillableTakerAssetAmount, - }; - await fillOrderCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); - }); - - it('should throw if takerAssetFillAmount is 0', async () => { - const fillScenario = { - ...defaultFillScenario, - takerAssetFillAmountScenario: TakerAssetFillAmountScenario.Zero, - }; - await fillOrderCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); - }); - - it('should throw if an order is expired', async () => { - const fillScenario = { - ...defaultFillScenario, - orderScenario: { - ...defaultFillScenario.orderScenario, - expirationTimeSecondsScenario: ExpirationTimeSecondsScenario.InPast, - }, - }; - await fillOrderCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); - }); - - it('should throw if maker erc20Balances are too low to fill order', async () => { - const fillScenario = { - ...defaultFillScenario, - makerStateScenario: { - ...defaultFillScenario.makerStateScenario, - traderAssetBalance: BalanceAmountScenario.TooLow, - }, - }; - await fillOrderCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); - }); - - it('should throw if taker erc20Balances are too low to fill order', async () => { - const fillScenario = { - ...defaultFillScenario, - takerStateScenario: { - ...defaultFillScenario.makerStateScenario, - traderAssetBalance: BalanceAmountScenario.TooLow, - }, - }; - await fillOrderCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); - }); - - it('should throw if maker allowances are too low to fill order', async () => { - const fillScenario = { - ...defaultFillScenario, - makerStateScenario: { - ...defaultFillScenario.makerStateScenario, - traderAssetAllowance: AllowanceAmountScenario.TooLow, - }, - }; - await fillOrderCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); - }); - - it('should throw if taker allowances are too low to fill order', async () => { - const fillScenario = { - ...defaultFillScenario, - takerStateScenario: { - ...defaultFillScenario.makerStateScenario, - traderAssetAllowance: AllowanceAmountScenario.TooLow, - }, - }; - await fillOrderCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); - }); - }); - - describe('Testing exchange of ERC721 Tokens', () => { - it('should successfully exchange a single token between the maker and taker (via fillOrder)', async () => { - const fillScenario = { - ...defaultFillScenario, - orderScenario: { - ...defaultFillScenario.orderScenario, - makerAssetDataScenario: AssetDataScenario.ERC721, - takerAssetDataScenario: AssetDataScenario.ERC721, - }, - takerAssetFillAmountScenario: TakerAssetFillAmountScenario.ExactlyRemainingFillableTakerAssetAmount, - }; - await fillOrderCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); - }); - - it('should successfully fill order when makerAsset is ERC721 and takerAsset is ERC20', async () => { - const fillScenario = { - ...defaultFillScenario, - orderScenario: { - ...defaultFillScenario.orderScenario, - makerAssetDataScenario: AssetDataScenario.ERC721, - takerAssetDataScenario: AssetDataScenario.ERC20NonZRXEighteenDecimals, - }, - takerAssetFillAmountScenario: TakerAssetFillAmountScenario.ExactlyRemainingFillableTakerAssetAmount, - }; - await fillOrderCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario, true); - }); - - it('should successfully fill order when makerAsset is ERC20 and takerAsset is ERC721', async () => { - const fillScenario = { - ...defaultFillScenario, - orderScenario: { - ...defaultFillScenario.orderScenario, - makerAssetDataScenario: AssetDataScenario.ERC20NonZRXEighteenDecimals, - takerAssetDataScenario: AssetDataScenario.ERC721, - }, - takerAssetFillAmountScenario: TakerAssetFillAmountScenario.ExactlyRemainingFillableTakerAssetAmount, - }; - await fillOrderCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); - }); - - it('should successfully fill order when makerAsset is ERC721 and approveAll is set for it', async () => { - const fillScenario = { - ...defaultFillScenario, - orderScenario: { - ...defaultFillScenario.orderScenario, - makerAssetDataScenario: AssetDataScenario.ERC721, - takerAssetDataScenario: AssetDataScenario.ERC20NonZRXEighteenDecimals, - }, - takerAssetFillAmountScenario: TakerAssetFillAmountScenario.ExactlyRemainingFillableTakerAssetAmount, - makerStateScenario: { - ...defaultFillScenario.makerStateScenario, - traderAssetAllowance: AllowanceAmountScenario.Unlimited, - }, - }; - await fillOrderCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); - }); - - it('should successfully fill order when makerAsset and takerAsset are ERC721 and approveAll is set for them', async () => { - const fillScenario = { - ...defaultFillScenario, - orderScenario: { - ...defaultFillScenario.orderScenario, - makerAssetDataScenario: AssetDataScenario.ERC721, - takerAssetDataScenario: AssetDataScenario.ERC721, - }, - takerAssetFillAmountScenario: TakerAssetFillAmountScenario.ExactlyRemainingFillableTakerAssetAmount, - makerStateScenario: { - ...defaultFillScenario.makerStateScenario, - traderAssetAllowance: AllowanceAmountScenario.Unlimited, - }, - takerStateScenario: { - ...defaultFillScenario.takerStateScenario, - traderAssetAllowance: AllowanceAmountScenario.Unlimited, - }, - }; - await fillOrderCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); - }); - }); -}); diff --git a/contracts/protocol/test/exchange/internal.ts b/contracts/protocol/test/exchange/internal.ts deleted file mode 100644 index 6ff1fdc2f..000000000 --- a/contracts/protocol/test/exchange/internal.ts +++ /dev/null @@ -1,472 +0,0 @@ -import { - bytes32Values, - chaiSetup, - constants, - FillResults, - getRevertReasonOrErrorMessageForSendTransactionAsync, - provider, - testCombinatoriallyWithReferenceFuncAsync, - txDefaults, - uint256Values, - web3Wrapper, -} from '@0x/contracts-test-utils'; -import { BlockchainLifecycle } from '@0x/dev-utils'; -import { Order, RevertReason, SignedOrder } from '@0x/types'; -import { BigNumber } from '@0x/utils'; -import * as chai from 'chai'; -import * as _ from 'lodash'; - -import { TestExchangeInternalsContract } from '../../generated-wrappers/test_exchange_internals'; -import { artifacts } from '../../src/artifacts'; - -chaiSetup.configure(); -const expect = chai.expect; - -const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); - -const MAX_UINT256 = new BigNumber(2).pow(256).minus(1); - -const emptyOrder: Order = { - senderAddress: constants.NULL_ADDRESS, - makerAddress: constants.NULL_ADDRESS, - takerAddress: constants.NULL_ADDRESS, - makerFee: new BigNumber(0), - takerFee: new BigNumber(0), - makerAssetAmount: new BigNumber(0), - takerAssetAmount: new BigNumber(0), - makerAssetData: '0x', - takerAssetData: '0x', - salt: new BigNumber(0), - exchangeAddress: constants.NULL_ADDRESS, - feeRecipientAddress: constants.NULL_ADDRESS, - expirationTimeSeconds: new BigNumber(0), -}; - -const emptySignedOrder: SignedOrder = { - ...emptyOrder, - signature: '', -}; - -const overflowErrorForCall = new Error(RevertReason.Uint256Overflow); - -describe('Exchange core internal functions', () => { - let testExchange: TestExchangeInternalsContract; - let overflowErrorForSendTransaction: Error | undefined; - let divisionByZeroErrorForCall: Error | undefined; - let roundingErrorForCall: Error | undefined; - - before(async () => { - await blockchainLifecycle.startAsync(); - }); - after(async () => { - await blockchainLifecycle.revertAsync(); - }); - before(async () => { - testExchange = await TestExchangeInternalsContract.deployFrom0xArtifactAsync( - artifacts.TestExchangeInternals, - provider, - txDefaults, - ); - overflowErrorForSendTransaction = new Error( - await getRevertReasonOrErrorMessageForSendTransactionAsync(RevertReason.Uint256Overflow), - ); - divisionByZeroErrorForCall = new Error(RevertReason.DivisionByZero); - roundingErrorForCall = new Error(RevertReason.RoundingError); - }); - // Note(albrow): Don't forget to add beforeEach and afterEach calls to reset - // the blockchain state for any tests which modify it! - - async function referenceIsRoundingErrorFloorAsync( - numerator: BigNumber, - denominator: BigNumber, - target: BigNumber, - ): Promise<boolean> { - if (denominator.eq(0)) { - throw divisionByZeroErrorForCall; - } - if (numerator.eq(0)) { - return false; - } - if (target.eq(0)) { - return false; - } - const product = numerator.multipliedBy(target); - const remainder = product.mod(denominator); - const remainderTimes1000 = remainder.multipliedBy('1000'); - const isError = remainderTimes1000.gte(product); - if (product.isGreaterThan(MAX_UINT256)) { - throw overflowErrorForCall; - } - if (remainderTimes1000.isGreaterThan(MAX_UINT256)) { - throw overflowErrorForCall; - } - return isError; - } - - async function referenceIsRoundingErrorCeilAsync( - numerator: BigNumber, - denominator: BigNumber, - target: BigNumber, - ): Promise<boolean> { - if (denominator.eq(0)) { - throw divisionByZeroErrorForCall; - } - if (numerator.eq(0)) { - return false; - } - if (target.eq(0)) { - return false; - } - const product = numerator.multipliedBy(target); - const remainder = product.mod(denominator); - const error = denominator.minus(remainder).mod(denominator); - const errorTimes1000 = error.multipliedBy('1000'); - const isError = errorTimes1000.gte(product); - if (product.isGreaterThan(MAX_UINT256)) { - throw overflowErrorForCall; - } - if (errorTimes1000.isGreaterThan(MAX_UINT256)) { - throw overflowErrorForCall; - } - return isError; - } - - async function referenceSafeGetPartialAmountFloorAsync( - numerator: BigNumber, - denominator: BigNumber, - target: BigNumber, - ): Promise<BigNumber> { - if (denominator.eq(0)) { - throw divisionByZeroErrorForCall; - } - const isRoundingError = await referenceIsRoundingErrorFloorAsync(numerator, denominator, target); - if (isRoundingError) { - throw roundingErrorForCall; - } - const product = numerator.multipliedBy(target); - if (product.isGreaterThan(MAX_UINT256)) { - throw overflowErrorForCall; - } - return product.dividedToIntegerBy(denominator); - } - - describe('addFillResults', async () => { - function makeFillResults(value: BigNumber): FillResults { - return { - makerAssetFilledAmount: value, - takerAssetFilledAmount: value, - makerFeePaid: value, - takerFeePaid: value, - }; - } - async function referenceAddFillResultsAsync( - totalValue: BigNumber, - singleValue: BigNumber, - ): Promise<FillResults> { - // Note(albrow): Here, each of totalFillResults and - // singleFillResults will consist of fields with the same values. - // This should be safe because none of the fields in a given - // FillResults are ever used together in a mathemetical operation. - // They are only used with the corresponding field from *the other* - // FillResults, which are different. - const totalFillResults = makeFillResults(totalValue); - const singleFillResults = makeFillResults(singleValue); - // HACK(albrow): _.mergeWith mutates the first argument! To - // workaround this we use _.cloneDeep. - return _.mergeWith( - _.cloneDeep(totalFillResults), - singleFillResults, - (totalVal: BigNumber, singleVal: BigNumber) => { - const newTotal = totalVal.plus(singleVal); - if (newTotal.isGreaterThan(MAX_UINT256)) { - throw overflowErrorForCall; - } - return newTotal; - }, - ); - } - async function testAddFillResultsAsync(totalValue: BigNumber, singleValue: BigNumber): Promise<FillResults> { - const totalFillResults = makeFillResults(totalValue); - const singleFillResults = makeFillResults(singleValue); - return testExchange.publicAddFillResults.callAsync(totalFillResults, singleFillResults); - } - await testCombinatoriallyWithReferenceFuncAsync( - 'addFillResults', - referenceAddFillResultsAsync, - testAddFillResultsAsync, - [uint256Values, uint256Values], - ); - }); - - describe('calculateFillResults', async () => { - function makeOrder( - makerAssetAmount: BigNumber, - takerAssetAmount: BigNumber, - makerFee: BigNumber, - takerFee: BigNumber, - ): Order { - return { - ...emptyOrder, - makerAssetAmount, - takerAssetAmount, - makerFee, - takerFee, - }; - } - async function referenceCalculateFillResultsAsync( - orderTakerAssetAmount: BigNumber, - takerAssetFilledAmount: BigNumber, - otherAmount: BigNumber, - ): Promise<FillResults> { - // Note(albrow): Here we are re-using the same value (otherAmount) - // for order.makerAssetAmount, order.makerFee, and order.takerFee. - // This should be safe because they are never used with each other - // in any mathematical operation in either the reference TypeScript - // implementation or the Solidity implementation of - // calculateFillResults. - const makerAssetFilledAmount = await referenceSafeGetPartialAmountFloorAsync( - takerAssetFilledAmount, - orderTakerAssetAmount, - otherAmount, - ); - const order = makeOrder(otherAmount, orderTakerAssetAmount, otherAmount, otherAmount); - const orderMakerAssetAmount = order.makerAssetAmount; - return { - makerAssetFilledAmount, - takerAssetFilledAmount, - makerFeePaid: await referenceSafeGetPartialAmountFloorAsync( - makerAssetFilledAmount, - orderMakerAssetAmount, - otherAmount, - ), - takerFeePaid: await referenceSafeGetPartialAmountFloorAsync( - takerAssetFilledAmount, - orderTakerAssetAmount, - otherAmount, - ), - }; - } - async function testCalculateFillResultsAsync( - orderTakerAssetAmount: BigNumber, - takerAssetFilledAmount: BigNumber, - otherAmount: BigNumber, - ): Promise<FillResults> { - const order = makeOrder(otherAmount, orderTakerAssetAmount, otherAmount, otherAmount); - return testExchange.publicCalculateFillResults.callAsync(order, takerAssetFilledAmount); - } - await testCombinatoriallyWithReferenceFuncAsync( - 'calculateFillResults', - referenceCalculateFillResultsAsync, - testCalculateFillResultsAsync, - [uint256Values, uint256Values, uint256Values], - ); - }); - - describe('getPartialAmountFloor', async () => { - async function referenceGetPartialAmountFloorAsync( - numerator: BigNumber, - denominator: BigNumber, - target: BigNumber, - ): Promise<BigNumber> { - if (denominator.eq(0)) { - throw divisionByZeroErrorForCall; - } - const product = numerator.multipliedBy(target); - if (product.isGreaterThan(MAX_UINT256)) { - throw overflowErrorForCall; - } - return product.dividedToIntegerBy(denominator); - } - async function testGetPartialAmountFloorAsync( - numerator: BigNumber, - denominator: BigNumber, - target: BigNumber, - ): Promise<BigNumber> { - return testExchange.publicGetPartialAmountFloor.callAsync(numerator, denominator, target); - } - await testCombinatoriallyWithReferenceFuncAsync( - 'getPartialAmountFloor', - referenceGetPartialAmountFloorAsync, - testGetPartialAmountFloorAsync, - [uint256Values, uint256Values, uint256Values], - ); - }); - - describe('getPartialAmountCeil', async () => { - async function referenceGetPartialAmountCeilAsync( - numerator: BigNumber, - denominator: BigNumber, - target: BigNumber, - ): Promise<BigNumber> { - if (denominator.eq(0)) { - throw divisionByZeroErrorForCall; - } - const product = numerator.multipliedBy(target); - const offset = product.plus(denominator.minus(1)); - if (offset.isGreaterThan(MAX_UINT256)) { - throw overflowErrorForCall; - } - const result = offset.dividedToIntegerBy(denominator); - if (product.mod(denominator).eq(0)) { - expect(result.multipliedBy(denominator)).to.be.bignumber.eq(product); - } else { - expect(result.multipliedBy(denominator)).to.be.bignumber.gt(product); - } - return result; - } - async function testGetPartialAmountCeilAsync( - numerator: BigNumber, - denominator: BigNumber, - target: BigNumber, - ): Promise<BigNumber> { - return testExchange.publicGetPartialAmountCeil.callAsync(numerator, denominator, target); - } - await testCombinatoriallyWithReferenceFuncAsync( - 'getPartialAmountCeil', - referenceGetPartialAmountCeilAsync, - testGetPartialAmountCeilAsync, - [uint256Values, uint256Values, uint256Values], - ); - }); - - describe('safeGetPartialAmountFloor', async () => { - async function testSafeGetPartialAmountFloorAsync( - numerator: BigNumber, - denominator: BigNumber, - target: BigNumber, - ): Promise<BigNumber> { - return testExchange.publicSafeGetPartialAmountFloor.callAsync(numerator, denominator, target); - } - await testCombinatoriallyWithReferenceFuncAsync( - 'safeGetPartialAmountFloor', - referenceSafeGetPartialAmountFloorAsync, - testSafeGetPartialAmountFloorAsync, - [uint256Values, uint256Values, uint256Values], - ); - }); - - describe('safeGetPartialAmountCeil', async () => { - async function referenceSafeGetPartialAmountCeilAsync( - numerator: BigNumber, - denominator: BigNumber, - target: BigNumber, - ): Promise<BigNumber> { - if (denominator.eq(0)) { - throw divisionByZeroErrorForCall; - } - const isRoundingError = await referenceIsRoundingErrorCeilAsync(numerator, denominator, target); - if (isRoundingError) { - throw roundingErrorForCall; - } - const product = numerator.multipliedBy(target); - const offset = product.plus(denominator.minus(1)); - if (offset.isGreaterThan(MAX_UINT256)) { - throw overflowErrorForCall; - } - const result = offset.dividedToIntegerBy(denominator); - if (product.mod(denominator).eq(0)) { - expect(result.multipliedBy(denominator)).to.be.bignumber.eq(product); - } else { - expect(result.multipliedBy(denominator)).to.be.bignumber.gt(product); - } - return result; - } - async function testSafeGetPartialAmountCeilAsync( - numerator: BigNumber, - denominator: BigNumber, - target: BigNumber, - ): Promise<BigNumber> { - return testExchange.publicSafeGetPartialAmountCeil.callAsync(numerator, denominator, target); - } - await testCombinatoriallyWithReferenceFuncAsync( - 'safeGetPartialAmountCeil', - referenceSafeGetPartialAmountCeilAsync, - testSafeGetPartialAmountCeilAsync, - [uint256Values, uint256Values, uint256Values], - ); - }); - - describe('isRoundingErrorFloor', async () => { - async function testIsRoundingErrorFloorAsync( - numerator: BigNumber, - denominator: BigNumber, - target: BigNumber, - ): Promise<boolean> { - return testExchange.publicIsRoundingErrorFloor.callAsync(numerator, denominator, target); - } - await testCombinatoriallyWithReferenceFuncAsync( - 'isRoundingErrorFloor', - referenceIsRoundingErrorFloorAsync, - testIsRoundingErrorFloorAsync, - [uint256Values, uint256Values, uint256Values], - ); - }); - - describe('isRoundingErrorCeil', async () => { - async function testIsRoundingErrorCeilAsync( - numerator: BigNumber, - denominator: BigNumber, - target: BigNumber, - ): Promise<boolean> { - return testExchange.publicIsRoundingErrorCeil.callAsync(numerator, denominator, target); - } - await testCombinatoriallyWithReferenceFuncAsync( - 'isRoundingErrorCeil', - referenceIsRoundingErrorCeilAsync, - testIsRoundingErrorCeilAsync, - [uint256Values, uint256Values, uint256Values], - ); - }); - - describe('updateFilledState', async () => { - // Note(albrow): Since updateFilledState modifies the state by calling - // sendTransaction, we must reset the state after each test. - beforeEach(async () => { - await blockchainLifecycle.startAsync(); - }); - afterEach(async () => { - await blockchainLifecycle.revertAsync(); - }); - async function referenceUpdateFilledStateAsync( - takerAssetFilledAmount: BigNumber, - orderTakerAssetFilledAmount: BigNumber, - // tslint:disable-next-line:no-unused-variable - orderHash: string, - ): Promise<BigNumber> { - const totalFilledAmount = takerAssetFilledAmount.plus(orderTakerAssetFilledAmount); - if (totalFilledAmount.isGreaterThan(MAX_UINT256)) { - throw overflowErrorForSendTransaction; - } - return totalFilledAmount; - } - async function testUpdateFilledStateAsync( - takerAssetFilledAmount: BigNumber, - orderTakerAssetFilledAmount: BigNumber, - orderHash: string, - ): Promise<BigNumber> { - const fillResults = { - makerAssetFilledAmount: new BigNumber(0), - takerAssetFilledAmount, - makerFeePaid: new BigNumber(0), - takerFeePaid: new BigNumber(0), - }; - await web3Wrapper.awaitTransactionSuccessAsync( - await testExchange.publicUpdateFilledState.sendTransactionAsync( - emptySignedOrder, - constants.NULL_ADDRESS, - orderHash, - orderTakerAssetFilledAmount, - fillResults, - ), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - return testExchange.filled.callAsync(orderHash); - } - await testCombinatoriallyWithReferenceFuncAsync( - 'updateFilledState', - referenceUpdateFilledStateAsync, - testUpdateFilledStateAsync, - [uint256Values, uint256Values, bytes32Values], - ); - }); -}); diff --git a/contracts/protocol/test/exchange/match_orders.ts b/contracts/protocol/test/exchange/match_orders.ts deleted file mode 100644 index 8e943c237..000000000 --- a/contracts/protocol/test/exchange/match_orders.ts +++ /dev/null @@ -1,1284 +0,0 @@ -import { - chaiSetup, - constants, - ERC20BalancesByOwner, - ERC721TokenIdsByOwner, - expectTransactionFailedAsync, - OrderFactory, - provider, - txDefaults, - web3Wrapper, -} from '@0x/contracts-test-utils'; -import { - artifacts as tokensArtifacts, - DummyERC20TokenContract, - DummyERC721TokenContract, - ReentrantERC20TokenContract, -} from '@0x/contracts-tokens'; -import { BlockchainLifecycle } from '@0x/dev-utils'; -import { assetDataUtils } from '@0x/order-utils'; -import { RevertReason } from '@0x/types'; -import { BigNumber } from '@0x/utils'; -import { Web3Wrapper } from '@0x/web3-wrapper'; -import * as chai from 'chai'; -import * as _ from 'lodash'; - -import { ERC20ProxyContract } from '../../generated-wrappers/erc20_proxy'; -import { ERC721ProxyContract } from '../../generated-wrappers/erc721_proxy'; -import { ExchangeContract } from '../../generated-wrappers/exchange'; -import { TestExchangeInternalsContract } from '../../generated-wrappers/test_exchange_internals'; -import { artifacts } from '../../src/artifacts'; -import { ERC20Wrapper } from '../utils/erc20_wrapper'; -import { ERC721Wrapper } from '../utils/erc721_wrapper'; -import { ExchangeWrapper } from '../utils/exchange_wrapper'; -import { MatchOrderTester } from '../utils/match_order_tester'; - -const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); -chaiSetup.configure(); -const expect = chai.expect; - -describe('matchOrders', () => { - let makerAddressLeft: string; - let makerAddressRight: string; - let owner: string; - let takerAddress: string; - let feeRecipientAddressLeft: string; - let feeRecipientAddressRight: string; - - let erc20TokenA: DummyERC20TokenContract; - let erc20TokenB: DummyERC20TokenContract; - let zrxToken: DummyERC20TokenContract; - let erc721Token: DummyERC721TokenContract; - let reentrantErc20Token: ReentrantERC20TokenContract; - let exchange: ExchangeContract; - let erc20Proxy: ERC20ProxyContract; - let erc721Proxy: ERC721ProxyContract; - - let erc20BalancesByOwner: ERC20BalancesByOwner; - let erc721TokenIdsByOwner: ERC721TokenIdsByOwner; - let exchangeWrapper: ExchangeWrapper; - let erc20Wrapper: ERC20Wrapper; - let erc721Wrapper: ERC721Wrapper; - let orderFactoryLeft: OrderFactory; - let orderFactoryRight: OrderFactory; - - let erc721LeftMakerAssetIds: BigNumber[]; - let erc721RightMakerAssetIds: BigNumber[]; - - let defaultERC20MakerAssetAddress: string; - let defaultERC20TakerAssetAddress: string; - let defaultERC721AssetAddress: string; - - let matchOrderTester: MatchOrderTester; - - let testExchange: TestExchangeInternalsContract; - - before(async () => { - await blockchainLifecycle.startAsync(); - }); - after(async () => { - await blockchainLifecycle.revertAsync(); - }); - before(async () => { - // Create accounts - const accounts = await web3Wrapper.getAvailableAddressesAsync(); - // Hack(albrow): Both Prettier and TSLint insert a trailing comma below - // but that is invalid syntax as of TypeScript version >= 2.8. We don't - // have the right fine-grained configuration options in TSLint, - // Prettier, or TypeScript, to reconcile this, so we will just have to - // wait for them to sort it out. We disable TSLint and Prettier for - // this part of the code for now. This occurs several times in this - // file. See https://github.com/prettier/prettier/issues/4624. - // prettier-ignore - const usedAddresses = ([ - owner, - makerAddressLeft, - makerAddressRight, - takerAddress, - feeRecipientAddressLeft, - // tslint:disable-next-line:trailing-comma - feeRecipientAddressRight - ] = _.slice(accounts, 0, 6)); - // Create wrappers - erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner); - erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner); - // Deploy ERC20 token & ERC20 proxy - const numDummyErc20ToDeploy = 3; - [erc20TokenA, erc20TokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync( - numDummyErc20ToDeploy, - constants.DUMMY_TOKEN_DECIMALS, - ); - erc20Proxy = await erc20Wrapper.deployProxyAsync(); - await erc20Wrapper.setBalancesAndAllowancesAsync(); - // Deploy ERC721 token and proxy - [erc721Token] = await erc721Wrapper.deployDummyTokensAsync(); - erc721Proxy = await erc721Wrapper.deployProxyAsync(); - await erc721Wrapper.setBalancesAndAllowancesAsync(); - const erc721Balances = await erc721Wrapper.getBalancesAsync(); - erc721LeftMakerAssetIds = erc721Balances[makerAddressLeft][erc721Token.address]; - erc721RightMakerAssetIds = erc721Balances[makerAddressRight][erc721Token.address]; - // Depoy exchange - exchange = await ExchangeContract.deployFrom0xArtifactAsync( - artifacts.Exchange, - provider, - txDefaults, - assetDataUtils.encodeERC20AssetData(zrxToken.address), - ); - exchangeWrapper = new ExchangeWrapper(exchange, provider); - await exchangeWrapper.registerAssetProxyAsync(erc20Proxy.address, owner); - await exchangeWrapper.registerAssetProxyAsync(erc721Proxy.address, owner); - // Authorize ERC20 and ERC721 trades by exchange - await web3Wrapper.awaitTransactionSuccessAsync( - await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(exchange.address, { - from: owner, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await erc721Proxy.addAuthorizedAddress.sendTransactionAsync(exchange.address, { - from: owner, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - - reentrantErc20Token = await ReentrantERC20TokenContract.deployFrom0xArtifactAsync( - tokensArtifacts.ReentrantERC20Token, - provider, - txDefaults, - exchange.address, - ); - - // Set default addresses - defaultERC20MakerAssetAddress = erc20TokenA.address; - defaultERC20TakerAssetAddress = erc20TokenB.address; - defaultERC721AssetAddress = erc721Token.address; - // Create default order parameters - const defaultOrderParamsLeft = { - ...constants.STATIC_ORDER_PARAMS, - makerAddress: makerAddressLeft, - exchangeAddress: exchange.address, - makerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), - takerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20TakerAssetAddress), - feeRecipientAddress: feeRecipientAddressLeft, - }; - const defaultOrderParamsRight = { - ...constants.STATIC_ORDER_PARAMS, - makerAddress: makerAddressRight, - exchangeAddress: exchange.address, - makerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20TakerAssetAddress), - takerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), - feeRecipientAddress: feeRecipientAddressRight, - }; - const privateKeyLeft = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddressLeft)]; - orderFactoryLeft = new OrderFactory(privateKeyLeft, defaultOrderParamsLeft); - const privateKeyRight = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddressRight)]; - orderFactoryRight = new OrderFactory(privateKeyRight, defaultOrderParamsRight); - // Set match order tester - matchOrderTester = new MatchOrderTester(exchangeWrapper, erc20Wrapper, erc721Wrapper, zrxToken.address); - testExchange = await TestExchangeInternalsContract.deployFrom0xArtifactAsync( - artifacts.TestExchangeInternals, - provider, - txDefaults, - ); - }); - beforeEach(async () => { - await blockchainLifecycle.startAsync(); - }); - afterEach(async () => { - await blockchainLifecycle.revertAsync(); - }); - describe('matchOrders', () => { - beforeEach(async () => { - erc20BalancesByOwner = await erc20Wrapper.getBalancesAsync(); - erc721TokenIdsByOwner = await erc721Wrapper.getBalancesAsync(); - }); - - it('Should transfer correct amounts when right order is fully filled and values pass isRoundingErrorFloor but fail isRoundingErrorCeil', async () => { - // Create orders to match - const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ - makerAddress: makerAddressLeft, - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(17), 0), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(98), 0), - feeRecipientAddress: feeRecipientAddressLeft, - }); - const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ - makerAddress: makerAddressRight, - makerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20TakerAssetAddress), - takerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(75), 0), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(13), 0), - feeRecipientAddress: feeRecipientAddressRight, - }); - // Assert is rounding error ceil & not rounding error floor - // These assertions are taken from MixinMatchOrders::calculateMatchedFillResults - // The rounding error is derived computating how much the left maker will sell. - const numerator = signedOrderLeft.makerAssetAmount; - const denominator = signedOrderLeft.takerAssetAmount; - const target = signedOrderRight.makerAssetAmount; - const isRoundingErrorCeil = await testExchange.publicIsRoundingErrorCeil.callAsync( - numerator, - denominator, - target, - ); - expect(isRoundingErrorCeil).to.be.true(); - const isRoundingErrorFloor = await testExchange.publicIsRoundingErrorFloor.callAsync( - numerator, - denominator, - target, - ); - expect(isRoundingErrorFloor).to.be.false(); - // Match signedOrderLeft with signedOrderRight - // Note that the left maker received a slightly better sell price. - // This is intentional; see note in MixinMatchOrders.calculateMatchedFillResults. - // Because the left maker received a slightly more favorable sell price, the fee - // paid by the left taker is slightly higher than that paid by the left maker. - // Fees can be thought of as a tax paid by the seller, derived from the sale price. - const expectedTransferAmounts = { - // Left Maker - amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(13), 0), - amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(75), 0), - feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber('76.4705882352941176'), 16), // 76.47% - // Right Maker - amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(75), 0), - amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(13), 0), - feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - // Taker - amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(0), 0), - feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber('76.5306122448979591'), 16), // 76.53% - feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - }; - await matchOrderTester.matchOrdersAndAssertEffectsAsync( - signedOrderLeft, - signedOrderRight, - takerAddress, - erc20BalancesByOwner, - erc721TokenIdsByOwner, - expectedTransferAmounts, - ); - }); - - it('Should transfer correct amounts when left order is fully filled and values pass isRoundingErrorCeil but fail isRoundingErrorFloor', async () => { - // Create orders to match - const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ - makerAddress: makerAddressLeft, - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(15), 0), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(90), 0), - feeRecipientAddress: feeRecipientAddressLeft, - }); - const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ - makerAddress: makerAddressRight, - makerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20TakerAssetAddress), - takerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(97), 0), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(14), 0), - feeRecipientAddress: feeRecipientAddressRight, - }); - // Assert is rounding error floor & not rounding error ceil - // These assertions are taken from MixinMatchOrders::calculateMatchedFillResults - // The rounding error is derived computating how much the right maker will buy. - const numerator = signedOrderRight.takerAssetAmount; - const denominator = signedOrderRight.makerAssetAmount; - const target = signedOrderLeft.takerAssetAmount; - const isRoundingErrorFloor = await testExchange.publicIsRoundingErrorFloor.callAsync( - numerator, - denominator, - target, - ); - expect(isRoundingErrorFloor).to.be.true(); - const isRoundingErrorCeil = await testExchange.publicIsRoundingErrorCeil.callAsync( - numerator, - denominator, - target, - ); - expect(isRoundingErrorCeil).to.be.false(); - // Match signedOrderLeft with signedOrderRight - // Note that the right maker received a slightly better purchase price. - // This is intentional; see note in MixinMatchOrders.calculateMatchedFillResults. - // Because the right maker received a slightly more favorable buy price, the fee - // paid by the right taker is slightly higher than that paid by the right maker. - // Fees can be thought of as a tax paid by the seller, derived from the sale price. - const expectedTransferAmounts = { - // Left Maker - amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(15), 0), - amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(90), 0), - feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - // Right Maker - amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(90), 0), - amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(13), 0), - feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber('92.7835051546391752'), 16), // 92.78% - // Taker - amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 0), - feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber('92.8571428571428571'), 16), // 92.85% - }; - await matchOrderTester.matchOrdersAndAssertEffectsAsync( - signedOrderLeft, - signedOrderRight, - takerAddress, - erc20BalancesByOwner, - erc721TokenIdsByOwner, - expectedTransferAmounts, - ); - }); - - it('Should give right maker a better buy price when rounding', async () => { - // Create orders to match - const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ - makerAddress: makerAddressLeft, - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(16), 0), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(22), 0), - feeRecipientAddress: feeRecipientAddressLeft, - }); - const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ - makerAddress: makerAddressRight, - makerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20TakerAssetAddress), - takerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(83), 0), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(49), 0), - feeRecipientAddress: feeRecipientAddressRight, - }); - // Note: - // The correct price buy price for the right maker would yield (49/83) * 22 = 12.988 units - // of the left maker asset. This gets rounded up to 13, giving the right maker a better price. - // Note: - // The maker/taker fee percentage paid on the right order differs because - // they received different sale prices. The right maker pays a - // fee slightly lower than the right taker. - const expectedTransferAmounts = { - // Left Maker - amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(16), 0), - amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(22), 0), - feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - // Right Maker - amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(22), 0), - amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(13), 0), - feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber('26.5060240963855421'), 16), // 26.506% - // Taker - amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(3), 0), - feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber('26.5306122448979591'), 16), // 26.531% - }; - // Match signedOrderLeft with signedOrderRight - await matchOrderTester.matchOrdersAndAssertEffectsAsync( - signedOrderLeft, - signedOrderRight, - takerAddress, - erc20BalancesByOwner, - erc721TokenIdsByOwner, - expectedTransferAmounts, - ); - }); - - it('Should give left maker a better sell price when rounding', async () => { - // Create orders to match - const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ - makerAddress: makerAddressLeft, - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(12), 0), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(97), 0), - feeRecipientAddress: feeRecipientAddressLeft, - }); - const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ - makerAddress: makerAddressRight, - makerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20TakerAssetAddress), - takerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(89), 0), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 0), - feeRecipientAddress: feeRecipientAddressRight, - }); - // Note: - // The maker/taker fee percentage paid on the left order differs because - // they received different sale prices. The left maker pays a fee - // slightly lower than the left taker. - const expectedTransferAmounts = { - // Left Maker - amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(11), 0), - amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(89), 0), - feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber('91.6666666666666666'), 16), // 91.6% - // Right Maker - amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(89), 0), - amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 0), - feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - // Taker - amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 0), - feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber('91.7525773195876288'), 16), // 91.75% - feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - }; - // Match signedOrderLeft with signedOrderRight - await matchOrderTester.matchOrdersAndAssertEffectsAsync( - signedOrderLeft, - signedOrderRight, - takerAddress, - erc20BalancesByOwner, - erc721TokenIdsByOwner, - expectedTransferAmounts, - ); - }); - - it('Should give right maker and right taker a favorable fee price when rounding', async () => { - // Create orders to match - const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ - makerAddress: makerAddressLeft, - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(16), 0), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(22), 0), - feeRecipientAddress: feeRecipientAddressLeft, - }); - const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ - makerAddress: makerAddressRight, - makerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20TakerAssetAddress), - takerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(83), 0), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(49), 0), - feeRecipientAddress: feeRecipientAddressRight, - makerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(10000), 0), - takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(10000), 0), - }); - // Note: - // The maker/taker fee percentage paid on the right order differs because - // they received different sale prices. The right maker pays a - // fee slightly lower than the right taker. - const expectedTransferAmounts = { - // Left Maker - amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(16), 0), - amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(22), 0), - feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - // Right Maker - amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(22), 0), - amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(13), 0), - feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(2650), 0), // 2650.6 rounded down tro 2650 - // Taker - amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(3), 0), - feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(2653), 0), // 2653.1 rounded down to 2653 - }; - // Match signedOrderLeft with signedOrderRight - await matchOrderTester.matchOrdersAndAssertEffectsAsync( - signedOrderLeft, - signedOrderRight, - takerAddress, - erc20BalancesByOwner, - erc721TokenIdsByOwner, - expectedTransferAmounts, - ); - }); - - it('Should give left maker and left taker a favorable fee price when rounding', async () => { - // Create orders to match - const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ - makerAddress: makerAddressLeft, - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(12), 0), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(97), 0), - feeRecipientAddress: feeRecipientAddressLeft, - makerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(10000), 0), - takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(10000), 0), - }); - const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ - makerAddress: makerAddressRight, - makerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20TakerAssetAddress), - takerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(89), 0), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 0), - feeRecipientAddress: feeRecipientAddressRight, - }); - // Note: - // The maker/taker fee percentage paid on the left order differs because - // they received different sale prices. The left maker pays a - // fee slightly lower than the left taker. - const expectedTransferAmounts = { - // Left Maker - amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(11), 0), - amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(89), 0), - feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(9166), 0), // 9166.6 rounded down to 9166 - // Right Maker - amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(89), 0), - amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 0), - feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - // Taker - amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 0), - feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(9175), 0), // 9175.2 rounded down to 9175 - feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - }; - // Match signedOrderLeft with signedOrderRight - await matchOrderTester.matchOrdersAndAssertEffectsAsync( - signedOrderLeft, - signedOrderRight, - takerAddress, - erc20BalancesByOwner, - erc721TokenIdsByOwner, - expectedTransferAmounts, - ); - }); - - it('Should transfer correct amounts when right order fill amount deviates from amount derived by `Exchange.fillOrder`', async () => { - // Create orders to match - const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ - makerAddress: makerAddressLeft, - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(1000), 0), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(1005), 0), - feeRecipientAddress: feeRecipientAddressLeft, - }); - const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ - makerAddress: makerAddressRight, - makerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20TakerAssetAddress), - takerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2126), 0), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(1063), 0), - feeRecipientAddress: feeRecipientAddressRight, - }); - const expectedTransferAmounts = { - // Left Maker - amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(1000), 0), - amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(1005), 0), - feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - // Right Maker - // Notes: - // i. - // The left order is fully filled by the right order, so the right maker must sell 1005 units of their asset to the left maker. - // By selling 1005 units, the right maker should theoretically receive 502.5 units of the left maker's asset. - // Since the transfer amount must be an integer, this value must be rounded down to 502 or up to 503. - // ii. - // If the right order were filled via `Exchange.fillOrder` the respective fill amounts would be [1004, 502] or [1006, 503]. - // It follows that we cannot trigger a sale of 1005 units of the right maker's asset through `Exchange.fillOrder`. - // iii. - // For an optimal match, the algorithm must choose either [1005, 502] or [1005, 503] as fill amounts for the right order. - // The algorithm favors the right maker when the exchange rate must be rounded, so the final fill for the right order is [1005, 503]. - // iv. - // The right maker fee differs from the right taker fee because their exchange rate differs. - // The right maker always receives the better exchange and fee price. - amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(1005), 0), - amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(503), 0), - feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber('47.2718720602069614'), 16), // 47.27% - // Taker - amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(497), 0), - feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber('47.3189087488240827'), 16), // 47.31% - }; - // Match signedOrderLeft with signedOrderRight - await matchOrderTester.matchOrdersAndAssertEffectsAsync( - signedOrderLeft, - signedOrderRight, - takerAddress, - erc20BalancesByOwner, - erc721TokenIdsByOwner, - expectedTransferAmounts, - ); - }); - - const reentrancyTest = (functionNames: string[]) => { - _.forEach(functionNames, async (functionName: string, functionId: number) => { - const description = `should not allow matchOrders to reenter the Exchange contract via ${functionName}`; - it(description, async () => { - const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ - makerAssetData: assetDataUtils.encodeERC20AssetData(reentrantErc20Token.address), - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - }); - const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ - makerAddress: makerAddressRight, - takerAssetData: assetDataUtils.encodeERC20AssetData(reentrantErc20Token.address), - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), - feeRecipientAddress: feeRecipientAddressRight, - }); - await web3Wrapper.awaitTransactionSuccessAsync( - await reentrantErc20Token.setCurrentFunction.sendTransactionAsync(functionId), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await expectTransactionFailedAsync( - exchangeWrapper.matchOrdersAsync(signedOrderLeft, signedOrderRight, takerAddress), - RevertReason.TransferFailed, - ); - }); - }); - }; - describe('matchOrders reentrancy tests', () => reentrancyTest(constants.FUNCTIONS_WITH_MUTEX)); - - it('should transfer the correct amounts when orders completely fill each other', async () => { - // Create orders to match - const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - }); - const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), - }); - // Match signedOrderLeft with signedOrderRight - const expectedTransferAmounts = { - // Left Maker - amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), - amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - // Right Maker - amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), - feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - // Taker - amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(3), 18), - feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - }; - await matchOrderTester.matchOrdersAndAssertEffectsAsync( - signedOrderLeft, - signedOrderRight, - takerAddress, - erc20BalancesByOwner, - erc721TokenIdsByOwner, - expectedTransferAmounts, - ); - }); - - it('should transfer the correct amounts when orders completely fill each other and taker doesnt take a profit', async () => { - // Create orders to match - const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - }); - const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), - }); - // Match signedOrderLeft with signedOrderRight - const expectedTransferAmounts = { - // Left Maker - amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), - amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - // Right Maker - amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), - feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - // Taker - amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(0), 18), - feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - }; - // Match signedOrderLeft with signedOrderRight - await matchOrderTester.matchOrdersAndAssertEffectsAsync( - signedOrderLeft, - signedOrderRight, - takerAddress, - erc20BalancesByOwner, - erc721TokenIdsByOwner, - expectedTransferAmounts, - ); - }); - - it('should transfer the correct amounts when left order is completely filled and right order is partially filled', async () => { - // Create orders to match - const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - }); - const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(20), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(4), 18), - }); - // Match signedOrderLeft with signedOrderRight - const expectedTransferAmounts = { - // Left Maker - amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), - amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - // Right Maker - amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), - feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(50), 16), // 50% - // Taker - amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(3), 18), - feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(50), 16), // 50% - }; - // Match signedOrderLeft with signedOrderRight - await matchOrderTester.matchOrdersAndAssertEffectsAsync( - signedOrderLeft, - signedOrderRight, - takerAddress, - erc20BalancesByOwner, - erc721TokenIdsByOwner, - expectedTransferAmounts, - ); - }); - - it('should transfer the correct amounts when right order is completely filled and left order is partially filled', async () => { - // Create orders to match - const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(50), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18), - }); - const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), - }); - // Match signedOrderLeft with signedOrderRight - const expectedTransferAmounts = { - // Left Maker - amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), - amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 16), // 10% - // Right Maker - amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), - feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - // Taker - amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(3), 18), - feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 16), // 10% - feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - }; - // Match signedOrderLeft with signedOrderRight - await matchOrderTester.matchOrdersAndAssertEffectsAsync( - signedOrderLeft, - signedOrderRight, - takerAddress, - erc20BalancesByOwner, - erc721TokenIdsByOwner, - expectedTransferAmounts, - ); - }); - - it('should transfer the correct amounts when consecutive calls are used to completely fill the left order', async () => { - // Create orders to match - const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(50), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18), - }); - const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), - }); - // Match orders - let newERC20BalancesByOwner: ERC20BalancesByOwner; - let newERC721TokenIdsByOwner: ERC721TokenIdsByOwner; - const expectedTransferAmounts = { - // Left Maker - amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), - amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 16), // 10% - // Right Maker - amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), - feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - // Taker - amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(3), 18), - feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 16), // 10% - feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - }; - // prettier-ignore - [ - newERC20BalancesByOwner, - // tslint:disable-next-line:trailing-comma - newERC721TokenIdsByOwner - ] = await matchOrderTester.matchOrdersAndAssertEffectsAsync( - signedOrderLeft, - signedOrderRight, - takerAddress, - erc20BalancesByOwner, - erc721TokenIdsByOwner, - expectedTransferAmounts, - ); - // Construct second right order - // Note: This order needs makerAssetAmount=90/takerAssetAmount=[anything <= 45] to fully fill the right order. - // However, we use 100/50 to ensure a partial fill as we want to go down the "left fill" - // branch in the contract twice for this test. - const signedOrderRight2 = await orderFactoryRight.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(50), 18), - }); - // Match signedOrderLeft with signedOrderRight2 - const leftTakerAssetFilledAmount = signedOrderRight.makerAssetAmount; - const rightTakerAssetFilledAmount = new BigNumber(0); - const expectedTransferAmounts2 = { - // Left Maker - amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(45), 18), - amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(90), 18), - feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(90), 16), // 90% (10% paid earlier) - // Right Maker - amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(90), 18), - amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(45), 18), - feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(90), 16), // 90% - // Taker - amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(0), 18), - feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(90), 16), // 90% (10% paid earlier) - feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(90), 16), // 90% - }; - await matchOrderTester.matchOrdersAndAssertEffectsAsync( - signedOrderLeft, - signedOrderRight2, - takerAddress, - newERC20BalancesByOwner, - newERC721TokenIdsByOwner, - expectedTransferAmounts2, - leftTakerAssetFilledAmount, - rightTakerAssetFilledAmount, - ); - }); - - it('should transfer the correct amounts when consecutive calls are used to completely fill the right order', async () => { - // Create orders to match - const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), - }); - - const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(50), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18), - }); - // Match orders - let newERC20BalancesByOwner: ERC20BalancesByOwner; - let newERC721TokenIdsByOwner: ERC721TokenIdsByOwner; - const expectedTransferAmounts = { - // Left Maker - amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), - feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - // Right Maker - amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), - amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(4), 18), - feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(4), 16), // 4% - // Taker - amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(6), 18), - feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(4), 16), // 4% - }; - // prettier-ignore - [ - newERC20BalancesByOwner, - // tslint:disable-next-line:trailing-comma - newERC721TokenIdsByOwner - ] = await matchOrderTester.matchOrdersAndAssertEffectsAsync( - signedOrderLeft, - signedOrderRight, - takerAddress, - erc20BalancesByOwner, - erc721TokenIdsByOwner, - expectedTransferAmounts, - ); - - // Create second left order - // Note: This order needs makerAssetAmount=96/takerAssetAmount=48 to fully fill the right order. - // However, we use 100/50 to ensure a partial fill as we want to go down the "right fill" - // branch in the contract twice for this test. - const signedOrderLeft2 = await orderFactoryLeft.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(50), 18), - }); - // Match signedOrderLeft2 with signedOrderRight - const leftTakerAssetFilledAmount = new BigNumber(0); - const takerAmountReceived = newERC20BalancesByOwner[takerAddress][defaultERC20MakerAssetAddress].minus( - erc20BalancesByOwner[takerAddress][defaultERC20MakerAssetAddress], - ); - const rightTakerAssetFilledAmount = signedOrderLeft.makerAssetAmount.minus(takerAmountReceived); - const expectedTransferAmounts2 = { - // Left Maker - amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(96), 18), - amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(48), 18), - feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(96), 16), // 96% - // Right Maker - amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(48), 18), - amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(96), 18), - feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(96), 16), // 96% - // Taker - amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(0), 18), - feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(96), 16), // 96% - feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(96), 16), // 96% - }; - await matchOrderTester.matchOrdersAndAssertEffectsAsync( - signedOrderLeft2, - signedOrderRight, - takerAddress, - newERC20BalancesByOwner, - newERC721TokenIdsByOwner, - expectedTransferAmounts2, - leftTakerAssetFilledAmount, - rightTakerAssetFilledAmount, - ); - }); - - it('should transfer the correct amounts if fee recipient is the same across both matched orders', async () => { - const feeRecipientAddress = feeRecipientAddressLeft; - const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - feeRecipientAddress, - }); - const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), - feeRecipientAddress, - }); - // Match orders - const expectedTransferAmounts = { - // Left Maker - amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), - amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - // Right Maker - amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), - feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - // Taker - amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(3), 18), - feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - }; - await matchOrderTester.matchOrdersAndAssertEffectsAsync( - signedOrderLeft, - signedOrderRight, - takerAddress, - erc20BalancesByOwner, - erc721TokenIdsByOwner, - expectedTransferAmounts, - ); - }); - - it('should transfer the correct amounts if taker is also the left order maker', async () => { - // Create orders to match - const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - }); - const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), - }); - // Match orders - takerAddress = signedOrderLeft.makerAddress; - const expectedTransferAmounts = { - // Left Maker - amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), - amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - // Right Maker - amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), - feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - // Taker - amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(3), 18), - feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - }; - await matchOrderTester.matchOrdersAndAssertEffectsAsync( - signedOrderLeft, - signedOrderRight, - takerAddress, - erc20BalancesByOwner, - erc721TokenIdsByOwner, - expectedTransferAmounts, - ); - }); - - it('should transfer the correct amounts if taker is also the right order maker', async () => { - // Create orders to match - const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - }); - const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), - }); - // Match orders - takerAddress = signedOrderRight.makerAddress; - const expectedTransferAmounts = { - // Left Maker - amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), - amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - // Right Maker - amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), - feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - // Taker - amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(3), 18), - feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - }; - await matchOrderTester.matchOrdersAndAssertEffectsAsync( - signedOrderLeft, - signedOrderRight, - takerAddress, - erc20BalancesByOwner, - erc721TokenIdsByOwner, - expectedTransferAmounts, - ); - }); - - it('should transfer the correct amounts if taker is also the left fee recipient', async () => { - // Create orders to match - const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - }); - const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), - }); - // Match orders - takerAddress = feeRecipientAddressLeft; - const expectedTransferAmounts = { - // Left Maker - amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), - amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - // Right Maker - amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), - feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - // Taker - amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(3), 18), - feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - }; - await matchOrderTester.matchOrdersAndAssertEffectsAsync( - signedOrderLeft, - signedOrderRight, - takerAddress, - erc20BalancesByOwner, - erc721TokenIdsByOwner, - expectedTransferAmounts, - ); - }); - - it('should transfer the correct amounts if taker is also the right fee recipient', async () => { - // Create orders to match - const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - }); - const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), - }); - // Match orders - takerAddress = feeRecipientAddressRight; - const expectedTransferAmounts = { - // Left Maker - amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), - amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - // Right Maker - amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), - feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - // Taker - amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(3), 18), - feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - }; - await matchOrderTester.matchOrdersAndAssertEffectsAsync( - signedOrderLeft, - signedOrderRight, - takerAddress, - erc20BalancesByOwner, - erc721TokenIdsByOwner, - expectedTransferAmounts, - ); - }); - - it('should transfer the correct amounts if left maker is the left fee recipient and right maker is the right fee recipient', async () => { - // Create orders to match - const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - }); - const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), - }); - // Match orders - const expectedTransferAmounts = { - // Left Maker - amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), - amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - // Right Maker - amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), - feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - // Taker - amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(3), 18), - feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - }; - await matchOrderTester.matchOrdersAndAssertEffectsAsync( - signedOrderLeft, - signedOrderRight, - takerAddress, - erc20BalancesByOwner, - erc721TokenIdsByOwner, - expectedTransferAmounts, - ); - }); - - it('Should throw if left order is not fillable', async () => { - // Create orders to match - const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - }); - const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), - }); - // Cancel left order - await exchangeWrapper.cancelOrderAsync(signedOrderLeft, signedOrderLeft.makerAddress); - // Match orders - return expectTransactionFailedAsync( - exchangeWrapper.matchOrdersAsync(signedOrderLeft, signedOrderRight, takerAddress), - RevertReason.OrderUnfillable, - ); - }); - - it('Should throw if right order is not fillable', async () => { - // Create orders to match - const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - }); - const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), - }); - // Cancel right order - await exchangeWrapper.cancelOrderAsync(signedOrderRight, signedOrderRight.makerAddress); - // Match orders - return expectTransactionFailedAsync( - exchangeWrapper.matchOrdersAsync(signedOrderLeft, signedOrderRight, takerAddress), - RevertReason.OrderUnfillable, - ); - }); - - it('should throw if there is not a positive spread', async () => { - // Create orders to match - const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18), - }); - const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(200), 18), - }); - // Match orders - return expectTransactionFailedAsync( - exchangeWrapper.matchOrdersAsync(signedOrderLeft, signedOrderRight, takerAddress), - RevertReason.NegativeSpreadRequired, - ); - }); - - it('should throw if the left maker asset is not equal to the right taker asset ', async () => { - // Create orders to match - const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - }); - const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ - takerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20TakerAssetAddress), - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), - }); - // Match orders - return expectTransactionFailedAsync( - exchangeWrapper.matchOrdersAsync(signedOrderLeft, signedOrderRight, takerAddress), - // We are assuming assetData fields of the right order are the - // reverse of the left order, rather than checking equality. This - // saves a bunch of gas, but as a result if the assetData fields are - // off then the failure ends up happening at signature validation - RevertReason.InvalidOrderSignature, - ); - }); - - it('should throw if the right maker asset is not equal to the left taker asset', async () => { - // Create orders to match - const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ - takerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - }); - const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), - }); - // Match orders - return expectTransactionFailedAsync( - exchangeWrapper.matchOrdersAsync(signedOrderLeft, signedOrderRight, takerAddress), - RevertReason.InvalidOrderSignature, - ); - }); - - it('should transfer correct amounts when left order maker asset is an ERC721 token', async () => { - // Create orders to match - const erc721TokenToTransfer = erc721LeftMakerAssetIds[0]; - const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ - makerAssetData: assetDataUtils.encodeERC721AssetData(defaultERC721AssetAddress, erc721TokenToTransfer), - makerAssetAmount: new BigNumber(1), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - }); - const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ - takerAssetData: assetDataUtils.encodeERC721AssetData(defaultERC721AssetAddress, erc721TokenToTransfer), - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - takerAssetAmount: new BigNumber(1), - }); - // Match orders - const expectedTransferAmounts = { - // Left Maker - amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 0), - amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - // Right Maker - amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 0), - feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - // Taker - amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(0), 18), - feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 50% - }; - await matchOrderTester.matchOrdersAndAssertEffectsAsync( - signedOrderLeft, - signedOrderRight, - takerAddress, - erc20BalancesByOwner, - erc721TokenIdsByOwner, - expectedTransferAmounts, - ); - }); - - it('should transfer correct amounts when right order maker asset is an ERC721 token', async () => { - // Create orders to match - const erc721TokenToTransfer = erc721RightMakerAssetIds[0]; - const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ - takerAssetData: assetDataUtils.encodeERC721AssetData(defaultERC721AssetAddress, erc721TokenToTransfer), - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - takerAssetAmount: new BigNumber(1), - }); - const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ - makerAssetData: assetDataUtils.encodeERC721AssetData(defaultERC721AssetAddress, erc721TokenToTransfer), - makerAssetAmount: new BigNumber(1), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(8), 18), - }); - // Match orders - const expectedTransferAmounts = { - // Left Maker - amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 0), - feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - // Right Maker - amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 0), - amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(8), 18), - feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - // Taker - amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), - feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - }; - await matchOrderTester.matchOrdersAndAssertEffectsAsync( - signedOrderLeft, - signedOrderRight, - takerAddress, - erc20BalancesByOwner, - erc721TokenIdsByOwner, - expectedTransferAmounts, - ); - }); - }); -}); // tslint:disable-line:max-file-line-count diff --git a/contracts/protocol/test/exchange/signature_validator.ts b/contracts/protocol/test/exchange/signature_validator.ts deleted file mode 100644 index 3f1786b5b..000000000 --- a/contracts/protocol/test/exchange/signature_validator.ts +++ /dev/null @@ -1,526 +0,0 @@ -import { artifacts as examplesArtifacts, ValidatorContract, WalletContract } from '@0x/contracts-examples'; -import { - addressUtils, - chaiSetup, - constants, - expectContractCallFailedAsync, - LogDecoder, - OrderFactory, - provider, - txDefaults, - web3Wrapper, -} from '@0x/contracts-test-utils'; -import { artifacts as tokensArtifacts } from '@0x/contracts-tokens'; -import { BlockchainLifecycle } from '@0x/dev-utils'; -import { assetDataUtils, orderHashUtils, signatureUtils } from '@0x/order-utils'; -import { RevertReason, SignatureType, SignedOrder } from '@0x/types'; -import * as chai from 'chai'; -import { LogWithDecodedArgs } from 'ethereum-types'; -import ethUtil = require('ethereumjs-util'); - -import { - artifacts, - TestSignatureValidatorContract, - TestSignatureValidatorSignatureValidatorApprovalEventArgs, - TestStaticCallReceiverContract, -} from '../../src'; - -chaiSetup.configure(); -const expect = chai.expect; - -const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); -// tslint:disable:no-unnecessary-type-assertion -describe('MixinSignatureValidator', () => { - let signedOrder: SignedOrder; - let orderFactory: OrderFactory; - let signatureValidator: TestSignatureValidatorContract; - let testWallet: WalletContract; - let testValidator: ValidatorContract; - let maliciousWallet: TestStaticCallReceiverContract; - let maliciousValidator: TestStaticCallReceiverContract; - let signerAddress: string; - let signerPrivateKey: Buffer; - let notSignerAddress: string; - let notSignerPrivateKey: Buffer; - let signatureValidatorLogDecoder: LogDecoder; - - before(async () => { - await blockchainLifecycle.startAsync(); - }); - after(async () => { - await blockchainLifecycle.revertAsync(); - }); - before(async () => { - const accounts = await web3Wrapper.getAvailableAddressesAsync(); - const makerAddress = accounts[0]; - signerAddress = makerAddress; - notSignerAddress = accounts[1]; - signatureValidator = await TestSignatureValidatorContract.deployFrom0xArtifactAsync( - artifacts.TestSignatureValidator, - provider, - txDefaults, - ); - testWallet = await WalletContract.deployFrom0xArtifactAsync( - examplesArtifacts.Wallet, - provider, - txDefaults, - signerAddress, - ); - testValidator = await ValidatorContract.deployFrom0xArtifactAsync( - examplesArtifacts.Validator, - provider, - txDefaults, - signerAddress, - ); - maliciousWallet = maliciousValidator = await TestStaticCallReceiverContract.deployFrom0xArtifactAsync( - artifacts.TestStaticCallReceiver, - provider, - txDefaults, - ); - signatureValidatorLogDecoder = new LogDecoder(web3Wrapper, { ...artifacts, ...tokensArtifacts }); - await web3Wrapper.awaitTransactionSuccessAsync( - await signatureValidator.setSignatureValidatorApproval.sendTransactionAsync(testValidator.address, true, { - from: signerAddress, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await signatureValidator.setSignatureValidatorApproval.sendTransactionAsync( - maliciousValidator.address, - true, - { - from: signerAddress, - }, - ), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - - const defaultOrderParams = { - ...constants.STATIC_ORDER_PARAMS, - exchangeAddress: signatureValidator.address, - makerAddress, - feeRecipientAddress: addressUtils.generatePseudoRandomAddress(), - makerAssetData: assetDataUtils.encodeERC20AssetData(addressUtils.generatePseudoRandomAddress()), - takerAssetData: assetDataUtils.encodeERC20AssetData(addressUtils.generatePseudoRandomAddress()), - }; - signerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddress)]; - notSignerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(notSignerAddress)]; - orderFactory = new OrderFactory(signerPrivateKey, defaultOrderParams); - }); - - beforeEach(async () => { - await blockchainLifecycle.startAsync(); - }); - afterEach(async () => { - await blockchainLifecycle.revertAsync(); - }); - - describe('isValidSignature', () => { - beforeEach(async () => { - signedOrder = await orderFactory.newSignedOrderAsync(); - }); - - it('should revert when signature is empty', async () => { - const emptySignature = '0x'; - const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); - return expectContractCallFailedAsync( - signatureValidator.publicIsValidSignature.callAsync( - orderHashHex, - signedOrder.makerAddress, - emptySignature, - ), - RevertReason.LengthGreaterThan0Required, - ); - }); - - it('should revert when signature type is unsupported', async () => { - const unsupportedSignatureType = SignatureType.NSignatureTypes; - const unsupportedSignatureHex = `0x${Buffer.from([unsupportedSignatureType]).toString('hex')}`; - const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); - return expectContractCallFailedAsync( - signatureValidator.publicIsValidSignature.callAsync( - orderHashHex, - signedOrder.makerAddress, - unsupportedSignatureHex, - ), - RevertReason.SignatureUnsupported, - ); - }); - - it('should revert when SignatureType=Illegal', async () => { - const unsupportedSignatureHex = `0x${Buffer.from([SignatureType.Illegal]).toString('hex')}`; - const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); - return expectContractCallFailedAsync( - signatureValidator.publicIsValidSignature.callAsync( - orderHashHex, - signedOrder.makerAddress, - unsupportedSignatureHex, - ), - RevertReason.SignatureIllegal, - ); - }); - - it('should return false when SignatureType=Invalid and signature has a length of zero', async () => { - const signatureHex = `0x${Buffer.from([SignatureType.Invalid]).toString('hex')}`; - const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); - const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( - orderHashHex, - signedOrder.makerAddress, - signatureHex, - ); - expect(isValidSignature).to.be.false(); - }); - - it('should revert when SignatureType=Invalid and signature length is non-zero', async () => { - const fillerData = ethUtil.toBuffer('0xdeadbeef'); - const signatureType = ethUtil.toBuffer(`0x${SignatureType.Invalid}`); - const signatureBuffer = Buffer.concat([fillerData, signatureType]); - const signatureHex = ethUtil.bufferToHex(signatureBuffer); - const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); - return expectContractCallFailedAsync( - signatureValidator.publicIsValidSignature.callAsync( - orderHashHex, - signedOrder.makerAddress, - signatureHex, - ), - RevertReason.Length0Required, - ); - }); - - it('should return true when SignatureType=EIP712 and signature is valid', async () => { - // Create EIP712 signature - const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); - const orderHashBuffer = ethUtil.toBuffer(orderHashHex); - const ecSignature = ethUtil.ecsign(orderHashBuffer, signerPrivateKey); - // Create 0x signature from EIP712 signature - const signature = Buffer.concat([ - ethUtil.toBuffer(ecSignature.v), - ecSignature.r, - ecSignature.s, - ethUtil.toBuffer(`0x${SignatureType.EIP712}`), - ]); - const signatureHex = ethUtil.bufferToHex(signature); - // Validate signature - const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( - orderHashHex, - signerAddress, - signatureHex, - ); - expect(isValidSignature).to.be.true(); - }); - - it('should return false when SignatureType=EIP712 and signature is invalid', async () => { - // Create EIP712 signature - const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); - const orderHashBuffer = ethUtil.toBuffer(orderHashHex); - const ecSignature = ethUtil.ecsign(orderHashBuffer, signerPrivateKey); - // Create 0x signature from EIP712 signature - const signature = Buffer.concat([ - ethUtil.toBuffer(ecSignature.v), - ecSignature.r, - ecSignature.s, - ethUtil.toBuffer(`0x${SignatureType.EIP712}`), - ]); - const signatureHex = ethUtil.bufferToHex(signature); - // Validate signature. - // This will fail because `signerAddress` signed the message, but we're passing in `notSignerAddress` - const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( - orderHashHex, - notSignerAddress, - signatureHex, - ); - expect(isValidSignature).to.be.false(); - }); - - it('should return true when SignatureType=EthSign and signature is valid', async () => { - // Create EthSign signature - const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); - const orderHashWithEthSignPrefixHex = signatureUtils.addSignedMessagePrefix(orderHashHex); - const orderHashWithEthSignPrefixBuffer = ethUtil.toBuffer(orderHashWithEthSignPrefixHex); - const ecSignature = ethUtil.ecsign(orderHashWithEthSignPrefixBuffer, signerPrivateKey); - // Create 0x signature from EthSign signature - const signature = Buffer.concat([ - ethUtil.toBuffer(ecSignature.v), - ecSignature.r, - ecSignature.s, - ethUtil.toBuffer(`0x${SignatureType.EthSign}`), - ]); - const signatureHex = ethUtil.bufferToHex(signature); - // Validate signature - const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( - orderHashHex, - signerAddress, - signatureHex, - ); - expect(isValidSignature).to.be.true(); - }); - - it('should return false when SignatureType=EthSign and signature is invalid', async () => { - // Create EthSign signature - const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); - const orderHashWithEthSignPrefixHex = signatureUtils.addSignedMessagePrefix(orderHashHex); - const orderHashWithEthSignPrefixBuffer = ethUtil.toBuffer(orderHashWithEthSignPrefixHex); - const ecSignature = ethUtil.ecsign(orderHashWithEthSignPrefixBuffer, signerPrivateKey); - // Create 0x signature from EthSign signature - const signature = Buffer.concat([ - ethUtil.toBuffer(ecSignature.v), - ecSignature.r, - ecSignature.s, - ethUtil.toBuffer(`0x${SignatureType.EthSign}`), - ]); - const signatureHex = ethUtil.bufferToHex(signature); - // Validate signature. - // This will fail because `signerAddress` signed the message, but we're passing in `notSignerAddress` - const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( - orderHashHex, - notSignerAddress, - signatureHex, - ); - expect(isValidSignature).to.be.false(); - }); - - it('should return true when SignatureType=Wallet and signature is valid', async () => { - // Create EIP712 signature - const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); - const orderHashBuffer = ethUtil.toBuffer(orderHashHex); - const ecSignature = ethUtil.ecsign(orderHashBuffer, signerPrivateKey); - // Create 0x signature from EIP712 signature - const signature = Buffer.concat([ - ethUtil.toBuffer(ecSignature.v), - ecSignature.r, - ecSignature.s, - ethUtil.toBuffer(`0x${SignatureType.Wallet}`), - ]); - const signatureHex = ethUtil.bufferToHex(signature); - // Validate signature - const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( - orderHashHex, - testWallet.address, - signatureHex, - ); - expect(isValidSignature).to.be.true(); - }); - - it('should return false when SignatureType=Wallet and signature is invalid', async () => { - // Create EIP712 signature using a private key that does not belong to the wallet owner. - const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); - const orderHashBuffer = ethUtil.toBuffer(orderHashHex); - const notWalletOwnerPrivateKey = notSignerPrivateKey; - const ecSignature = ethUtil.ecsign(orderHashBuffer, notWalletOwnerPrivateKey); - // Create 0x signature from EIP712 signature - const signature = Buffer.concat([ - ethUtil.toBuffer(ecSignature.v), - ecSignature.r, - ecSignature.s, - ethUtil.toBuffer(`0x${SignatureType.Wallet}`), - ]); - const signatureHex = ethUtil.bufferToHex(signature); - // Validate signature - const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( - orderHashHex, - testWallet.address, - signatureHex, - ); - expect(isValidSignature).to.be.false(); - }); - - it('should revert when `isValidSignature` attempts to update state and SignatureType=Wallet', async () => { - // Create EIP712 signature - const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); - const orderHashBuffer = ethUtil.toBuffer(orderHashHex); - const ecSignature = ethUtil.ecsign(orderHashBuffer, signerPrivateKey); - // Create 0x signature from EIP712 signature - const signature = Buffer.concat([ - ethUtil.toBuffer(ecSignature.v), - ecSignature.r, - ecSignature.s, - ethUtil.toBuffer(`0x${SignatureType.Wallet}`), - ]); - const signatureHex = ethUtil.bufferToHex(signature); - await expectContractCallFailedAsync( - signatureValidator.publicIsValidSignature.callAsync( - orderHashHex, - maliciousWallet.address, - signatureHex, - ), - RevertReason.WalletError, - ); - }); - - it('should return true when SignatureType=Validator, signature is valid and validator is approved', async () => { - const validatorAddress = ethUtil.toBuffer(`${testValidator.address}`); - const signatureType = ethUtil.toBuffer(`0x${SignatureType.Validator}`); - const signature = Buffer.concat([validatorAddress, signatureType]); - const signatureHex = ethUtil.bufferToHex(signature); - const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); - const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( - orderHashHex, - signerAddress, - signatureHex, - ); - expect(isValidSignature).to.be.true(); - }); - - it('should return false when SignatureType=Validator, signature is invalid and validator is approved', async () => { - const validatorAddress = ethUtil.toBuffer(`${testValidator.address}`); - const signatureType = ethUtil.toBuffer(`0x${SignatureType.Validator}`); - const signature = Buffer.concat([validatorAddress, signatureType]); - const signatureHex = ethUtil.bufferToHex(signature); - const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); - // This will return false because we signed the message with `signerAddress`, but - // are validating against `notSignerAddress` - const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( - orderHashHex, - notSignerAddress, - signatureHex, - ); - expect(isValidSignature).to.be.false(); - }); - - it('should revert when `isValidSignature` attempts to update state and SignatureType=Validator', async () => { - const validatorAddress = ethUtil.toBuffer(`${maliciousValidator.address}`); - const signatureType = ethUtil.toBuffer(`0x${SignatureType.Validator}`); - const signature = Buffer.concat([validatorAddress, signatureType]); - const signatureHex = ethUtil.bufferToHex(signature); - const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); - await expectContractCallFailedAsync( - signatureValidator.publicIsValidSignature.callAsync(orderHashHex, signerAddress, signatureHex), - RevertReason.ValidatorError, - ); - }); - it('should return false when SignatureType=Validator, signature is valid and validator is not approved', async () => { - // Set approval of signature validator to false - await web3Wrapper.awaitTransactionSuccessAsync( - await signatureValidator.setSignatureValidatorApproval.sendTransactionAsync( - testValidator.address, - false, - { from: signerAddress }, - ), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - // Validate signature - const validatorAddress = ethUtil.toBuffer(`${testValidator.address}`); - const signatureType = ethUtil.toBuffer(`0x${SignatureType.Validator}`); - const signature = Buffer.concat([validatorAddress, signatureType]); - const signatureHex = ethUtil.bufferToHex(signature); - const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); - const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( - orderHashHex, - signerAddress, - signatureHex, - ); - expect(isValidSignature).to.be.false(); - }); - - it('should return true when SignatureType=Presigned and signer has presigned hash', async () => { - // Presign hash - const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); - await web3Wrapper.awaitTransactionSuccessAsync( - await signatureValidator.preSign.sendTransactionAsync( - orderHashHex, - signedOrder.makerAddress, - signedOrder.signature, - ), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - // Validate presigned signature - const signature = ethUtil.toBuffer(`0x${SignatureType.PreSigned}`); - const signatureHex = ethUtil.bufferToHex(signature); - const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( - orderHashHex, - signedOrder.makerAddress, - signatureHex, - ); - expect(isValidSignature).to.be.true(); - }); - - it('should return false when SignatureType=Presigned and signer has not presigned hash', async () => { - const signature = ethUtil.toBuffer(`0x${SignatureType.PreSigned}`); - const signatureHex = ethUtil.bufferToHex(signature); - const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); - const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( - orderHashHex, - signedOrder.makerAddress, - signatureHex, - ); - expect(isValidSignature).to.be.false(); - }); - - it('should return true when message was signed by a Trezor One (firmware version 1.6.2)', async () => { - // messageHash translates to 0x2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b - const messageHash = ethUtil.bufferToHex(ethUtil.toBuffer('++++++++++++++++++++++++++++++++')); - const signer = '0xc28b145f10f0bcf0fc000e778615f8fd73490bad'; - const v = ethUtil.toBuffer('0x1c'); - const r = ethUtil.toBuffer('0x7b888b596ccf87f0bacab0dcb483124973f7420f169b4824d7a12534ac1e9832'); - const s = ethUtil.toBuffer('0x0c8e14f7edc01459e13965f1da56e0c23ed11e2cca932571eee1292178f90424'); - const trezorSignatureType = ethUtil.toBuffer(`0x${SignatureType.EthSign}`); - const signature = Buffer.concat([v, r, s, trezorSignatureType]); - const signatureHex = ethUtil.bufferToHex(signature); - const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( - messageHash, - signer, - signatureHex, - ); - expect(isValidSignature).to.be.true(); - }); - - it('should return true when message was signed by a Trezor Model T (firmware version 2.0.7)', async () => { - // messageHash translates to 0x2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b - const messageHash = ethUtil.bufferToHex(ethUtil.toBuffer('++++++++++++++++++++++++++++++++')); - const signer = '0x98ce6d9345e8ffa7d99ee0822272fae9d2c0e895'; - const v = ethUtil.toBuffer('0x1c'); - const r = ethUtil.toBuffer('0x423b71062c327f0ec4fe199b8da0f34185e59b4c1cb4cc23df86cac4a601fb3f'); - const s = ethUtil.toBuffer('0x53810d6591b5348b7ee08ee812c874b0fdfb942c9849d59512c90e295221091f'); - const trezorSignatureType = ethUtil.toBuffer(`0x${SignatureType.EthSign}`); - const signature = Buffer.concat([v, r, s, trezorSignatureType]); - const signatureHex = ethUtil.bufferToHex(signature); - const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( - messageHash, - signer, - signatureHex, - ); - expect(isValidSignature).to.be.true(); - }); - }); - - describe('setSignatureValidatorApproval', () => { - it('should emit a SignatureValidatorApprovalSet with correct args when a validator is approved', async () => { - const approval = true; - const res = await signatureValidatorLogDecoder.getTxWithDecodedLogsAsync( - await signatureValidator.setSignatureValidatorApproval.sendTransactionAsync( - testValidator.address, - approval, - { - from: signerAddress, - }, - ), - ); - expect(res.logs.length).to.equal(1); - const log = res.logs[0] as LogWithDecodedArgs<TestSignatureValidatorSignatureValidatorApprovalEventArgs>; - const logArgs = log.args; - expect(logArgs.signerAddress).to.equal(signerAddress); - expect(logArgs.validatorAddress).to.equal(testValidator.address); - expect(logArgs.approved).to.equal(approval); - }); - it('should emit a SignatureValidatorApprovalSet with correct args when a validator is disapproved', async () => { - const approval = false; - const res = await signatureValidatorLogDecoder.getTxWithDecodedLogsAsync( - await signatureValidator.setSignatureValidatorApproval.sendTransactionAsync( - testValidator.address, - approval, - { - from: signerAddress, - }, - ), - ); - expect(res.logs.length).to.equal(1); - const log = res.logs[0] as LogWithDecodedArgs<TestSignatureValidatorSignatureValidatorApprovalEventArgs>; - const logArgs = log.args; - expect(logArgs.signerAddress).to.equal(signerAddress); - expect(logArgs.validatorAddress).to.equal(testValidator.address); - expect(logArgs.approved).to.equal(approval); - }); - }); -}); -// tslint:disable:max-file-line-count -// tslint:enable:no-unnecessary-type-assertion diff --git a/contracts/protocol/test/exchange/transactions.ts b/contracts/protocol/test/exchange/transactions.ts deleted file mode 100644 index e8c775f3e..000000000 --- a/contracts/protocol/test/exchange/transactions.ts +++ /dev/null @@ -1,466 +0,0 @@ -import { artifacts as examplesArtifacts, ExchangeWrapperContract, WhitelistContract } from '@0x/contracts-examples'; -import { - chaiSetup, - constants, - ERC20BalancesByOwner, - expectTransactionFailedAsync, - OrderFactory, - orderUtils, - provider, - SignedTransaction, - TransactionFactory, - txDefaults, - web3Wrapper, -} from '@0x/contracts-test-utils'; -import { DummyERC20TokenContract } from '@0x/contracts-tokens'; -import { BlockchainLifecycle } from '@0x/dev-utils'; -import { assetDataUtils, generatePseudoRandomSalt } from '@0x/order-utils'; -import { OrderWithoutExchangeAddress, RevertReason, SignedOrder } from '@0x/types'; -import { BigNumber } from '@0x/utils'; -import * as chai from 'chai'; -import * as _ from 'lodash'; - -import { ERC20ProxyContract } from '../../generated-wrappers/erc20_proxy'; -import { ExchangeContract } from '../../generated-wrappers/exchange'; -import { artifacts } from '../../src/artifacts'; -import { ERC20Wrapper } from '../utils/erc20_wrapper'; -import { ExchangeWrapper } from '../utils/exchange_wrapper'; - -chaiSetup.configure(); -const expect = chai.expect; -const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); - -describe('Exchange transactions', () => { - let senderAddress: string; - let owner: string; - let makerAddress: string; - let takerAddress: string; - let feeRecipientAddress: string; - - let erc20TokenA: DummyERC20TokenContract; - let erc20TokenB: DummyERC20TokenContract; - let zrxToken: DummyERC20TokenContract; - let exchange: ExchangeContract; - let erc20Proxy: ERC20ProxyContract; - - let erc20Balances: ERC20BalancesByOwner; - let signedOrder: SignedOrder; - let signedTx: SignedTransaction; - let orderWithoutExchangeAddress: OrderWithoutExchangeAddress; - let orderFactory: OrderFactory; - let makerTransactionFactory: TransactionFactory; - let takerTransactionFactory: TransactionFactory; - let exchangeWrapper: ExchangeWrapper; - let erc20Wrapper: ERC20Wrapper; - - let defaultMakerTokenAddress: string; - let defaultTakerTokenAddress: string; - let makerPrivateKey: Buffer; - let takerPrivateKey: Buffer; - - before(async () => { - await blockchainLifecycle.startAsync(); - }); - after(async () => { - await blockchainLifecycle.revertAsync(); - }); - beforeEach(async () => { - await blockchainLifecycle.startAsync(); - }); - afterEach(async () => { - await blockchainLifecycle.revertAsync(); - }); - before(async () => { - const accounts = await web3Wrapper.getAvailableAddressesAsync(); - const usedAddresses = ([owner, senderAddress, makerAddress, takerAddress, feeRecipientAddress] = _.slice( - accounts, - 0, - 5, - )); - - erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner); - - const numDummyErc20ToDeploy = 3; - [erc20TokenA, erc20TokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync( - numDummyErc20ToDeploy, - constants.DUMMY_TOKEN_DECIMALS, - ); - erc20Proxy = await erc20Wrapper.deployProxyAsync(); - await erc20Wrapper.setBalancesAndAllowancesAsync(); - - exchange = await ExchangeContract.deployFrom0xArtifactAsync( - artifacts.Exchange, - provider, - txDefaults, - assetDataUtils.encodeERC20AssetData(zrxToken.address), - ); - exchangeWrapper = new ExchangeWrapper(exchange, provider); - await exchangeWrapper.registerAssetProxyAsync(erc20Proxy.address, owner); - - await web3Wrapper.awaitTransactionSuccessAsync( - await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(exchange.address, { from: owner }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - - defaultMakerTokenAddress = erc20TokenA.address; - defaultTakerTokenAddress = erc20TokenB.address; - - const defaultOrderParams = { - ...constants.STATIC_ORDER_PARAMS, - senderAddress, - exchangeAddress: exchange.address, - makerAddress, - feeRecipientAddress, - makerAssetData: assetDataUtils.encodeERC20AssetData(defaultMakerTokenAddress), - takerAssetData: assetDataUtils.encodeERC20AssetData(defaultTakerTokenAddress), - }; - makerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddress)]; - takerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(takerAddress)]; - orderFactory = new OrderFactory(makerPrivateKey, defaultOrderParams); - makerTransactionFactory = new TransactionFactory(makerPrivateKey, exchange.address); - takerTransactionFactory = new TransactionFactory(takerPrivateKey, exchange.address); - }); - describe('executeTransaction', () => { - describe('fillOrder', () => { - let takerAssetFillAmount: BigNumber; - beforeEach(async () => { - erc20Balances = await erc20Wrapper.getBalancesAsync(); - signedOrder = await orderFactory.newSignedOrderAsync(); - orderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress(signedOrder); - - takerAssetFillAmount = signedOrder.takerAssetAmount.div(2); - const data = exchange.fillOrder.getABIEncodedTransactionData( - orderWithoutExchangeAddress, - takerAssetFillAmount, - signedOrder.signature, - ); - signedTx = takerTransactionFactory.newSignedTransaction(data); - }); - - it('should throw if not called by specified sender', async () => { - return expectTransactionFailedAsync( - exchangeWrapper.executeTransactionAsync(signedTx, takerAddress), - RevertReason.FailedExecution, - ); - }); - - it('should transfer the correct amounts when signed by taker and called by sender', async () => { - await exchangeWrapper.executeTransactionAsync(signedTx, senderAddress); - const newBalances = await erc20Wrapper.getBalancesAsync(); - const makerAssetFillAmount = takerAssetFillAmount - .times(signedOrder.makerAssetAmount) - .dividedToIntegerBy(signedOrder.takerAssetAmount); - const makerFeePaid = signedOrder.makerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(signedOrder.makerAssetAmount); - const takerFeePaid = signedOrder.takerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(signedOrder.makerAssetAmount); - expect(newBalances[makerAddress][defaultMakerTokenAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultMakerTokenAddress].minus(makerAssetFillAmount), - ); - expect(newBalances[makerAddress][defaultTakerTokenAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultTakerTokenAddress].plus(takerAssetFillAmount), - ); - expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[makerAddress][zrxToken.address].minus(makerFeePaid), - ); - expect(newBalances[takerAddress][defaultTakerTokenAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultTakerTokenAddress].minus(takerAssetFillAmount), - ); - expect(newBalances[takerAddress][defaultMakerTokenAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultMakerTokenAddress].plus(makerAssetFillAmount), - ); - expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[takerAddress][zrxToken.address].minus(takerFeePaid), - ); - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].plus(makerFeePaid.plus(takerFeePaid)), - ); - }); - - it('should throw if the a 0x transaction with the same transactionHash has already been executed', async () => { - await exchangeWrapper.executeTransactionAsync(signedTx, senderAddress); - return expectTransactionFailedAsync( - exchangeWrapper.executeTransactionAsync(signedTx, senderAddress), - RevertReason.InvalidTxHash, - ); - }); - - it('should reset the currentContextAddress', async () => { - await exchangeWrapper.executeTransactionAsync(signedTx, senderAddress); - const currentContextAddress = await exchange.currentContextAddress.callAsync(); - expect(currentContextAddress).to.equal(constants.NULL_ADDRESS); - }); - }); - - describe('cancelOrder', () => { - beforeEach(async () => { - const data = exchange.cancelOrder.getABIEncodedTransactionData(orderWithoutExchangeAddress); - signedTx = makerTransactionFactory.newSignedTransaction(data); - }); - - it('should throw if not called by specified sender', async () => { - return expectTransactionFailedAsync( - exchangeWrapper.executeTransactionAsync(signedTx, makerAddress), - RevertReason.FailedExecution, - ); - }); - - it('should cancel the order when signed by maker and called by sender', async () => { - await exchangeWrapper.executeTransactionAsync(signedTx, senderAddress); - return expectTransactionFailedAsync( - exchangeWrapper.fillOrderAsync(signedOrder, senderAddress), - RevertReason.OrderUnfillable, - ); - }); - }); - - describe('cancelOrdersUpTo', () => { - let exchangeWrapperContract: ExchangeWrapperContract; - - before(async () => { - exchangeWrapperContract = await ExchangeWrapperContract.deployFrom0xArtifactAsync( - examplesArtifacts.ExchangeWrapper, - provider, - txDefaults, - exchange.address, - ); - }); - - it("should cancel an order if called from the order's sender", async () => { - const orderSalt = new BigNumber(0); - signedOrder = await orderFactory.newSignedOrderAsync({ - senderAddress: exchangeWrapperContract.address, - salt: orderSalt, - }); - const targetOrderEpoch = orderSalt.plus(1); - const cancelData = exchange.cancelOrdersUpTo.getABIEncodedTransactionData(targetOrderEpoch); - const signedCancelTx = makerTransactionFactory.newSignedTransaction(cancelData); - await exchangeWrapperContract.cancelOrdersUpTo.sendTransactionAsync( - targetOrderEpoch, - signedCancelTx.salt, - signedCancelTx.signature, - { - from: makerAddress, - }, - ); - - const takerAssetFillAmount = signedOrder.takerAssetAmount; - orderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress(signedOrder); - const fillData = exchange.fillOrder.getABIEncodedTransactionData( - orderWithoutExchangeAddress, - takerAssetFillAmount, - signedOrder.signature, - ); - const signedFillTx = takerTransactionFactory.newSignedTransaction(fillData); - return expectTransactionFailedAsync( - exchangeWrapperContract.fillOrder.sendTransactionAsync( - orderWithoutExchangeAddress, - takerAssetFillAmount, - signedFillTx.salt, - signedOrder.signature, - signedFillTx.signature, - { from: takerAddress }, - ), - RevertReason.FailedExecution, - ); - }); - - it("should not cancel an order if not called from the order's sender", async () => { - const orderSalt = new BigNumber(0); - signedOrder = await orderFactory.newSignedOrderAsync({ - senderAddress: exchangeWrapperContract.address, - salt: orderSalt, - }); - const targetOrderEpoch = orderSalt.plus(1); - await exchangeWrapper.cancelOrdersUpToAsync(targetOrderEpoch, makerAddress); - - erc20Balances = await erc20Wrapper.getBalancesAsync(); - const takerAssetFillAmount = signedOrder.takerAssetAmount; - orderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress(signedOrder); - const data = exchange.fillOrder.getABIEncodedTransactionData( - orderWithoutExchangeAddress, - takerAssetFillAmount, - signedOrder.signature, - ); - signedTx = takerTransactionFactory.newSignedTransaction(data); - await exchangeWrapperContract.fillOrder.sendTransactionAsync( - orderWithoutExchangeAddress, - takerAssetFillAmount, - signedTx.salt, - signedOrder.signature, - signedTx.signature, - { from: takerAddress }, - ); - - const newBalances = await erc20Wrapper.getBalancesAsync(); - const makerAssetFillAmount = takerAssetFillAmount - .times(signedOrder.makerAssetAmount) - .dividedToIntegerBy(signedOrder.takerAssetAmount); - const makerFeePaid = signedOrder.makerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(signedOrder.makerAssetAmount); - const takerFeePaid = signedOrder.takerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(signedOrder.makerAssetAmount); - expect(newBalances[makerAddress][defaultMakerTokenAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultMakerTokenAddress].minus(makerAssetFillAmount), - ); - expect(newBalances[makerAddress][defaultTakerTokenAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultTakerTokenAddress].plus(takerAssetFillAmount), - ); - expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[makerAddress][zrxToken.address].minus(makerFeePaid), - ); - expect(newBalances[takerAddress][defaultTakerTokenAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultTakerTokenAddress].minus(takerAssetFillAmount), - ); - expect(newBalances[takerAddress][defaultMakerTokenAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultMakerTokenAddress].plus(makerAssetFillAmount), - ); - expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[takerAddress][zrxToken.address].minus(takerFeePaid), - ); - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].plus(makerFeePaid.plus(takerFeePaid)), - ); - }); - }); - }); - - describe('Whitelist', () => { - let whitelist: WhitelistContract; - let whitelistOrderFactory: OrderFactory; - - before(async () => { - whitelist = await WhitelistContract.deployFrom0xArtifactAsync( - examplesArtifacts.Whitelist, - provider, - txDefaults, - exchange.address, - ); - const isApproved = true; - await web3Wrapper.awaitTransactionSuccessAsync( - await exchange.setSignatureValidatorApproval.sendTransactionAsync(whitelist.address, isApproved, { - from: takerAddress, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - const defaultOrderParams = { - ...constants.STATIC_ORDER_PARAMS, - senderAddress: whitelist.address, - exchangeAddress: exchange.address, - makerAddress, - feeRecipientAddress, - makerAssetData: assetDataUtils.encodeERC20AssetData(defaultMakerTokenAddress), - takerAssetData: assetDataUtils.encodeERC20AssetData(defaultTakerTokenAddress), - }; - whitelistOrderFactory = new OrderFactory(makerPrivateKey, defaultOrderParams); - }); - - beforeEach(async () => { - signedOrder = await whitelistOrderFactory.newSignedOrderAsync(); - erc20Balances = await erc20Wrapper.getBalancesAsync(); - }); - - it('should revert if maker has not been whitelisted', async () => { - const isApproved = true; - await web3Wrapper.awaitTransactionSuccessAsync( - await whitelist.updateWhitelistStatus.sendTransactionAsync(takerAddress, isApproved, { from: owner }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - - orderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress(signedOrder); - const takerAssetFillAmount = signedOrder.takerAssetAmount; - const salt = generatePseudoRandomSalt(); - return expectTransactionFailedAsync( - whitelist.fillOrderIfWhitelisted.sendTransactionAsync( - orderWithoutExchangeAddress, - takerAssetFillAmount, - salt, - signedOrder.signature, - { from: takerAddress }, - ), - RevertReason.MakerNotWhitelisted, - ); - }); - - it('should revert if taker has not been whitelisted', async () => { - const isApproved = true; - await web3Wrapper.awaitTransactionSuccessAsync( - await whitelist.updateWhitelistStatus.sendTransactionAsync(makerAddress, isApproved, { from: owner }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - - orderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress(signedOrder); - const takerAssetFillAmount = signedOrder.takerAssetAmount; - const salt = generatePseudoRandomSalt(); - return expectTransactionFailedAsync( - whitelist.fillOrderIfWhitelisted.sendTransactionAsync( - orderWithoutExchangeAddress, - takerAssetFillAmount, - salt, - signedOrder.signature, - { from: takerAddress }, - ), - RevertReason.TakerNotWhitelisted, - ); - }); - - it('should fill the order if maker and taker have been whitelisted', async () => { - const isApproved = true; - await web3Wrapper.awaitTransactionSuccessAsync( - await whitelist.updateWhitelistStatus.sendTransactionAsync(makerAddress, isApproved, { from: owner }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - - await web3Wrapper.awaitTransactionSuccessAsync( - await whitelist.updateWhitelistStatus.sendTransactionAsync(takerAddress, isApproved, { from: owner }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - - orderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress(signedOrder); - const takerAssetFillAmount = signedOrder.takerAssetAmount; - const salt = generatePseudoRandomSalt(); - await web3Wrapper.awaitTransactionSuccessAsync( - await whitelist.fillOrderIfWhitelisted.sendTransactionAsync( - orderWithoutExchangeAddress, - takerAssetFillAmount, - salt, - signedOrder.signature, - { from: takerAddress }, - ), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - - const newBalances = await erc20Wrapper.getBalancesAsync(); - - const makerAssetFillAmount = signedOrder.makerAssetAmount; - const makerFeePaid = signedOrder.makerFee; - const takerFeePaid = signedOrder.takerFee; - - expect(newBalances[makerAddress][defaultMakerTokenAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultMakerTokenAddress].minus(makerAssetFillAmount), - ); - expect(newBalances[makerAddress][defaultTakerTokenAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultTakerTokenAddress].plus(takerAssetFillAmount), - ); - expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[makerAddress][zrxToken.address].minus(makerFeePaid), - ); - expect(newBalances[takerAddress][defaultTakerTokenAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultTakerTokenAddress].minus(takerAssetFillAmount), - ); - expect(newBalances[takerAddress][defaultMakerTokenAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultMakerTokenAddress].plus(makerAssetFillAmount), - ); - expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[takerAddress][zrxToken.address].minus(takerFeePaid), - ); - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].plus(makerFeePaid.plus(takerFeePaid)), - ); - }); - }); -}); diff --git a/contracts/protocol/test/exchange/wrapper.ts b/contracts/protocol/test/exchange/wrapper.ts deleted file mode 100644 index 49ba5a2e5..000000000 --- a/contracts/protocol/test/exchange/wrapper.ts +++ /dev/null @@ -1,1461 +0,0 @@ -import { - chaiSetup, - constants, - ERC20BalancesByOwner, - expectTransactionFailedAsync, - getLatestBlockTimestampAsync, - increaseTimeAndMineBlockAsync, - OrderFactory, - OrderStatus, - provider, - txDefaults, - web3Wrapper, -} from '@0x/contracts-test-utils'; -import { - artifacts as tokensArtifacts, - DummyERC20TokenContract, - DummyERC721TokenContract, - ReentrantERC20TokenContract, -} from '@0x/contracts-tokens'; -import { BlockchainLifecycle } from '@0x/dev-utils'; -import { assetDataUtils, orderHashUtils } from '@0x/order-utils'; -import { RevertReason, SignedOrder } from '@0x/types'; -import { BigNumber } from '@0x/utils'; -import { Web3Wrapper } from '@0x/web3-wrapper'; -import * as chai from 'chai'; -import * as _ from 'lodash'; - -import { ERC20ProxyContract } from '../../generated-wrappers/erc20_proxy'; -import { ERC721ProxyContract } from '../../generated-wrappers/erc721_proxy'; -import { ExchangeContract } from '../../generated-wrappers/exchange'; -import { artifacts } from '../../src/artifacts'; -import { ERC20Wrapper } from '../utils/erc20_wrapper'; -import { ERC721Wrapper } from '../utils/erc721_wrapper'; -import { ExchangeWrapper } from '../utils/exchange_wrapper'; - -chaiSetup.configure(); -const expect = chai.expect; -const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); - -describe('Exchange wrappers', () => { - let makerAddress: string; - let owner: string; - let takerAddress: string; - let feeRecipientAddress: string; - - let erc20TokenA: DummyERC20TokenContract; - let erc20TokenB: DummyERC20TokenContract; - let zrxToken: DummyERC20TokenContract; - let erc721Token: DummyERC721TokenContract; - let exchange: ExchangeContract; - let erc20Proxy: ERC20ProxyContract; - let erc721Proxy: ERC721ProxyContract; - let reentrantErc20Token: ReentrantERC20TokenContract; - - let exchangeWrapper: ExchangeWrapper; - let erc20Wrapper: ERC20Wrapper; - let erc721Wrapper: ERC721Wrapper; - let erc20Balances: ERC20BalancesByOwner; - let orderFactory: OrderFactory; - - let erc721MakerAssetId: BigNumber; - let erc721TakerAssetId: BigNumber; - - let defaultMakerAssetAddress: string; - let defaultTakerAssetAddress: string; - - before(async () => { - await blockchainLifecycle.startAsync(); - }); - after(async () => { - await blockchainLifecycle.revertAsync(); - }); - before(async () => { - const accounts = await web3Wrapper.getAvailableAddressesAsync(); - const usedAddresses = ([owner, makerAddress, takerAddress, feeRecipientAddress] = _.slice(accounts, 0, 4)); - - erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner); - erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner); - - const numDummyErc20ToDeploy = 3; - [erc20TokenA, erc20TokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync( - numDummyErc20ToDeploy, - constants.DUMMY_TOKEN_DECIMALS, - ); - erc20Proxy = await erc20Wrapper.deployProxyAsync(); - await erc20Wrapper.setBalancesAndAllowancesAsync(); - - [erc721Token] = await erc721Wrapper.deployDummyTokensAsync(); - erc721Proxy = await erc721Wrapper.deployProxyAsync(); - await erc721Wrapper.setBalancesAndAllowancesAsync(); - const erc721Balances = await erc721Wrapper.getBalancesAsync(); - erc721MakerAssetId = erc721Balances[makerAddress][erc721Token.address][0]; - erc721TakerAssetId = erc721Balances[takerAddress][erc721Token.address][0]; - - exchange = await ExchangeContract.deployFrom0xArtifactAsync( - artifacts.Exchange, - provider, - txDefaults, - assetDataUtils.encodeERC20AssetData(zrxToken.address), - ); - exchangeWrapper = new ExchangeWrapper(exchange, provider); - await exchangeWrapper.registerAssetProxyAsync(erc20Proxy.address, owner); - await exchangeWrapper.registerAssetProxyAsync(erc721Proxy.address, owner); - - await web3Wrapper.awaitTransactionSuccessAsync( - await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(exchange.address, { - from: owner, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await erc721Proxy.addAuthorizedAddress.sendTransactionAsync(exchange.address, { - from: owner, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - - reentrantErc20Token = await ReentrantERC20TokenContract.deployFrom0xArtifactAsync( - tokensArtifacts.ReentrantERC20Token, - provider, - txDefaults, - exchange.address, - ); - - defaultMakerAssetAddress = erc20TokenA.address; - defaultTakerAssetAddress = erc20TokenB.address; - - const defaultOrderParams = { - ...constants.STATIC_ORDER_PARAMS, - exchangeAddress: exchange.address, - makerAddress, - feeRecipientAddress, - makerAssetData: assetDataUtils.encodeERC20AssetData(defaultMakerAssetAddress), - takerAssetData: assetDataUtils.encodeERC20AssetData(defaultTakerAssetAddress), - }; - const privateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddress)]; - orderFactory = new OrderFactory(privateKey, defaultOrderParams); - }); - beforeEach(async () => { - await blockchainLifecycle.startAsync(); - erc20Balances = await erc20Wrapper.getBalancesAsync(); - }); - afterEach(async () => { - await blockchainLifecycle.revertAsync(); - }); - describe('fillOrKillOrder', () => { - const reentrancyTest = (functionNames: string[]) => { - _.forEach(functionNames, async (functionName: string, functionId: number) => { - const description = `should not allow fillOrKillOrder to reenter the Exchange contract via ${functionName}`; - it(description, async () => { - const signedOrder = await orderFactory.newSignedOrderAsync({ - makerAssetData: assetDataUtils.encodeERC20AssetData(reentrantErc20Token.address), - }); - await web3Wrapper.awaitTransactionSuccessAsync( - await reentrantErc20Token.setCurrentFunction.sendTransactionAsync(functionId), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await expectTransactionFailedAsync( - exchangeWrapper.fillOrKillOrderAsync(signedOrder, takerAddress), - RevertReason.TransferFailed, - ); - }); - }); - }; - describe('fillOrKillOrder reentrancy tests', () => reentrancyTest(constants.FUNCTIONS_WITH_MUTEX)); - - it('should transfer the correct amounts', async () => { - const signedOrder = await orderFactory.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(200), 18), - }); - const takerAssetFillAmount = signedOrder.takerAssetAmount.div(2); - await exchangeWrapper.fillOrKillOrderAsync(signedOrder, takerAddress, { - takerAssetFillAmount, - }); - - const newBalances = await erc20Wrapper.getBalancesAsync(); - - const makerAssetFilledAmount = takerAssetFillAmount - .times(signedOrder.makerAssetAmount) - .dividedToIntegerBy(signedOrder.takerAssetAmount); - const makerFee = signedOrder.makerFee - .times(makerAssetFilledAmount) - .dividedToIntegerBy(signedOrder.makerAssetAmount); - const takerFee = signedOrder.takerFee - .times(makerAssetFilledAmount) - .dividedToIntegerBy(signedOrder.makerAssetAmount); - expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFilledAmount), - ); - expect(newBalances[makerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultTakerAssetAddress].plus(takerAssetFillAmount), - ); - expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[makerAddress][zrxToken.address].minus(makerFee), - ); - expect(newBalances[takerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount), - ); - expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFilledAmount), - ); - expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[takerAddress][zrxToken.address].minus(takerFee), - ); - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].plus(makerFee.plus(takerFee)), - ); - }); - - it('should throw if a signedOrder is expired', async () => { - const currentTimestamp = await getLatestBlockTimestampAsync(); - const signedOrder = await orderFactory.newSignedOrderAsync({ - expirationTimeSeconds: new BigNumber(currentTimestamp).minus(10), - }); - - return expectTransactionFailedAsync( - exchangeWrapper.fillOrKillOrderAsync(signedOrder, takerAddress), - RevertReason.OrderUnfillable, - ); - }); - - it('should throw if entire takerAssetFillAmount not filled', async () => { - const signedOrder = await orderFactory.newSignedOrderAsync(); - - await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { - takerAssetFillAmount: signedOrder.takerAssetAmount.div(2), - }); - - return expectTransactionFailedAsync( - exchangeWrapper.fillOrKillOrderAsync(signedOrder, takerAddress), - RevertReason.CompleteFillFailed, - ); - }); - }); - - describe('fillOrderNoThrow', () => { - const reentrancyTest = (functionNames: string[]) => { - _.forEach(functionNames, async (functionName: string, functionId: number) => { - const description = `should not allow fillOrderNoThrow to reenter the Exchange contract via ${functionName}`; - it(description, async () => { - const signedOrder = await orderFactory.newSignedOrderAsync({ - makerAssetData: assetDataUtils.encodeERC20AssetData(reentrantErc20Token.address), - }); - await web3Wrapper.awaitTransactionSuccessAsync( - await reentrantErc20Token.setCurrentFunction.sendTransactionAsync(functionId), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await exchangeWrapper.fillOrderNoThrowAsync(signedOrder, takerAddress); - const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(erc20Balances).to.deep.equal(newBalances); - }); - }); - }; - describe('fillOrderNoThrow reentrancy tests', () => reentrancyTest(constants.FUNCTIONS_WITH_MUTEX)); - - it('should transfer the correct amounts', async () => { - const signedOrder = await orderFactory.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(200), 18), - }); - const takerAssetFillAmount = signedOrder.takerAssetAmount.div(2); - - await exchangeWrapper.fillOrderNoThrowAsync(signedOrder, takerAddress, { - takerAssetFillAmount, - // HACK(albrow): We need to hardcode the gas estimate here because - // the Geth gas estimator doesn't work with the way we use - // delegatecall and swallow errors. - gas: 250000, - }); - - const newBalances = await erc20Wrapper.getBalancesAsync(); - const makerAssetFilledAmount = takerAssetFillAmount - .times(signedOrder.makerAssetAmount) - .dividedToIntegerBy(signedOrder.takerAssetAmount); - const makerFee = signedOrder.makerFee - .times(makerAssetFilledAmount) - .dividedToIntegerBy(signedOrder.makerAssetAmount); - const takerFee = signedOrder.takerFee - .times(makerAssetFilledAmount) - .dividedToIntegerBy(signedOrder.makerAssetAmount); - - expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFilledAmount), - ); - expect(newBalances[makerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultTakerAssetAddress].plus(takerAssetFillAmount), - ); - expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[makerAddress][zrxToken.address].minus(makerFee), - ); - expect(newBalances[takerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount), - ); - expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFilledAmount), - ); - expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[takerAddress][zrxToken.address].minus(takerFee), - ); - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].plus(makerFee.plus(takerFee)), - ); - }); - - it('should not change erc20Balances if maker erc20Balances are too low to fill order', async () => { - const signedOrder = await orderFactory.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100000), 18), - }); - - await exchangeWrapper.fillOrderNoThrowAsync(signedOrder, takerAddress); - const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(newBalances).to.be.deep.equal(erc20Balances); - }); - - it('should not change erc20Balances if taker erc20Balances are too low to fill order', async () => { - const signedOrder = await orderFactory.newSignedOrderAsync({ - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100000), 18), - }); - - await exchangeWrapper.fillOrderNoThrowAsync(signedOrder, takerAddress); - const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(newBalances).to.be.deep.equal(erc20Balances); - }); - - it('should not change erc20Balances if maker allowances are too low to fill order', async () => { - const signedOrder = await orderFactory.newSignedOrderAsync(); - await web3Wrapper.awaitTransactionSuccessAsync( - await erc20TokenA.approve.sendTransactionAsync(erc20Proxy.address, new BigNumber(0), { - from: makerAddress, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await exchangeWrapper.fillOrderNoThrowAsync(signedOrder, takerAddress); - await web3Wrapper.awaitTransactionSuccessAsync( - await erc20TokenA.approve.sendTransactionAsync(erc20Proxy.address, constants.INITIAL_ERC20_ALLOWANCE, { - from: makerAddress, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - - const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(newBalances).to.be.deep.equal(erc20Balances); - }); - - it('should not change erc20Balances if taker allowances are too low to fill order', async () => { - const signedOrder = await orderFactory.newSignedOrderAsync(); - await web3Wrapper.awaitTransactionSuccessAsync( - await erc20TokenB.approve.sendTransactionAsync(erc20Proxy.address, new BigNumber(0), { - from: takerAddress, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await exchangeWrapper.fillOrderNoThrowAsync(signedOrder, takerAddress); - await web3Wrapper.awaitTransactionSuccessAsync( - await erc20TokenB.approve.sendTransactionAsync(erc20Proxy.address, constants.INITIAL_ERC20_ALLOWANCE, { - from: takerAddress, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - - const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(newBalances).to.be.deep.equal(erc20Balances); - }); - - it('should not change erc20Balances if makerAssetAddress is ZRX, makerAssetAmount + makerFee > maker balance', async () => { - const makerZRXBalance = new BigNumber(erc20Balances[makerAddress][zrxToken.address]); - const signedOrder = await orderFactory.newSignedOrderAsync({ - makerAssetAmount: makerZRXBalance, - makerFee: new BigNumber(1), - makerAssetData: assetDataUtils.encodeERC20AssetData(zrxToken.address), - }); - await exchangeWrapper.fillOrderNoThrowAsync(signedOrder, takerAddress); - const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(newBalances).to.be.deep.equal(erc20Balances); - }); - - it('should not change erc20Balances if makerAssetAddress is ZRX, makerAssetAmount + makerFee > maker allowance', async () => { - const makerZRXAllowance = await zrxToken.allowance.callAsync(makerAddress, erc20Proxy.address); - const signedOrder = await orderFactory.newSignedOrderAsync({ - makerAssetAmount: new BigNumber(makerZRXAllowance), - makerFee: new BigNumber(1), - makerAssetData: assetDataUtils.encodeERC20AssetData(zrxToken.address), - }); - await exchangeWrapper.fillOrderNoThrowAsync(signedOrder, takerAddress); - const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(newBalances).to.be.deep.equal(erc20Balances); - }); - - it('should not change erc20Balances if takerAssetAddress is ZRX, takerAssetAmount + takerFee > taker balance', async () => { - const takerZRXBalance = new BigNumber(erc20Balances[takerAddress][zrxToken.address]); - const signedOrder = await orderFactory.newSignedOrderAsync({ - takerAssetAmount: takerZRXBalance, - takerFee: new BigNumber(1), - takerAssetData: assetDataUtils.encodeERC20AssetData(zrxToken.address), - }); - await exchangeWrapper.fillOrderNoThrowAsync(signedOrder, takerAddress); - const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(newBalances).to.be.deep.equal(erc20Balances); - }); - - it('should not change erc20Balances if takerAssetAddress is ZRX, takerAssetAmount + takerFee > taker allowance', async () => { - const takerZRXAllowance = await zrxToken.allowance.callAsync(takerAddress, erc20Proxy.address); - const signedOrder = await orderFactory.newSignedOrderAsync({ - takerAssetAmount: new BigNumber(takerZRXAllowance), - takerFee: new BigNumber(1), - takerAssetData: assetDataUtils.encodeERC20AssetData(zrxToken.address), - }); - await exchangeWrapper.fillOrderNoThrowAsync(signedOrder, takerAddress); - const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(newBalances).to.be.deep.equal(erc20Balances); - }); - - it('should successfully exchange ERC721 tokens', async () => { - // Construct Exchange parameters - const makerAssetId = erc721MakerAssetId; - const takerAssetId = erc721TakerAssetId; - const signedOrder = await orderFactory.newSignedOrderAsync({ - makerAssetAmount: new BigNumber(1), - takerAssetAmount: new BigNumber(1), - makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId), - takerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, takerAssetId), - }); - // Verify pre-conditions - const initialOwnerMakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId); - expect(initialOwnerMakerAsset).to.be.bignumber.equal(makerAddress); - const initialOwnerTakerAsset = await erc721Token.ownerOf.callAsync(takerAssetId); - expect(initialOwnerTakerAsset).to.be.bignumber.equal(takerAddress); - // Call Exchange - const takerAssetFillAmount = signedOrder.takerAssetAmount; - await exchangeWrapper.fillOrderNoThrowAsync(signedOrder, takerAddress, { - takerAssetFillAmount, - // HACK(albrow): We need to hardcode the gas estimate here because - // the Geth gas estimator doesn't work with the way we use - // delegatecall and swallow errors. - gas: 280000, - }); - // Verify post-conditions - const newOwnerMakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId); - expect(newOwnerMakerAsset).to.be.bignumber.equal(takerAddress); - const newOwnerTakerAsset = await erc721Token.ownerOf.callAsync(takerAssetId); - expect(newOwnerTakerAsset).to.be.bignumber.equal(makerAddress); - }); - }); - - describe('batch functions', () => { - let signedOrders: SignedOrder[]; - beforeEach(async () => { - signedOrders = [ - await orderFactory.newSignedOrderAsync(), - await orderFactory.newSignedOrderAsync(), - await orderFactory.newSignedOrderAsync(), - ]; - }); - - describe('batchFillOrders', () => { - const reentrancyTest = (functionNames: string[]) => { - _.forEach(functionNames, async (functionName: string, functionId: number) => { - const description = `should not allow batchFillOrders to reenter the Exchange contract via ${functionName}`; - it(description, async () => { - const signedOrder = await orderFactory.newSignedOrderAsync({ - makerAssetData: assetDataUtils.encodeERC20AssetData(reentrantErc20Token.address), - }); - await web3Wrapper.awaitTransactionSuccessAsync( - await reentrantErc20Token.setCurrentFunction.sendTransactionAsync(functionId), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await expectTransactionFailedAsync( - exchangeWrapper.batchFillOrdersAsync([signedOrder], takerAddress), - RevertReason.TransferFailed, - ); - }); - }); - }; - describe('batchFillOrders reentrancy tests', () => reentrancyTest(constants.FUNCTIONS_WITH_MUTEX)); - - it('should transfer the correct amounts', async () => { - const takerAssetFillAmounts: BigNumber[] = []; - const makerAssetAddress = erc20TokenA.address; - const takerAssetAddress = erc20TokenB.address; - _.forEach(signedOrders, signedOrder => { - const takerAssetFillAmount = signedOrder.takerAssetAmount.div(2); - const makerAssetFilledAmount = takerAssetFillAmount - .times(signedOrder.makerAssetAmount) - .dividedToIntegerBy(signedOrder.takerAssetAmount); - const makerFee = signedOrder.makerFee - .times(makerAssetFilledAmount) - .dividedToIntegerBy(signedOrder.makerAssetAmount); - const takerFee = signedOrder.takerFee - .times(makerAssetFilledAmount) - .dividedToIntegerBy(signedOrder.makerAssetAmount); - takerAssetFillAmounts.push(takerAssetFillAmount); - erc20Balances[makerAddress][makerAssetAddress] = erc20Balances[makerAddress][ - makerAssetAddress - ].minus(makerAssetFilledAmount); - erc20Balances[makerAddress][takerAssetAddress] = erc20Balances[makerAddress][ - takerAssetAddress - ].plus(takerAssetFillAmount); - erc20Balances[makerAddress][zrxToken.address] = erc20Balances[makerAddress][zrxToken.address].minus( - makerFee, - ); - erc20Balances[takerAddress][makerAssetAddress] = erc20Balances[takerAddress][ - makerAssetAddress - ].plus(makerAssetFilledAmount); - erc20Balances[takerAddress][takerAssetAddress] = erc20Balances[takerAddress][ - takerAssetAddress - ].minus(takerAssetFillAmount); - erc20Balances[takerAddress][zrxToken.address] = erc20Balances[takerAddress][zrxToken.address].minus( - takerFee, - ); - erc20Balances[feeRecipientAddress][zrxToken.address] = erc20Balances[feeRecipientAddress][ - zrxToken.address - ].plus(makerFee.plus(takerFee)); - }); - - await exchangeWrapper.batchFillOrdersAsync(signedOrders, takerAddress, { - takerAssetFillAmounts, - }); - - const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(newBalances).to.be.deep.equal(erc20Balances); - }); - }); - - describe('batchFillOrKillOrders', () => { - const reentrancyTest = (functionNames: string[]) => { - _.forEach(functionNames, async (functionName: string, functionId: number) => { - const description = `should not allow batchFillOrKillOrders to reenter the Exchange contract via ${functionName}`; - it(description, async () => { - const signedOrder = await orderFactory.newSignedOrderAsync({ - makerAssetData: assetDataUtils.encodeERC20AssetData(reentrantErc20Token.address), - }); - await web3Wrapper.awaitTransactionSuccessAsync( - await reentrantErc20Token.setCurrentFunction.sendTransactionAsync(functionId), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await expectTransactionFailedAsync( - exchangeWrapper.batchFillOrKillOrdersAsync([signedOrder], takerAddress), - RevertReason.TransferFailed, - ); - }); - }); - }; - describe('batchFillOrKillOrders reentrancy tests', () => reentrancyTest(constants.FUNCTIONS_WITH_MUTEX)); - - it('should transfer the correct amounts', async () => { - const takerAssetFillAmounts: BigNumber[] = []; - const makerAssetAddress = erc20TokenA.address; - const takerAssetAddress = erc20TokenB.address; - _.forEach(signedOrders, signedOrder => { - const takerAssetFillAmount = signedOrder.takerAssetAmount.div(2); - const makerAssetFilledAmount = takerAssetFillAmount - .times(signedOrder.makerAssetAmount) - .dividedToIntegerBy(signedOrder.takerAssetAmount); - const makerFee = signedOrder.makerFee - .times(makerAssetFilledAmount) - .dividedToIntegerBy(signedOrder.makerAssetAmount); - const takerFee = signedOrder.takerFee - .times(makerAssetFilledAmount) - .dividedToIntegerBy(signedOrder.makerAssetAmount); - takerAssetFillAmounts.push(takerAssetFillAmount); - erc20Balances[makerAddress][makerAssetAddress] = erc20Balances[makerAddress][ - makerAssetAddress - ].minus(makerAssetFilledAmount); - erc20Balances[makerAddress][takerAssetAddress] = erc20Balances[makerAddress][ - takerAssetAddress - ].plus(takerAssetFillAmount); - erc20Balances[makerAddress][zrxToken.address] = erc20Balances[makerAddress][zrxToken.address].minus( - makerFee, - ); - erc20Balances[takerAddress][makerAssetAddress] = erc20Balances[takerAddress][ - makerAssetAddress - ].plus(makerAssetFilledAmount); - erc20Balances[takerAddress][takerAssetAddress] = erc20Balances[takerAddress][ - takerAssetAddress - ].minus(takerAssetFillAmount); - erc20Balances[takerAddress][zrxToken.address] = erc20Balances[takerAddress][zrxToken.address].minus( - takerFee, - ); - erc20Balances[feeRecipientAddress][zrxToken.address] = erc20Balances[feeRecipientAddress][ - zrxToken.address - ].plus(makerFee.plus(takerFee)); - }); - - await exchangeWrapper.batchFillOrKillOrdersAsync(signedOrders, takerAddress, { - takerAssetFillAmounts, - }); - - const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(newBalances).to.be.deep.equal(erc20Balances); - }); - - it('should throw if a single signedOrder does not fill the expected amount', async () => { - const takerAssetFillAmounts: BigNumber[] = []; - _.forEach(signedOrders, signedOrder => { - const takerAssetFillAmount = signedOrder.takerAssetAmount.div(2); - takerAssetFillAmounts.push(takerAssetFillAmount); - }); - - await exchangeWrapper.fillOrKillOrderAsync(signedOrders[0], takerAddress); - - return expectTransactionFailedAsync( - exchangeWrapper.batchFillOrKillOrdersAsync(signedOrders, takerAddress, { - takerAssetFillAmounts, - }), - RevertReason.OrderUnfillable, - ); - }); - }); - - describe('batchFillOrdersNoThrow', async () => { - const reentrancyTest = (functionNames: string[]) => { - _.forEach(functionNames, async (functionName: string, functionId: number) => { - const description = `should not allow batchFillOrdersNoThrow to reenter the Exchange contract via ${functionName}`; - it(description, async () => { - const signedOrder = await orderFactory.newSignedOrderAsync({ - makerAssetData: assetDataUtils.encodeERC20AssetData(reentrantErc20Token.address), - }); - await web3Wrapper.awaitTransactionSuccessAsync( - await reentrantErc20Token.setCurrentFunction.sendTransactionAsync(functionId), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await exchangeWrapper.batchFillOrdersNoThrowAsync([signedOrder], takerAddress); - const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(erc20Balances).to.deep.equal(newBalances); - }); - }); - }; - describe('batchFillOrdersNoThrow reentrancy tests', () => reentrancyTest(constants.FUNCTIONS_WITH_MUTEX)); - - it('should transfer the correct amounts', async () => { - const takerAssetFillAmounts: BigNumber[] = []; - const makerAssetAddress = erc20TokenA.address; - const takerAssetAddress = erc20TokenB.address; - _.forEach(signedOrders, signedOrder => { - const takerAssetFillAmount = signedOrder.takerAssetAmount.div(2); - const makerAssetFilledAmount = takerAssetFillAmount - .times(signedOrder.makerAssetAmount) - .dividedToIntegerBy(signedOrder.takerAssetAmount); - const makerFee = signedOrder.makerFee - .times(makerAssetFilledAmount) - .dividedToIntegerBy(signedOrder.makerAssetAmount); - const takerFee = signedOrder.takerFee - .times(makerAssetFilledAmount) - .dividedToIntegerBy(signedOrder.makerAssetAmount); - takerAssetFillAmounts.push(takerAssetFillAmount); - erc20Balances[makerAddress][makerAssetAddress] = erc20Balances[makerAddress][ - makerAssetAddress - ].minus(makerAssetFilledAmount); - erc20Balances[makerAddress][takerAssetAddress] = erc20Balances[makerAddress][ - takerAssetAddress - ].plus(takerAssetFillAmount); - erc20Balances[makerAddress][zrxToken.address] = erc20Balances[makerAddress][zrxToken.address].minus( - makerFee, - ); - erc20Balances[takerAddress][makerAssetAddress] = erc20Balances[takerAddress][ - makerAssetAddress - ].plus(makerAssetFilledAmount); - erc20Balances[takerAddress][takerAssetAddress] = erc20Balances[takerAddress][ - takerAssetAddress - ].minus(takerAssetFillAmount); - erc20Balances[takerAddress][zrxToken.address] = erc20Balances[takerAddress][zrxToken.address].minus( - takerFee, - ); - erc20Balances[feeRecipientAddress][zrxToken.address] = erc20Balances[feeRecipientAddress][ - zrxToken.address - ].plus(makerFee.plus(takerFee)); - }); - - await exchangeWrapper.batchFillOrdersNoThrowAsync(signedOrders, takerAddress, { - takerAssetFillAmounts, - // HACK(albrow): We need to hardcode the gas estimate here because - // the Geth gas estimator doesn't work with the way we use - // delegatecall and swallow errors. - gas: 600000, - }); - - const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(newBalances).to.be.deep.equal(erc20Balances); - }); - - it('should not throw if an order is invalid and fill the remaining orders', async () => { - const takerAssetFillAmounts: BigNumber[] = []; - const makerAssetAddress = erc20TokenA.address; - const takerAssetAddress = erc20TokenB.address; - - const invalidOrder = { - ...signedOrders[0], - signature: '0x00', - }; - const validOrders = signedOrders.slice(1); - - takerAssetFillAmounts.push(invalidOrder.takerAssetAmount.div(2)); - _.forEach(validOrders, signedOrder => { - const takerAssetFillAmount = signedOrder.takerAssetAmount.div(2); - const makerAssetFilledAmount = takerAssetFillAmount - .times(signedOrder.makerAssetAmount) - .dividedToIntegerBy(signedOrder.takerAssetAmount); - const makerFee = signedOrder.makerFee - .times(makerAssetFilledAmount) - .dividedToIntegerBy(signedOrder.makerAssetAmount); - const takerFee = signedOrder.takerFee - .times(makerAssetFilledAmount) - .dividedToIntegerBy(signedOrder.makerAssetAmount); - takerAssetFillAmounts.push(takerAssetFillAmount); - erc20Balances[makerAddress][makerAssetAddress] = erc20Balances[makerAddress][ - makerAssetAddress - ].minus(makerAssetFilledAmount); - erc20Balances[makerAddress][takerAssetAddress] = erc20Balances[makerAddress][ - takerAssetAddress - ].plus(takerAssetFillAmount); - erc20Balances[makerAddress][zrxToken.address] = erc20Balances[makerAddress][zrxToken.address].minus( - makerFee, - ); - erc20Balances[takerAddress][makerAssetAddress] = erc20Balances[takerAddress][ - makerAssetAddress - ].plus(makerAssetFilledAmount); - erc20Balances[takerAddress][takerAssetAddress] = erc20Balances[takerAddress][ - takerAssetAddress - ].minus(takerAssetFillAmount); - erc20Balances[takerAddress][zrxToken.address] = erc20Balances[takerAddress][zrxToken.address].minus( - takerFee, - ); - erc20Balances[feeRecipientAddress][zrxToken.address] = erc20Balances[feeRecipientAddress][ - zrxToken.address - ].plus(makerFee.plus(takerFee)); - }); - - const newOrders = [invalidOrder, ...validOrders]; - await exchangeWrapper.batchFillOrdersNoThrowAsync(newOrders, takerAddress, { - takerAssetFillAmounts, - // HACK(albrow): We need to hardcode the gas estimate here because - // the Geth gas estimator doesn't work with the way we use - // delegatecall and swallow errors. - gas: 450000, - }); - - const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(newBalances).to.be.deep.equal(erc20Balances); - }); - }); - - describe('marketSellOrders', () => { - const reentrancyTest = (functionNames: string[]) => { - _.forEach(functionNames, async (functionName: string, functionId: number) => { - const description = `should not allow marketSellOrders to reenter the Exchange contract via ${functionName}`; - it(description, async () => { - const signedOrder = await orderFactory.newSignedOrderAsync({ - makerAssetData: assetDataUtils.encodeERC20AssetData(reentrantErc20Token.address), - }); - await web3Wrapper.awaitTransactionSuccessAsync( - await reentrantErc20Token.setCurrentFunction.sendTransactionAsync(functionId), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await expectTransactionFailedAsync( - exchangeWrapper.marketSellOrdersAsync([signedOrder], takerAddress, { - takerAssetFillAmount: signedOrder.takerAssetAmount, - }), - RevertReason.TransferFailed, - ); - }); - }); - }; - describe('marketSellOrders reentrancy tests', () => reentrancyTest(constants.FUNCTIONS_WITH_MUTEX)); - - it('should stop when the entire takerAssetFillAmount is filled', async () => { - const takerAssetFillAmount = signedOrders[0].takerAssetAmount.plus( - signedOrders[1].takerAssetAmount.div(2), - ); - await exchangeWrapper.marketSellOrdersAsync(signedOrders, takerAddress, { - takerAssetFillAmount, - }); - - const newBalances = await erc20Wrapper.getBalancesAsync(); - - const makerAssetFilledAmount = signedOrders[0].makerAssetAmount.plus( - signedOrders[1].makerAssetAmount.dividedToIntegerBy(2), - ); - const makerFee = signedOrders[0].makerFee.plus(signedOrders[1].makerFee.dividedToIntegerBy(2)); - const takerFee = signedOrders[0].takerFee.plus(signedOrders[1].takerFee.dividedToIntegerBy(2)); - expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFilledAmount), - ); - expect(newBalances[makerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultTakerAssetAddress].plus(takerAssetFillAmount), - ); - expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[makerAddress][zrxToken.address].minus(makerFee), - ); - expect(newBalances[takerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount), - ); - expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFilledAmount), - ); - expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[takerAddress][zrxToken.address].minus(takerFee), - ); - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].plus(makerFee.plus(takerFee)), - ); - }); - - it('should fill all signedOrders if cannot fill entire takerAssetFillAmount', async () => { - const takerAssetFillAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(100000), 18); - _.forEach(signedOrders, signedOrder => { - erc20Balances[makerAddress][defaultMakerAssetAddress] = erc20Balances[makerAddress][ - defaultMakerAssetAddress - ].minus(signedOrder.makerAssetAmount); - erc20Balances[makerAddress][defaultTakerAssetAddress] = erc20Balances[makerAddress][ - defaultTakerAssetAddress - ].plus(signedOrder.takerAssetAmount); - erc20Balances[makerAddress][zrxToken.address] = erc20Balances[makerAddress][zrxToken.address].minus( - signedOrder.makerFee, - ); - erc20Balances[takerAddress][defaultMakerAssetAddress] = erc20Balances[takerAddress][ - defaultMakerAssetAddress - ].plus(signedOrder.makerAssetAmount); - erc20Balances[takerAddress][defaultTakerAssetAddress] = erc20Balances[takerAddress][ - defaultTakerAssetAddress - ].minus(signedOrder.takerAssetAmount); - erc20Balances[takerAddress][zrxToken.address] = erc20Balances[takerAddress][zrxToken.address].minus( - signedOrder.takerFee, - ); - erc20Balances[feeRecipientAddress][zrxToken.address] = erc20Balances[feeRecipientAddress][ - zrxToken.address - ].plus(signedOrder.makerFee.plus(signedOrder.takerFee)); - }); - await exchangeWrapper.marketSellOrdersAsync(signedOrders, takerAddress, { - takerAssetFillAmount, - }); - - const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(newBalances).to.be.deep.equal(erc20Balances); - }); - - it('should throw when a signedOrder does not use the same takerAssetAddress', async () => { - signedOrders = [ - await orderFactory.newSignedOrderAsync(), - await orderFactory.newSignedOrderAsync({ - takerAssetData: assetDataUtils.encodeERC20AssetData(zrxToken.address), - }), - await orderFactory.newSignedOrderAsync(), - ]; - - return expectTransactionFailedAsync( - exchangeWrapper.marketSellOrdersAsync(signedOrders, takerAddress, { - takerAssetFillAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(1000), 18), - }), - // We simply use the takerAssetData from the first order for all orders. - // If they are not the same, the contract throws when validating the order signature - RevertReason.InvalidOrderSignature, - ); - }); - }); - - describe('marketSellOrdersNoThrow', () => { - const reentrancyTest = (functionNames: string[]) => { - _.forEach(functionNames, async (functionName: string, functionId: number) => { - const description = `should not allow marketSellOrdersNoThrow to reenter the Exchange contract via ${functionName}`; - it(description, async () => { - const signedOrder = await orderFactory.newSignedOrderAsync({ - makerAssetData: assetDataUtils.encodeERC20AssetData(reentrantErc20Token.address), - }); - await web3Wrapper.awaitTransactionSuccessAsync( - await reentrantErc20Token.setCurrentFunction.sendTransactionAsync(functionId), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await exchangeWrapper.marketSellOrdersNoThrowAsync([signedOrder], takerAddress, { - takerAssetFillAmount: signedOrder.takerAssetAmount, - }); - const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(erc20Balances).to.deep.equal(newBalances); - }); - }); - }; - describe('marketSellOrdersNoThrow reentrancy tests', () => reentrancyTest(constants.FUNCTIONS_WITH_MUTEX)); - - it('should stop when the entire takerAssetFillAmount is filled', async () => { - const takerAssetFillAmount = signedOrders[0].takerAssetAmount.plus( - signedOrders[1].takerAssetAmount.div(2), - ); - await exchangeWrapper.marketSellOrdersNoThrowAsync(signedOrders, takerAddress, { - takerAssetFillAmount, - // HACK(albrow): We need to hardcode the gas estimate here because - // the Geth gas estimator doesn't work with the way we use - // delegatecall and swallow errors. - gas: 6000000, - }); - - const newBalances = await erc20Wrapper.getBalancesAsync(); - - const makerAssetFilledAmount = signedOrders[0].makerAssetAmount.plus( - signedOrders[1].makerAssetAmount.dividedToIntegerBy(2), - ); - const makerFee = signedOrders[0].makerFee.plus(signedOrders[1].makerFee.dividedToIntegerBy(2)); - const takerFee = signedOrders[0].takerFee.plus(signedOrders[1].takerFee.dividedToIntegerBy(2)); - expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFilledAmount), - ); - expect(newBalances[makerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultTakerAssetAddress].plus(takerAssetFillAmount), - ); - expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[makerAddress][zrxToken.address].minus(makerFee), - ); - expect(newBalances[takerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount), - ); - expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFilledAmount), - ); - expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[takerAddress][zrxToken.address].minus(takerFee), - ); - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].plus(makerFee.plus(takerFee)), - ); - }); - - it('should fill all signedOrders if cannot fill entire takerAssetFillAmount', async () => { - const takerAssetFillAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(100000), 18); - _.forEach(signedOrders, signedOrder => { - erc20Balances[makerAddress][defaultMakerAssetAddress] = erc20Balances[makerAddress][ - defaultMakerAssetAddress - ].minus(signedOrder.makerAssetAmount); - erc20Balances[makerAddress][defaultTakerAssetAddress] = erc20Balances[makerAddress][ - defaultTakerAssetAddress - ].plus(signedOrder.takerAssetAmount); - erc20Balances[makerAddress][zrxToken.address] = erc20Balances[makerAddress][zrxToken.address].minus( - signedOrder.makerFee, - ); - erc20Balances[takerAddress][defaultMakerAssetAddress] = erc20Balances[takerAddress][ - defaultMakerAssetAddress - ].plus(signedOrder.makerAssetAmount); - erc20Balances[takerAddress][defaultTakerAssetAddress] = erc20Balances[takerAddress][ - defaultTakerAssetAddress - ].minus(signedOrder.takerAssetAmount); - erc20Balances[takerAddress][zrxToken.address] = erc20Balances[takerAddress][zrxToken.address].minus( - signedOrder.takerFee, - ); - erc20Balances[feeRecipientAddress][zrxToken.address] = erc20Balances[feeRecipientAddress][ - zrxToken.address - ].plus(signedOrder.makerFee.plus(signedOrder.takerFee)); - }); - await exchangeWrapper.marketSellOrdersNoThrowAsync(signedOrders, takerAddress, { - takerAssetFillAmount, - // HACK(albrow): We need to hardcode the gas estimate here because - // the Geth gas estimator doesn't work with the way we use - // delegatecall and swallow errors. - gas: 600000, - }); - - const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(newBalances).to.be.deep.equal(erc20Balances); - }); - - it('should not fill a signedOrder that does not use the same takerAssetAddress', async () => { - signedOrders = [ - await orderFactory.newSignedOrderAsync(), - await orderFactory.newSignedOrderAsync(), - await orderFactory.newSignedOrderAsync({ - takerAssetData: assetDataUtils.encodeERC20AssetData(zrxToken.address), - }), - ]; - const takerAssetFillAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(100000), 18); - const filledSignedOrders = signedOrders.slice(0, -1); - _.forEach(filledSignedOrders, signedOrder => { - erc20Balances[makerAddress][defaultMakerAssetAddress] = erc20Balances[makerAddress][ - defaultMakerAssetAddress - ].minus(signedOrder.makerAssetAmount); - erc20Balances[makerAddress][defaultTakerAssetAddress] = erc20Balances[makerAddress][ - defaultTakerAssetAddress - ].plus(signedOrder.takerAssetAmount); - erc20Balances[makerAddress][zrxToken.address] = erc20Balances[makerAddress][zrxToken.address].minus( - signedOrder.makerFee, - ); - erc20Balances[takerAddress][defaultMakerAssetAddress] = erc20Balances[takerAddress][ - defaultMakerAssetAddress - ].plus(signedOrder.makerAssetAmount); - erc20Balances[takerAddress][defaultTakerAssetAddress] = erc20Balances[takerAddress][ - defaultTakerAssetAddress - ].minus(signedOrder.takerAssetAmount); - erc20Balances[takerAddress][zrxToken.address] = erc20Balances[takerAddress][zrxToken.address].minus( - signedOrder.takerFee, - ); - erc20Balances[feeRecipientAddress][zrxToken.address] = erc20Balances[feeRecipientAddress][ - zrxToken.address - ].plus(signedOrder.makerFee.plus(signedOrder.takerFee)); - }); - await exchangeWrapper.marketSellOrdersNoThrowAsync(signedOrders, takerAddress, { - takerAssetFillAmount, - // HACK(albrow): We need to hardcode the gas estimate here because - // the Geth gas estimator doesn't work with the way we use - // delegatecall and swallow errors. - gas: 600000, - }); - - const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(newBalances).to.be.deep.equal(erc20Balances); - }); - }); - - describe('marketBuyOrders', () => { - const reentrancyTest = (functionNames: string[]) => { - _.forEach(functionNames, async (functionName: string, functionId: number) => { - const description = `should not allow marketBuyOrders to reenter the Exchange contract via ${functionName}`; - it(description, async () => { - const signedOrder = await orderFactory.newSignedOrderAsync({ - makerAssetData: assetDataUtils.encodeERC20AssetData(reentrantErc20Token.address), - }); - await web3Wrapper.awaitTransactionSuccessAsync( - await reentrantErc20Token.setCurrentFunction.sendTransactionAsync(functionId), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await expectTransactionFailedAsync( - exchangeWrapper.marketBuyOrdersAsync([signedOrder], takerAddress, { - makerAssetFillAmount: signedOrder.makerAssetAmount, - }), - RevertReason.TransferFailed, - ); - }); - }); - }; - describe('marketBuyOrders reentrancy tests', () => reentrancyTest(constants.FUNCTIONS_WITH_MUTEX)); - - it('should stop when the entire makerAssetFillAmount is filled', async () => { - const makerAssetFillAmount = signedOrders[0].makerAssetAmount.plus( - signedOrders[1].makerAssetAmount.div(2), - ); - await exchangeWrapper.marketBuyOrdersAsync(signedOrders, takerAddress, { - makerAssetFillAmount, - }); - - const newBalances = await erc20Wrapper.getBalancesAsync(); - - const makerAmountBought = signedOrders[0].takerAssetAmount.plus( - signedOrders[1].takerAssetAmount.dividedToIntegerBy(2), - ); - const makerFee = signedOrders[0].makerFee.plus(signedOrders[1].makerFee.dividedToIntegerBy(2)); - const takerFee = signedOrders[0].takerFee.plus(signedOrders[1].takerFee.dividedToIntegerBy(2)); - expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), - ); - expect(newBalances[makerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultTakerAssetAddress].plus(makerAmountBought), - ); - expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[makerAddress][zrxToken.address].minus(makerFee), - ); - expect(newBalances[takerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultTakerAssetAddress].minus(makerAmountBought), - ); - expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFillAmount), - ); - expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[takerAddress][zrxToken.address].minus(takerFee), - ); - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].plus(makerFee.plus(takerFee)), - ); - }); - - it('should fill all signedOrders if cannot fill entire makerAssetFillAmount', async () => { - const makerAssetFillAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(100000), 18); - _.forEach(signedOrders, signedOrder => { - erc20Balances[makerAddress][defaultMakerAssetAddress] = erc20Balances[makerAddress][ - defaultMakerAssetAddress - ].minus(signedOrder.makerAssetAmount); - erc20Balances[makerAddress][defaultTakerAssetAddress] = erc20Balances[makerAddress][ - defaultTakerAssetAddress - ].plus(signedOrder.takerAssetAmount); - erc20Balances[makerAddress][zrxToken.address] = erc20Balances[makerAddress][zrxToken.address].minus( - signedOrder.makerFee, - ); - erc20Balances[takerAddress][defaultMakerAssetAddress] = erc20Balances[takerAddress][ - defaultMakerAssetAddress - ].plus(signedOrder.makerAssetAmount); - erc20Balances[takerAddress][defaultTakerAssetAddress] = erc20Balances[takerAddress][ - defaultTakerAssetAddress - ].minus(signedOrder.takerAssetAmount); - erc20Balances[takerAddress][zrxToken.address] = erc20Balances[takerAddress][zrxToken.address].minus( - signedOrder.takerFee, - ); - erc20Balances[feeRecipientAddress][zrxToken.address] = erc20Balances[feeRecipientAddress][ - zrxToken.address - ].plus(signedOrder.makerFee.plus(signedOrder.takerFee)); - }); - await exchangeWrapper.marketBuyOrdersAsync(signedOrders, takerAddress, { - makerAssetFillAmount, - }); - - const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(newBalances).to.be.deep.equal(erc20Balances); - }); - - it('should throw when a signedOrder does not use the same makerAssetAddress', async () => { - signedOrders = [ - await orderFactory.newSignedOrderAsync(), - await orderFactory.newSignedOrderAsync({ - makerAssetData: assetDataUtils.encodeERC20AssetData(zrxToken.address), - }), - await orderFactory.newSignedOrderAsync(), - ]; - - return expectTransactionFailedAsync( - exchangeWrapper.marketBuyOrdersAsync(signedOrders, takerAddress, { - makerAssetFillAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(1000), 18), - }), - RevertReason.InvalidOrderSignature, - ); - }); - }); - - describe('marketBuyOrdersNoThrow', () => { - const reentrancyTest = (functionNames: string[]) => { - _.forEach(functionNames, async (functionName: string, functionId: number) => { - const description = `should not allow marketBuyOrdersNoThrow to reenter the Exchange contract via ${functionName}`; - it(description, async () => { - const signedOrder = await orderFactory.newSignedOrderAsync({ - makerAssetData: assetDataUtils.encodeERC20AssetData(reentrantErc20Token.address), - }); - await web3Wrapper.awaitTransactionSuccessAsync( - await reentrantErc20Token.setCurrentFunction.sendTransactionAsync(functionId), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await exchangeWrapper.marketBuyOrdersNoThrowAsync([signedOrder], takerAddress, { - makerAssetFillAmount: signedOrder.makerAssetAmount, - }); - const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(erc20Balances).to.deep.equal(newBalances); - }); - }); - }; - describe('marketBuyOrdersNoThrow reentrancy tests', () => reentrancyTest(constants.FUNCTIONS_WITH_MUTEX)); - - it('should stop when the entire makerAssetFillAmount is filled', async () => { - const makerAssetFillAmount = signedOrders[0].makerAssetAmount.plus( - signedOrders[1].makerAssetAmount.div(2), - ); - await exchangeWrapper.marketBuyOrdersNoThrowAsync(signedOrders, takerAddress, { - makerAssetFillAmount, - // HACK(albrow): We need to hardcode the gas estimate here because - // the Geth gas estimator doesn't work with the way we use - // delegatecall and swallow errors. - gas: 600000, - }); - - const newBalances = await erc20Wrapper.getBalancesAsync(); - - const makerAmountBought = signedOrders[0].takerAssetAmount.plus( - signedOrders[1].takerAssetAmount.dividedToIntegerBy(2), - ); - const makerFee = signedOrders[0].makerFee.plus(signedOrders[1].makerFee.dividedToIntegerBy(2)); - const takerFee = signedOrders[0].takerFee.plus(signedOrders[1].takerFee.dividedToIntegerBy(2)); - expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), - ); - expect(newBalances[makerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultTakerAssetAddress].plus(makerAmountBought), - ); - expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[makerAddress][zrxToken.address].minus(makerFee), - ); - expect(newBalances[takerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultTakerAssetAddress].minus(makerAmountBought), - ); - expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFillAmount), - ); - expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[takerAddress][zrxToken.address].minus(takerFee), - ); - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].plus(makerFee.plus(takerFee)), - ); - }); - - it('should fill all signedOrders if cannot fill entire makerAssetFillAmount', async () => { - const makerAssetFillAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(100000), 18); - _.forEach(signedOrders, signedOrder => { - erc20Balances[makerAddress][defaultMakerAssetAddress] = erc20Balances[makerAddress][ - defaultMakerAssetAddress - ].minus(signedOrder.makerAssetAmount); - erc20Balances[makerAddress][defaultTakerAssetAddress] = erc20Balances[makerAddress][ - defaultTakerAssetAddress - ].plus(signedOrder.takerAssetAmount); - erc20Balances[makerAddress][zrxToken.address] = erc20Balances[makerAddress][zrxToken.address].minus( - signedOrder.makerFee, - ); - erc20Balances[takerAddress][defaultMakerAssetAddress] = erc20Balances[takerAddress][ - defaultMakerAssetAddress - ].plus(signedOrder.makerAssetAmount); - erc20Balances[takerAddress][defaultTakerAssetAddress] = erc20Balances[takerAddress][ - defaultTakerAssetAddress - ].minus(signedOrder.takerAssetAmount); - erc20Balances[takerAddress][zrxToken.address] = erc20Balances[takerAddress][zrxToken.address].minus( - signedOrder.takerFee, - ); - erc20Balances[feeRecipientAddress][zrxToken.address] = erc20Balances[feeRecipientAddress][ - zrxToken.address - ].plus(signedOrder.makerFee.plus(signedOrder.takerFee)); - }); - await exchangeWrapper.marketBuyOrdersNoThrowAsync(signedOrders, takerAddress, { - makerAssetFillAmount, - // HACK(albrow): We need to hardcode the gas estimate here because - // the Geth gas estimator doesn't work with the way we use - // delegatecall and swallow errors. - gas: 600000, - }); - - const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(newBalances).to.be.deep.equal(erc20Balances); - }); - - it('should not fill a signedOrder that does not use the same makerAssetAddress', async () => { - signedOrders = [ - await orderFactory.newSignedOrderAsync(), - await orderFactory.newSignedOrderAsync(), - await orderFactory.newSignedOrderAsync({ - makerAssetData: assetDataUtils.encodeERC20AssetData(zrxToken.address), - }), - ]; - - const makerAssetFillAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(100000), 18); - const filledSignedOrders = signedOrders.slice(0, -1); - _.forEach(filledSignedOrders, signedOrder => { - erc20Balances[makerAddress][defaultMakerAssetAddress] = erc20Balances[makerAddress][ - defaultMakerAssetAddress - ].minus(signedOrder.makerAssetAmount); - erc20Balances[makerAddress][defaultTakerAssetAddress] = erc20Balances[makerAddress][ - defaultTakerAssetAddress - ].plus(signedOrder.takerAssetAmount); - erc20Balances[makerAddress][zrxToken.address] = erc20Balances[makerAddress][zrxToken.address].minus( - signedOrder.makerFee, - ); - erc20Balances[takerAddress][defaultMakerAssetAddress] = erc20Balances[takerAddress][ - defaultMakerAssetAddress - ].plus(signedOrder.makerAssetAmount); - erc20Balances[takerAddress][defaultTakerAssetAddress] = erc20Balances[takerAddress][ - defaultTakerAssetAddress - ].minus(signedOrder.takerAssetAmount); - erc20Balances[takerAddress][zrxToken.address] = erc20Balances[takerAddress][zrxToken.address].minus( - signedOrder.takerFee, - ); - erc20Balances[feeRecipientAddress][zrxToken.address] = erc20Balances[feeRecipientAddress][ - zrxToken.address - ].plus(signedOrder.makerFee.plus(signedOrder.takerFee)); - }); - await exchangeWrapper.marketBuyOrdersNoThrowAsync(signedOrders, takerAddress, { - makerAssetFillAmount, - // HACK(albrow): We need to hardcode the gas estimate here because - // the Geth gas estimator doesn't work with the way we use - // delegatecall and swallow errors. - gas: 600000, - }); - - const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(newBalances).to.be.deep.equal(erc20Balances); - }); - }); - - describe('batchCancelOrders', () => { - it('should be able to cancel multiple signedOrders', async () => { - const takerAssetCancelAmounts = _.map(signedOrders, signedOrder => signedOrder.takerAssetAmount); - await exchangeWrapper.batchCancelOrdersAsync(signedOrders, makerAddress); - - await exchangeWrapper.batchFillOrdersNoThrowAsync(signedOrders, takerAddress, { - takerAssetFillAmounts: takerAssetCancelAmounts, - }); - const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(erc20Balances).to.be.deep.equal(newBalances); - }); - }); - - describe('getOrdersInfo', () => { - beforeEach(async () => { - signedOrders = [ - await orderFactory.newSignedOrderAsync(), - await orderFactory.newSignedOrderAsync(), - await orderFactory.newSignedOrderAsync(), - ]; - }); - it('should get the correct information for multiple unfilled orders', async () => { - const ordersInfo = await exchangeWrapper.getOrdersInfoAsync(signedOrders); - expect(ordersInfo.length).to.be.equal(3); - _.forEach(signedOrders, (signedOrder, index) => { - const expectedOrderHash = orderHashUtils.getOrderHashHex(signedOrder); - const expectedTakerAssetFilledAmount = new BigNumber(0); - const expectedOrderStatus = OrderStatus.Fillable; - const orderInfo = ordersInfo[index]; - expect(orderInfo.orderHash).to.be.equal(expectedOrderHash); - expect(orderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal(expectedTakerAssetFilledAmount); - expect(orderInfo.orderStatus).to.equal(expectedOrderStatus); - }); - }); - it('should get the correct information for multiple partially filled orders', async () => { - const takerAssetFillAmounts = _.map(signedOrders, signedOrder => signedOrder.takerAssetAmount.div(2)); - await exchangeWrapper.batchFillOrdersAsync(signedOrders, takerAddress, { takerAssetFillAmounts }); - const ordersInfo = await exchangeWrapper.getOrdersInfoAsync(signedOrders); - expect(ordersInfo.length).to.be.equal(3); - _.forEach(signedOrders, (signedOrder, index) => { - const expectedOrderHash = orderHashUtils.getOrderHashHex(signedOrder); - const expectedTakerAssetFilledAmount = signedOrder.takerAssetAmount.div(2); - const expectedOrderStatus = OrderStatus.Fillable; - const orderInfo = ordersInfo[index]; - expect(orderInfo.orderHash).to.be.equal(expectedOrderHash); - expect(orderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal(expectedTakerAssetFilledAmount); - expect(orderInfo.orderStatus).to.equal(expectedOrderStatus); - }); - }); - it('should get the correct information for multiple fully filled orders', async () => { - await exchangeWrapper.batchFillOrdersAsync(signedOrders, takerAddress); - const ordersInfo = await exchangeWrapper.getOrdersInfoAsync(signedOrders); - expect(ordersInfo.length).to.be.equal(3); - _.forEach(signedOrders, (signedOrder, index) => { - const expectedOrderHash = orderHashUtils.getOrderHashHex(signedOrder); - const expectedTakerAssetFilledAmount = signedOrder.takerAssetAmount; - const expectedOrderStatus = OrderStatus.FullyFilled; - const orderInfo = ordersInfo[index]; - expect(orderInfo.orderHash).to.be.equal(expectedOrderHash); - expect(orderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal(expectedTakerAssetFilledAmount); - expect(orderInfo.orderStatus).to.equal(expectedOrderStatus); - }); - }); - it('should get the correct information for multiple cancelled and unfilled orders', async () => { - await exchangeWrapper.batchCancelOrdersAsync(signedOrders, makerAddress); - const ordersInfo = await exchangeWrapper.getOrdersInfoAsync(signedOrders); - expect(ordersInfo.length).to.be.equal(3); - _.forEach(signedOrders, (signedOrder, index) => { - const expectedOrderHash = orderHashUtils.getOrderHashHex(signedOrder); - const expectedTakerAssetFilledAmount = new BigNumber(0); - const expectedOrderStatus = OrderStatus.Cancelled; - const orderInfo = ordersInfo[index]; - expect(orderInfo.orderHash).to.be.equal(expectedOrderHash); - expect(orderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal(expectedTakerAssetFilledAmount); - expect(orderInfo.orderStatus).to.equal(expectedOrderStatus); - }); - }); - it('should get the correct information for multiple cancelled and partially filled orders', async () => { - const takerAssetFillAmounts = _.map(signedOrders, signedOrder => signedOrder.takerAssetAmount.div(2)); - await exchangeWrapper.batchFillOrdersAsync(signedOrders, takerAddress, { takerAssetFillAmounts }); - await exchangeWrapper.batchCancelOrdersAsync(signedOrders, makerAddress); - const ordersInfo = await exchangeWrapper.getOrdersInfoAsync(signedOrders); - expect(ordersInfo.length).to.be.equal(3); - _.forEach(signedOrders, (signedOrder, index) => { - const expectedOrderHash = orderHashUtils.getOrderHashHex(signedOrder); - const expectedTakerAssetFilledAmount = signedOrder.takerAssetAmount.div(2); - const expectedOrderStatus = OrderStatus.Cancelled; - const orderInfo = ordersInfo[index]; - expect(orderInfo.orderHash).to.be.equal(expectedOrderHash); - expect(orderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal(expectedTakerAssetFilledAmount); - expect(orderInfo.orderStatus).to.equal(expectedOrderStatus); - }); - }); - it('should get the correct information for multiple expired and unfilled orders', async () => { - const currentTimestamp = await getLatestBlockTimestampAsync(); - const timeUntilExpiration = signedOrders[0].expirationTimeSeconds.minus(currentTimestamp).toNumber(); - await increaseTimeAndMineBlockAsync(timeUntilExpiration); - const ordersInfo = await exchangeWrapper.getOrdersInfoAsync(signedOrders); - expect(ordersInfo.length).to.be.equal(3); - _.forEach(signedOrders, (signedOrder, index) => { - const expectedOrderHash = orderHashUtils.getOrderHashHex(signedOrder); - const expectedTakerAssetFilledAmount = new BigNumber(0); - const expectedOrderStatus = OrderStatus.Expired; - const orderInfo = ordersInfo[index]; - expect(orderInfo.orderHash).to.be.equal(expectedOrderHash); - expect(orderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal(expectedTakerAssetFilledAmount); - expect(orderInfo.orderStatus).to.equal(expectedOrderStatus); - }); - }); - it('should get the correct information for multiple expired and partially filled orders', async () => { - const takerAssetFillAmounts = _.map(signedOrders, signedOrder => signedOrder.takerAssetAmount.div(2)); - await exchangeWrapper.batchFillOrdersAsync(signedOrders, takerAddress, { takerAssetFillAmounts }); - const currentTimestamp = await getLatestBlockTimestampAsync(); - const timeUntilExpiration = signedOrders[0].expirationTimeSeconds.minus(currentTimestamp).toNumber(); - await increaseTimeAndMineBlockAsync(timeUntilExpiration); - const ordersInfo = await exchangeWrapper.getOrdersInfoAsync(signedOrders); - expect(ordersInfo.length).to.be.equal(3); - _.forEach(signedOrders, (signedOrder, index) => { - const expectedOrderHash = orderHashUtils.getOrderHashHex(signedOrder); - const expectedTakerAssetFilledAmount = signedOrder.takerAssetAmount.div(2); - const expectedOrderStatus = OrderStatus.Expired; - const orderInfo = ordersInfo[index]; - expect(orderInfo.orderHash).to.be.equal(expectedOrderHash); - expect(orderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal(expectedTakerAssetFilledAmount); - expect(orderInfo.orderStatus).to.equal(expectedOrderStatus); - }); - }); - it('should get the correct information for a mix of unfilled, partially filled, fully filled, cancelled, and expired orders', async () => { - const unfilledOrder = await orderFactory.newSignedOrderAsync(); - const partiallyFilledOrder = await orderFactory.newSignedOrderAsync(); - await exchangeWrapper.fillOrderAsync(partiallyFilledOrder, takerAddress, { - takerAssetFillAmount: partiallyFilledOrder.takerAssetAmount.div(2), - }); - const fullyFilledOrder = await orderFactory.newSignedOrderAsync(); - await exchangeWrapper.fillOrderAsync(fullyFilledOrder, takerAddress); - const cancelledOrder = await orderFactory.newSignedOrderAsync(); - await exchangeWrapper.cancelOrderAsync(cancelledOrder, makerAddress); - const currentTimestamp = await getLatestBlockTimestampAsync(); - const expiredOrder = await orderFactory.newSignedOrderAsync({ - expirationTimeSeconds: new BigNumber(currentTimestamp), - }); - signedOrders = [unfilledOrder, partiallyFilledOrder, fullyFilledOrder, cancelledOrder, expiredOrder]; - const ordersInfo = await exchangeWrapper.getOrdersInfoAsync(signedOrders); - expect(ordersInfo.length).to.be.equal(5); - - const expectedUnfilledOrderHash = orderHashUtils.getOrderHashHex(unfilledOrder); - const expectedUnfilledTakerAssetFilledAmount = new BigNumber(0); - const expectedUnfilledOrderStatus = OrderStatus.Fillable; - const unfilledOrderInfo = ordersInfo[0]; - expect(unfilledOrderInfo.orderHash).to.be.equal(expectedUnfilledOrderHash); - expect(unfilledOrderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal( - expectedUnfilledTakerAssetFilledAmount, - ); - expect(unfilledOrderInfo.orderStatus).to.be.equal(expectedUnfilledOrderStatus); - - const expectedPartialOrderHash = orderHashUtils.getOrderHashHex(partiallyFilledOrder); - const expectedPartialTakerAssetFilledAmount = partiallyFilledOrder.takerAssetAmount.div(2); - const expectedPartialOrderStatus = OrderStatus.Fillable; - const partialOrderInfo = ordersInfo[1]; - expect(partialOrderInfo.orderHash).to.be.equal(expectedPartialOrderHash); - expect(partialOrderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal( - expectedPartialTakerAssetFilledAmount, - ); - expect(partialOrderInfo.orderStatus).to.be.equal(expectedPartialOrderStatus); - - const expectedFilledOrderHash = orderHashUtils.getOrderHashHex(fullyFilledOrder); - const expectedFilledTakerAssetFilledAmount = fullyFilledOrder.takerAssetAmount; - const expectedFilledOrderStatus = OrderStatus.FullyFilled; - const filledOrderInfo = ordersInfo[2]; - expect(filledOrderInfo.orderHash).to.be.equal(expectedFilledOrderHash); - expect(filledOrderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal( - expectedFilledTakerAssetFilledAmount, - ); - expect(filledOrderInfo.orderStatus).to.be.equal(expectedFilledOrderStatus); - - const expectedCancelledOrderHash = orderHashUtils.getOrderHashHex(cancelledOrder); - const expectedCancelledTakerAssetFilledAmount = new BigNumber(0); - const expectedCancelledOrderStatus = OrderStatus.Cancelled; - const cancelledOrderInfo = ordersInfo[3]; - expect(cancelledOrderInfo.orderHash).to.be.equal(expectedCancelledOrderHash); - expect(cancelledOrderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal( - expectedCancelledTakerAssetFilledAmount, - ); - expect(cancelledOrderInfo.orderStatus).to.be.equal(expectedCancelledOrderStatus); - - const expectedExpiredOrderHash = orderHashUtils.getOrderHashHex(expiredOrder); - const expectedExpiredTakerAssetFilledAmount = new BigNumber(0); - const expectedExpiredOrderStatus = OrderStatus.Expired; - const expiredOrderInfo = ordersInfo[4]; - expect(expiredOrderInfo.orderHash).to.be.equal(expectedExpiredOrderHash); - expect(expiredOrderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal( - expectedExpiredTakerAssetFilledAmount, - ); - expect(expiredOrderInfo.orderStatus).to.be.equal(expectedExpiredOrderStatus); - }); - }); - }); -}); // tslint:disable-line:max-file-line-count |