diff options
Diffstat (limited to 'packages/contracts/src')
-rw-r--r-- | packages/contracts/src/utils/address_utils.ts | 4 | ||||
-rw-r--r-- | packages/contracts/src/utils/constants.ts | 16 | ||||
-rw-r--r-- | packages/contracts/src/utils/coverage.ts | 21 | ||||
-rw-r--r-- | packages/contracts/src/utils/erc20_wrapper.ts | 8 | ||||
-rw-r--r-- | packages/contracts/src/utils/erc721_wrapper.ts | 14 | ||||
-rw-r--r-- | packages/contracts/src/utils/exchange_wrapper.ts | 15 | ||||
-rw-r--r-- | packages/contracts/src/utils/formatters.ts | 8 | ||||
-rw-r--r-- | packages/contracts/src/utils/match_order_tester.ts | 356 | ||||
-rw-r--r-- | packages/contracts/src/utils/order_factory.ts | 9 | ||||
-rw-r--r-- | packages/contracts/src/utils/order_utils.ts | 6 | ||||
-rw-r--r-- | packages/contracts/src/utils/transaction_factory.ts | 4 | ||||
-rw-r--r-- | packages/contracts/src/utils/types.ts | 12 | ||||
-rw-r--r-- | packages/contracts/src/utils/web3_wrapper.ts | 13 |
13 files changed, 443 insertions, 43 deletions
diff --git a/packages/contracts/src/utils/address_utils.ts b/packages/contracts/src/utils/address_utils.ts index 01a7a6fd4..dc63459f9 100644 --- a/packages/contracts/src/utils/address_utils.ts +++ b/packages/contracts/src/utils/address_utils.ts @@ -1,10 +1,10 @@ -import { ZeroEx } from '0x.js'; +import { generatePseudoRandomSalt } from '@0xproject/order-utils'; import { crypto } from './crypto'; export const addressUtils = { generatePseudoRandomAddress(): string { - const randomBigNum = ZeroEx.generatePseudoRandomSalt(); + const randomBigNum = generatePseudoRandomSalt(); const randomBuff = crypto.solSHA3([randomBigNum]); const randomAddress = `0x${randomBuff.slice(0, 20).toString('hex')}`; return randomAddress; diff --git a/packages/contracts/src/utils/constants.ts b/packages/contracts/src/utils/constants.ts index 3fd2a0a3b..483340186 100644 --- a/packages/contracts/src/utils/constants.ts +++ b/packages/contracts/src/utils/constants.ts @@ -1,5 +1,5 @@ -import { ZeroEx } from '0x.js'; import { BigNumber } from '@0xproject/utils'; +import { Web3Wrapper } from '@0xproject/web3-wrapper'; import * as ethUtil from 'ethereumjs-util'; import * as _ from 'lodash'; @@ -20,6 +20,7 @@ export const constants = { INVALID_OPCODE: 'invalid opcode', REVERT: 'revert', TESTRPC_NETWORK_ID: 50, + AWAIT_TRANSACTION_MINED_MS: 100, MAX_ETHERTOKEN_WITHDRAW_GAS: 43000, MAX_TOKEN_TRANSFERFROM_GAS: 80000, MAX_TOKEN_APPROVE_GAS: 60000, @@ -30,13 +31,14 @@ export const constants = { NUM_DUMMY_ERC20_TO_DEPLOY: 3, NUM_DUMMY_ERC721_TO_DEPLOY: 1, NUM_ERC721_TOKENS_TO_MINT: 2, + NULL_ADDRESS: '0x0000000000000000000000000000000000000000', TESTRPC_PRIVATE_KEYS: _.map(TESTRPC_PRIVATE_KEYS_STRINGS, privateKeyString => ethUtil.toBuffer(privateKeyString)), - INITIAL_ERC20_BALANCE: ZeroEx.toBaseUnitAmount(new BigNumber(10000), 18), - INITIAL_ERC20_ALLOWANCE: ZeroEx.toBaseUnitAmount(new BigNumber(10000), 18), + INITIAL_ERC20_BALANCE: Web3Wrapper.toBaseUnitAmount(new BigNumber(10000), 18), + INITIAL_ERC20_ALLOWANCE: Web3Wrapper.toBaseUnitAmount(new BigNumber(10000), 18), STATIC_ORDER_PARAMS: { - makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18), - takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(200), 18), - makerFee: ZeroEx.toBaseUnitAmount(new BigNumber(1), 18), - takerFee: ZeroEx.toBaseUnitAmount(new BigNumber(1), 18), + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(200), 18), + makerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 18), + takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 18), }, }; diff --git a/packages/contracts/src/utils/coverage.ts b/packages/contracts/src/utils/coverage.ts new file mode 100644 index 000000000..a37939464 --- /dev/null +++ b/packages/contracts/src/utils/coverage.ts @@ -0,0 +1,21 @@ +import { devConstants } from '@0xproject/dev-utils'; +import { CoverageSubprovider, SolCompilerArtifactAdapter } from '@0xproject/sol-cov'; +import * as fs from 'fs'; +import * as _ from 'lodash'; + +let coverageSubprovider: CoverageSubprovider; + +export const coverage = { + getCoverageSubproviderSingleton(): CoverageSubprovider { + if (_.isUndefined(coverageSubprovider)) { + coverageSubprovider = coverage._getCoverageSubprovider(); + } + return coverageSubprovider; + }, + _getCoverageSubprovider(): CoverageSubprovider { + const defaultFromAddress = devConstants.TESTRPC_FIRST_ADDRESS; + const solCompilerArtifactAdapter = new SolCompilerArtifactAdapter(); + const subprovider = new CoverageSubprovider(solCompilerArtifactAdapter, defaultFromAddress); + return subprovider; + }, +}; diff --git a/packages/contracts/src/utils/erc20_wrapper.ts b/packages/contracts/src/utils/erc20_wrapper.ts index f12571b48..0303649a5 100644 --- a/packages/contracts/src/utils/erc20_wrapper.ts +++ b/packages/contracts/src/utils/erc20_wrapper.ts @@ -23,7 +23,7 @@ export class ERC20Wrapper { } public async deployDummyTokensAsync(): Promise<DummyERC20TokenContract[]> { this._dummyTokenContracts = await Promise.all( - _.times(constants.NUM_DUMMY_ERC20_TO_DEPLOY, () => + _.times(constants.NUM_DUMMY_ERC20_TO_DEPLOY, async () => DummyERC20TokenContract.deployFrom0xArtifactAsync( artifacts.DummyERC20Token, this._provider, @@ -45,7 +45,7 @@ export class ERC20Wrapper { ); return this._proxyContract; } - public async setBalancesAndAllowancesAsync() { + public async setBalancesAndAllowancesAsync(): Promise<void> { this._validateDummyTokenContractsExistOrThrow(); this._validateProxyContractExistsOrThrow(); const setBalancePromises: Array<Promise<string>> = []; @@ -103,12 +103,12 @@ export class ERC20Wrapper { const tokenAddresses = _.map(this._dummyTokenContracts, dummyTokenContract => dummyTokenContract.address); return tokenAddresses; } - private _validateDummyTokenContractsExistOrThrow() { + private _validateDummyTokenContractsExistOrThrow(): void { if (_.isUndefined(this._dummyTokenContracts)) { throw new Error('Dummy ERC20 tokens not yet deployed, please call "deployDummyTokensAsync"'); } } - private _validateProxyContractExistsOrThrow() { + private _validateProxyContractExistsOrThrow(): void { if (_.isUndefined(this._proxyContract)) { throw new Error('ERC20 proxy contract not yet deployed, please call "deployProxyAsync"'); } diff --git a/packages/contracts/src/utils/erc721_wrapper.ts b/packages/contracts/src/utils/erc721_wrapper.ts index 5b5de262f..aee796e4b 100644 --- a/packages/contracts/src/utils/erc721_wrapper.ts +++ b/packages/contracts/src/utils/erc721_wrapper.ts @@ -1,4 +1,4 @@ -import { ZeroEx } from '0x.js'; +import { generatePseudoRandomSalt } from '@0xproject/order-utils'; import { Provider } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; @@ -25,7 +25,7 @@ export class ERC721Wrapper { } public async deployDummyTokensAsync(): Promise<DummyERC721TokenContract[]> { this._dummyTokenContracts = await Promise.all( - _.times(constants.NUM_DUMMY_ERC721_TO_DEPLOY, () => + _.times(constants.NUM_DUMMY_ERC721_TO_DEPLOY, async () => DummyERC721TokenContract.deployFrom0xArtifactAsync( artifacts.DummyERC721Token, this._provider, @@ -45,7 +45,7 @@ export class ERC721Wrapper { ); return this._proxyContract; } - public async setBalancesAndAllowancesAsync() { + public async setBalancesAndAllowancesAsync(): Promise<void> { this._validateDummyTokenContractsExistOrThrow(); this._validateProxyContractExistsOrThrow(); const setBalancePromises: Array<Promise<string>> = []; @@ -54,7 +54,7 @@ export class ERC721Wrapper { _.forEach(this._dummyTokenContracts, dummyTokenContract => { _.forEach(this._tokenOwnerAddresses, tokenOwnerAddress => { _.forEach(_.range(constants.NUM_ERC721_TOKENS_TO_MINT), () => { - const tokenId = ZeroEx.generatePseudoRandomSalt(); + const tokenId = generatePseudoRandomSalt(); setBalancePromises.push( dummyTokenContract.mint.sendTransactionAsync(tokenOwnerAddress, tokenId, { from: this._contractOwnerAddress, @@ -125,17 +125,17 @@ export class ERC721Wrapper { const tokenAddresses = _.map(this._dummyTokenContracts, dummyTokenContract => dummyTokenContract.address); return tokenAddresses; } - private _validateDummyTokenContractsExistOrThrow() { + private _validateDummyTokenContractsExistOrThrow(): void { if (_.isUndefined(this._dummyTokenContracts)) { throw new Error('Dummy ERC721 tokens not yet deployed, please call "deployDummyTokensAsync"'); } } - private _validateProxyContractExistsOrThrow() { + private _validateProxyContractExistsOrThrow(): void { if (_.isUndefined(this._proxyContract)) { throw new Error('ERC721 proxy contract not yet deployed, please call "deployProxyAsync"'); } } - private _validateBalancesAndAllowancesSetOrThrow() { + private _validateBalancesAndAllowancesSetOrThrow(): void { if (_.keys(this._initialTokenIdsByOwner).length === 0) { throw new Error( 'Dummy ERC721 balances and allowances not yet set, please call "setBalancesAndAllowancesAsync"', diff --git a/packages/contracts/src/utils/exchange_wrapper.ts b/packages/contracts/src/utils/exchange_wrapper.ts index 46531fa3f..0d0977ab8 100644 --- a/packages/contracts/src/utils/exchange_wrapper.ts +++ b/packages/contracts/src/utils/exchange_wrapper.ts @@ -1,5 +1,6 @@ -import { TransactionReceiptWithDecodedLogs, ZeroEx } from '0x.js'; +import { Provider, TransactionReceiptWithDecodedLogs } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; +import { Web3Wrapper } from '@0xproject/web3-wrapper'; import * as _ from 'lodash'; import * as Web3 from 'web3'; @@ -13,11 +14,11 @@ import { AssetProxyId, OrderInfo, SignedOrder, SignedTransaction } from './types export class ExchangeWrapper { private _exchange: ExchangeContract; + private _web3Wrapper: Web3Wrapper; private _logDecoder: LogDecoder = new LogDecoder(constants.TESTRPC_NETWORK_ID); - private _zeroEx: ZeroEx; - constructor(exchangeContract: ExchangeContract, zeroEx: ZeroEx) { + constructor(exchangeContract: ExchangeContract, provider: Provider) { this._exchange = exchangeContract; - this._zeroEx = zeroEx; + this._web3Wrapper = new Web3Wrapper(provider); } public async fillOrderAsync( signedOrder: SignedOrder, @@ -196,7 +197,7 @@ export class ExchangeWrapper { opts: { oldAssetProxyAddressIfExists?: string } = {}, ): Promise<TransactionReceiptWithDecodedLogs> { const oldAssetProxyAddress = _.isUndefined(opts.oldAssetProxyAddressIfExists) - ? ZeroEx.NULL_ADDRESS + ? constants.NULL_ADDRESS : opts.oldAssetProxyAddressIfExists; const txHash = await this._exchange.registerAssetProxy.sendTransactionAsync( assetProxyId, @@ -245,8 +246,8 @@ export class ExchangeWrapper { const tx = await this._getTxWithDecodedExchangeLogsAsync(txHash); return tx; } - private async _getTxWithDecodedExchangeLogsAsync(txHash: string) { - const tx = await this._zeroEx.awaitTransactionMinedAsync(txHash); + private async _getTxWithDecodedExchangeLogsAsync(txHash: string): Promise<TransactionReceiptWithDecodedLogs> { + const tx = await this._web3Wrapper.awaitTransactionMinedAsync(txHash, constants.AWAIT_TRANSACTION_MINED_MS); tx.logs = _.filter(tx.logs, log => log.address === this._exchange.address); tx.logs = _.map(tx.logs, log => this._logDecoder.decodeLogOrThrow(log)); return tx; diff --git a/packages/contracts/src/utils/formatters.ts b/packages/contracts/src/utils/formatters.ts index e706c15b5..bfa48d6f1 100644 --- a/packages/contracts/src/utils/formatters.ts +++ b/packages/contracts/src/utils/formatters.ts @@ -5,7 +5,7 @@ import { orderUtils } from './order_utils'; import { BatchCancelOrders, BatchFillOrders, MarketBuyOrders, MarketSellOrders, SignedOrder } from './types'; export const formatters = { - createBatchFill(signedOrders: SignedOrder[], takerAssetFillAmounts: BigNumber[] = []) { + createBatchFill(signedOrders: SignedOrder[], takerAssetFillAmounts: BigNumber[] = []): BatchFillOrders { const batchFill: BatchFillOrders = { orders: [], signatures: [], @@ -21,7 +21,7 @@ export const formatters = { }); return batchFill; }, - createMarketSellOrders(signedOrders: SignedOrder[], takerAssetFillAmount: BigNumber) { + createMarketSellOrders(signedOrders: SignedOrder[], takerAssetFillAmount: BigNumber): MarketSellOrders { const marketSellOrders: MarketSellOrders = { orders: [], signatures: [], @@ -34,7 +34,7 @@ export const formatters = { }); return marketSellOrders; }, - createMarketBuyOrders(signedOrders: SignedOrder[], makerAssetFillAmount: BigNumber) { + createMarketBuyOrders(signedOrders: SignedOrder[], makerAssetFillAmount: BigNumber): MarketBuyOrders { const marketBuyOrders: MarketBuyOrders = { orders: [], signatures: [], @@ -47,7 +47,7 @@ export const formatters = { }); return marketBuyOrders; }, - createBatchCancel(signedOrders: SignedOrder[]) { + createBatchCancel(signedOrders: SignedOrder[]): BatchCancelOrders { const batchCancel: BatchCancelOrders = { orders: [], }; diff --git a/packages/contracts/src/utils/match_order_tester.ts b/packages/contracts/src/utils/match_order_tester.ts new file mode 100644 index 000000000..30039937f --- /dev/null +++ b/packages/contracts/src/utils/match_order_tester.ts @@ -0,0 +1,356 @@ +import { BlockchainLifecycle } from '@0xproject/dev-utils'; +import { LogWithDecodedArgs } from '@0xproject/types'; +import { BigNumber } from '@0xproject/utils'; +import * as chai from 'chai'; +import ethUtil = require('ethereumjs-util'); +import * as _ from 'lodash'; + +import { DummyERC20TokenContract } from '../contract_wrappers/generated/dummy_e_r_c20_token'; +import { DummyERC721TokenContract } from '../contract_wrappers/generated/dummy_e_r_c721_token'; +import { ERC20ProxyContract } from '../contract_wrappers/generated/e_r_c20_proxy'; +import { ERC721ProxyContract } from '../contract_wrappers/generated/e_r_c721_proxy'; +import { + CancelContractEventArgs, + ExchangeContract, + FillContractEventArgs, +} from '../contract_wrappers/generated/exchange'; +import { assetProxyUtils } from '../utils/asset_proxy_utils'; +import { chaiSetup } from '../utils/chai_setup'; +import { constants } from '../utils/constants'; +import { crypto } from '../utils/crypto'; +import { ERC20Wrapper } from '../utils/erc20_wrapper'; +import { ERC721Wrapper } from '../utils/erc721_wrapper'; +import { ExchangeWrapper } from '../utils/exchange_wrapper'; +import { OrderFactory } from '../utils/order_factory'; +import { orderUtils } from '../utils/order_utils'; +import { + AssetProxyId, + ContractName, + ERC20BalancesByOwner, + ERC721TokenIdsByOwner, + ExchangeStatus, + SignedOrder, + TransferAmountsByMatchOrders as TransferAmounts, +} from '../utils/types'; +import { provider, web3Wrapper } from '../utils/web3_wrapper'; + +chaiSetup.configure(); +const expect = chai.expect; +const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); + +export class MatchOrderTester { + private _exchangeWrapper: ExchangeWrapper; + private _erc20Wrapper: ERC20Wrapper; + private _erc721Wrapper: ERC721Wrapper; + private _feeTokenAddress: string; + + /// @dev Compares a pair of ERC20 balances and a pair of ERC721 token owners. + /// @param expectedNewERC20BalancesByOwner Expected ERC20 balances. + /// @param realERC20BalancesByOwner Actual ERC20 balances. + /// @param expectedNewERC721TokenIdsByOwner Expected ERC721 token owners. + /// @param realERC721TokenIdsByOwner Actual ERC20 token owners. + /// @return True only if ERC20 balances match and ERC721 token owners match. + private static _compareExpectedAndRealBalances( + expectedNewERC20BalancesByOwner: ERC20BalancesByOwner, + realERC20BalancesByOwner: ERC20BalancesByOwner, + expectedNewERC721TokenIdsByOwner: ERC721TokenIdsByOwner, + realERC721TokenIdsByOwner: ERC721TokenIdsByOwner, + ): boolean { + // ERC20 Balances + const doesErc20BalancesMatch = _.isEqual(expectedNewERC20BalancesByOwner, realERC20BalancesByOwner); + if (!doesErc20BalancesMatch) { + return false; + } + // ERC721 Token Ids + const sortedExpectedNewERC721TokenIdsByOwner = _.mapValues( + expectedNewERC721TokenIdsByOwner, + tokenIdsByOwner => { + _.mapValues(tokenIdsByOwner, tokenIds => { + _.sortBy(tokenIds); + }); + }, + ); + const sortedNewERC721TokenIdsByOwner = _.mapValues(realERC721TokenIdsByOwner, tokenIdsByOwner => { + _.mapValues(tokenIdsByOwner, tokenIds => { + _.sortBy(tokenIds); + }); + }); + const doesErc721TokenIdsMatch = _.isEqual( + sortedExpectedNewERC721TokenIdsByOwner, + sortedNewERC721TokenIdsByOwner, + ); + return doesErc721TokenIdsMatch; + } + /// @dev Constructs new MatchOrderTester. + /// @param exchangeWrapper Used to call to the Exchange. + /// @param erc20Wrapper Used to fetch ERC20 balances. + /// @param erc721Wrapper Used to fetch ERC721 token owners. + /// @param feeTokenAddress Address of ERC20 fee token. + constructor( + exchangeWrapper: ExchangeWrapper, + erc20Wrapper: ERC20Wrapper, + erc721Wrapper: ERC721Wrapper, + feeTokenAddress: string, + ) { + this._exchangeWrapper = exchangeWrapper; + this._erc20Wrapper = erc20Wrapper; + this._erc721Wrapper = erc721Wrapper; + this._feeTokenAddress = feeTokenAddress; + } + /// @dev Matches two complementary orders and validates results. + /// Validation either succeeds or throws. + /// @param signedOrderLeft First matched order. + /// @param signedOrderRight Second matched order. + /// @param takerAddress Address of taker (the address who matched the two orders) + /// @param erc20BalancesByOwner Current ERC20 balances. + /// @param erc721TokenIdsByOwner Current ERC721 token owners. + /// @param initialTakerAssetFilledAmountLeft Current amount the left order has been filled. + /// @param initialTakerAssetFilledAmountRight Current amount the right order has been filled. + /// @return New ERC20 balances & ERC721 token owners. + public async matchOrdersAndVerifyBalancesAsync( + signedOrderLeft: SignedOrder, + signedOrderRight: SignedOrder, + takerAddress: string, + erc20BalancesByOwner: ERC20BalancesByOwner, + erc721TokenIdsByOwner: ERC721TokenIdsByOwner, + initialTakerAssetFilledAmountLeft?: BigNumber, + initialTakerAssetFilledAmountRight?: BigNumber, + ): Promise<[ERC20BalancesByOwner, ERC721TokenIdsByOwner]> { + // Test setup & verify preconditions + const makerAddressLeft = signedOrderLeft.makerAddress; + const makerAddressRight = signedOrderRight.makerAddress; + const feeRecipientAddressLeft = signedOrderLeft.feeRecipientAddress; + const feeRecipientAddressRight = signedOrderRight.feeRecipientAddress; + // Verify Left order preconditions + const orderTakerAssetFilledAmountLeft = await this._exchangeWrapper.getTakerAssetFilledAmountAsync( + orderUtils.getOrderHashHex(signedOrderLeft), + ); + const expectedOrderFilledAmountLeft = initialTakerAssetFilledAmountLeft + ? initialTakerAssetFilledAmountLeft + : new BigNumber(0); + expect(expectedOrderFilledAmountLeft).to.be.bignumber.equal(orderTakerAssetFilledAmountLeft); + // Verify Right order preconditions + const orderTakerAssetFilledAmountRight = await this._exchangeWrapper.getTakerAssetFilledAmountAsync( + orderUtils.getOrderHashHex(signedOrderRight), + ); + const expectedOrderFilledAmountRight = initialTakerAssetFilledAmountRight + ? initialTakerAssetFilledAmountRight + : new BigNumber(0); + expect(expectedOrderFilledAmountRight).to.be.bignumber.equal(orderTakerAssetFilledAmountRight); + // Match left & right orders + await this._exchangeWrapper.matchOrdersAsync(signedOrderLeft, signedOrderRight, takerAddress); + const newERC20BalancesByOwner = await this._erc20Wrapper.getBalancesAsync(); + const newERC721TokenIdsByOwner = await this._erc721Wrapper.getBalancesAsync(); + // Calculate expected balance changes + const expectedTransferAmounts = await this._calculateExpectedTransferAmountsAsync( + signedOrderLeft, + signedOrderRight, + orderTakerAssetFilledAmountLeft, + orderTakerAssetFilledAmountRight, + ); + let expectedERC20BalancesByOwner: ERC20BalancesByOwner; + let expectedERC721TokenIdsByOwner: ERC721TokenIdsByOwner; + [expectedERC20BalancesByOwner, expectedERC721TokenIdsByOwner] = this._calculateExpectedBalances( + signedOrderLeft, + signedOrderRight, + takerAddress, + erc20BalancesByOwner, + erc721TokenIdsByOwner, + expectedTransferAmounts, + ); + // Assert our expected balances are equal to the actual balances + const didExpectedBalancesMatchRealBalances = MatchOrderTester._compareExpectedAndRealBalances( + expectedERC20BalancesByOwner, + newERC20BalancesByOwner, + expectedERC721TokenIdsByOwner, + newERC721TokenIdsByOwner, + ); + expect(didExpectedBalancesMatchRealBalances).to.be.true(); + return [newERC20BalancesByOwner, newERC721TokenIdsByOwner]; + } + /// @dev Calculates expected transfer amounts between order makers, fee recipients, and + /// the taker when two orders are matched. + /// @param signedOrderLeft First matched order. + /// @param signedOrderRight Second matched order. + /// @param orderTakerAssetFilledAmountLeft How much left order has been filled, prior to matching orders. + /// @param orderTakerAssetFilledAmountRight How much the right order has been filled, prior to matching orders. + /// @return TransferAmounts A struct containing the expected transfer amounts. + private async _calculateExpectedTransferAmountsAsync( + signedOrderLeft: SignedOrder, + signedOrderRight: SignedOrder, + orderTakerAssetFilledAmountLeft: BigNumber, + orderTakerAssetFilledAmountRight: BigNumber, + ): Promise<TransferAmounts> { + let amountBoughtByLeftMaker = await this._exchangeWrapper.getTakerAssetFilledAmountAsync( + orderUtils.getOrderHashHex(signedOrderLeft), + ); + amountBoughtByLeftMaker = amountBoughtByLeftMaker.minus(orderTakerAssetFilledAmountLeft); + const amountSoldByLeftMaker = amountBoughtByLeftMaker + .times(signedOrderLeft.makerAssetAmount) + .dividedToIntegerBy(signedOrderLeft.takerAssetAmount); + const amountReceivedByRightMaker = amountBoughtByLeftMaker + .times(signedOrderRight.takerAssetAmount) + .dividedToIntegerBy(signedOrderRight.makerAssetAmount); + const amountReceivedByTaker = amountSoldByLeftMaker.minus(amountReceivedByRightMaker); + let amountBoughtByRightMaker = await this._exchangeWrapper.getTakerAssetFilledAmountAsync( + orderUtils.getOrderHashHex(signedOrderRight), + ); + amountBoughtByRightMaker = amountBoughtByRightMaker.minus(orderTakerAssetFilledAmountRight); + const amountSoldByRightMaker = amountBoughtByRightMaker + .times(signedOrderRight.makerAssetAmount) + .dividedToIntegerBy(signedOrderRight.takerAssetAmount); + const amountReceivedByLeftMaker = amountSoldByRightMaker; + const feePaidByLeftMaker = signedOrderLeft.makerFee + .times(amountSoldByLeftMaker) + .dividedToIntegerBy(signedOrderLeft.makerAssetAmount); + const feePaidByRightMaker = signedOrderRight.makerFee + .times(amountSoldByRightMaker) + .dividedToIntegerBy(signedOrderRight.makerAssetAmount); + const feePaidByTakerLeft = signedOrderLeft.takerFee + .times(amountSoldByLeftMaker) + .dividedToIntegerBy(signedOrderLeft.makerAssetAmount); + const feePaidByTakerRight = signedOrderRight.takerFee + .times(amountSoldByRightMaker) + .dividedToIntegerBy(signedOrderRight.makerAssetAmount); + const totalFeePaidByTaker = feePaidByTakerLeft.add(feePaidByTakerRight); + const feeReceivedLeft = feePaidByLeftMaker.add(feePaidByTakerLeft); + const feeReceivedRight = feePaidByRightMaker.add(feePaidByTakerRight); + // Return values + const expectedTransferAmounts = { + // Left Maker + amountBoughtByLeftMaker, + amountSoldByLeftMaker, + amountReceivedByLeftMaker, + feePaidByLeftMaker, + // Right Maker + amountBoughtByRightMaker, + amountSoldByRightMaker, + amountReceivedByRightMaker, + feePaidByRightMaker, + // Taker + amountReceivedByTaker, + feePaidByTakerLeft, + feePaidByTakerRight, + totalFeePaidByTaker, + // Fee Recipients + feeReceivedLeft, + feeReceivedRight, + }; + return expectedTransferAmounts; + } + /// @dev Calculates the expected balances of order makers, fee recipients, and the taker, + /// as a result of matching two orders. + /// @param signedOrderLeft First matched order. + /// @param signedOrderRight Second matched order. + /// @param takerAddress Address of taker (the address who matched the two orders) + /// @param erc20BalancesByOwner Current ERC20 balances. + /// @param erc721TokenIdsByOwner Current ERC721 token owners. + /// @param expectedTransferAmounts A struct containing the expected transfer amounts. + /// @return Expected ERC20 balances & ERC721 token owners after orders have been matched. + private _calculateExpectedBalances( + signedOrderLeft: SignedOrder, + signedOrderRight: SignedOrder, + takerAddress: string, + erc20BalancesByOwner: ERC20BalancesByOwner, + erc721TokenIdsByOwner: ERC721TokenIdsByOwner, + expectedTransferAmounts: TransferAmounts, + ): [ERC20BalancesByOwner, ERC721TokenIdsByOwner] { + const makerAddressLeft = signedOrderLeft.makerAddress; + const makerAddressRight = signedOrderRight.makerAddress; + const feeRecipientAddressLeft = signedOrderLeft.feeRecipientAddress; + const feeRecipientAddressRight = signedOrderRight.feeRecipientAddress; + // Operations are performed on copies of the balances + const expectedNewERC20BalancesByOwner = _.cloneDeep(erc20BalancesByOwner); + const expectedNewERC721TokenIdsByOwner = _.cloneDeep(erc721TokenIdsByOwner); + // Left Maker Asset (Right Taker Asset) + const makerAssetProxyIdLeft = assetProxyUtils.decodeProxyDataId(signedOrderLeft.makerAssetData); + if (makerAssetProxyIdLeft === AssetProxyId.ERC20) { + // Decode asset data + const erc20ProxyData = assetProxyUtils.decodeERC20ProxyData(signedOrderLeft.makerAssetData); + const makerAssetAddressLeft = erc20ProxyData.tokenAddress; + const takerAssetAddressRight = makerAssetAddressLeft; + // Left Maker + expectedNewERC20BalancesByOwner[makerAddressLeft][makerAssetAddressLeft] = expectedNewERC20BalancesByOwner[ + makerAddressLeft + ][makerAssetAddressLeft].minus(expectedTransferAmounts.amountSoldByLeftMaker); + // Right Maker + expectedNewERC20BalancesByOwner[makerAddressRight][ + takerAssetAddressRight + ] = expectedNewERC20BalancesByOwner[makerAddressRight][takerAssetAddressRight].add( + expectedTransferAmounts.amountReceivedByRightMaker, + ); + // Taker + expectedNewERC20BalancesByOwner[takerAddress][makerAssetAddressLeft] = expectedNewERC20BalancesByOwner[ + takerAddress + ][makerAssetAddressLeft].add(expectedTransferAmounts.amountReceivedByTaker); + } else if (makerAssetProxyIdLeft === AssetProxyId.ERC721) { + // Decode asset data + const erc721ProxyData = assetProxyUtils.decodeERC721ProxyData(signedOrderLeft.makerAssetData); + const makerAssetAddressLeft = erc721ProxyData.tokenAddress; + const makerAssetIdLeft = erc721ProxyData.tokenId; + const takerAssetAddressRight = makerAssetAddressLeft; + const takerAssetIdRight = makerAssetIdLeft; + // Left Maker + _.remove(expectedNewERC721TokenIdsByOwner[makerAddressLeft][makerAssetAddressLeft], makerAssetIdLeft); + // Right Maker + expectedNewERC721TokenIdsByOwner[makerAddressRight][takerAssetAddressRight].push(takerAssetIdRight); + // Taker: Since there is only 1 asset transferred, the taker does not receive any of the left maker asset. + } + // Left Taker Asset (Right Maker Asset) + // Note: This exchange is only between the order makers: the Taker does not receive any of the left taker asset. + const takerAssetProxyIdLeft = assetProxyUtils.decodeProxyDataId(signedOrderLeft.takerAssetData); + if (takerAssetProxyIdLeft === AssetProxyId.ERC20) { + // Decode asset data + const erc20ProxyData = assetProxyUtils.decodeERC20ProxyData(signedOrderLeft.takerAssetData); + const takerAssetAddressLeft = erc20ProxyData.tokenAddress; + const makerAssetAddressRight = takerAssetAddressLeft; + // Left Maker + expectedNewERC20BalancesByOwner[makerAddressLeft][takerAssetAddressLeft] = expectedNewERC20BalancesByOwner[ + makerAddressLeft + ][takerAssetAddressLeft].add(expectedTransferAmounts.amountReceivedByLeftMaker); + // Right Maker + expectedNewERC20BalancesByOwner[makerAddressRight][ + makerAssetAddressRight + ] = expectedNewERC20BalancesByOwner[makerAddressRight][makerAssetAddressRight].minus( + expectedTransferAmounts.amountSoldByRightMaker, + ); + } else if (takerAssetProxyIdLeft === AssetProxyId.ERC721) { + // Decode asset data + const erc721ProxyData = assetProxyUtils.decodeERC721ProxyData(signedOrderRight.makerAssetData); + const makerAssetAddressRight = erc721ProxyData.tokenAddress; + const makerAssetIdRight = erc721ProxyData.tokenId; + const takerAssetAddressLeft = makerAssetAddressRight; + const takerAssetIdLeft = makerAssetIdRight; + // Right Maker + _.remove(expectedNewERC721TokenIdsByOwner[makerAddressRight][makerAssetAddressRight], makerAssetIdRight); + // Left Maker + expectedNewERC721TokenIdsByOwner[makerAddressLeft][takerAssetAddressLeft].push(takerAssetIdLeft); + } + // Left Maker Fees + expectedNewERC20BalancesByOwner[makerAddressLeft][this._feeTokenAddress] = expectedNewERC20BalancesByOwner[ + makerAddressLeft + ][this._feeTokenAddress].minus(expectedTransferAmounts.feePaidByLeftMaker); + // Right Maker Fees + expectedNewERC20BalancesByOwner[makerAddressRight][this._feeTokenAddress] = expectedNewERC20BalancesByOwner[ + makerAddressRight + ][this._feeTokenAddress].minus(expectedTransferAmounts.feePaidByRightMaker); + // Taker Fees + expectedNewERC20BalancesByOwner[takerAddress][this._feeTokenAddress] = expectedNewERC20BalancesByOwner[ + takerAddress + ][this._feeTokenAddress].minus(expectedTransferAmounts.totalFeePaidByTaker); + // Left Fee Recipient Fees + expectedNewERC20BalancesByOwner[feeRecipientAddressLeft][ + this._feeTokenAddress + ] = expectedNewERC20BalancesByOwner[feeRecipientAddressLeft][this._feeTokenAddress].add( + expectedTransferAmounts.feeReceivedLeft, + ); + // Right Fee Recipient Fees + expectedNewERC20BalancesByOwner[feeRecipientAddressRight][ + this._feeTokenAddress + ] = expectedNewERC20BalancesByOwner[feeRecipientAddressRight][this._feeTokenAddress].add( + expectedTransferAmounts.feeReceivedRight, + ); + + return [expectedNewERC20BalancesByOwner, expectedNewERC721TokenIdsByOwner]; + } +} diff --git a/packages/contracts/src/utils/order_factory.ts b/packages/contracts/src/utils/order_factory.ts index 044e9b865..86b3d5ac7 100644 --- a/packages/contracts/src/utils/order_factory.ts +++ b/packages/contracts/src/utils/order_factory.ts @@ -1,7 +1,8 @@ -import { ZeroEx } from '0x.js'; +import { generatePseudoRandomSalt } from '@0xproject/order-utils'; import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; +import { constants } from './constants'; import { orderUtils } from './order_utils'; import { signingUtils } from './signing_utils'; import { SignatureType, SignedOrder, UnsignedOrder } from './types'; @@ -19,10 +20,10 @@ export class OrderFactory { ): SignedOrder { const randomExpiration = new BigNumber(Math.floor((Date.now() + Math.random() * 100000000000) / 1000)); const order = ({ - senderAddress: ZeroEx.NULL_ADDRESS, + senderAddress: constants.NULL_ADDRESS, expirationTimeSeconds: randomExpiration, - salt: ZeroEx.generatePseudoRandomSalt(), - takerAddress: ZeroEx.NULL_ADDRESS, + salt: generatePseudoRandomSalt(), + takerAddress: constants.NULL_ADDRESS, ...this._defaultOrderParams, ...customOrderParams, } as any) as UnsignedOrder; diff --git a/packages/contracts/src/utils/order_utils.ts b/packages/contracts/src/utils/order_utils.ts index 7a482ad9e..8adc6b735 100644 --- a/packages/contracts/src/utils/order_utils.ts +++ b/packages/contracts/src/utils/order_utils.ts @@ -4,7 +4,7 @@ import ethUtil = require('ethereumjs-util'); import * as _ from 'lodash'; import { crypto } from './crypto'; -import { OrderStruct, SignatureType, SignedOrder, UnsignedOrder } from './types'; +import { CancelOrder, MatchOrder, OrderStruct, SignatureType, SignedOrder, UnsignedOrder } from './types'; export const orderUtils = { createFill: (signedOrder: SignedOrder, takerAssetFillAmount?: BigNumber) => { @@ -15,7 +15,7 @@ export const orderUtils = { }; return fill; }, - createCancel(signedOrder: SignedOrder, takerAssetCancelAmount?: BigNumber) { + createCancel(signedOrder: SignedOrder, takerAssetCancelAmount?: BigNumber): CancelOrder { const cancel = { order: orderUtils.getOrderStruct(signedOrder), takerAssetCancelAmount: takerAssetCancelAmount || signedOrder.takerAssetAmount, @@ -80,7 +80,7 @@ export const orderUtils = { const orderHashHex = `0x${orderHashBuff.toString('hex')}`; return orderHashHex; }, - createMatchOrders(signedOrderLeft: SignedOrder, signedOrderRight: SignedOrder) { + createMatchOrders(signedOrderLeft: SignedOrder, signedOrderRight: SignedOrder): MatchOrder { const fill = { left: orderUtils.getOrderStruct(signedOrderLeft), right: orderUtils.getOrderStruct(signedOrderRight), diff --git a/packages/contracts/src/utils/transaction_factory.ts b/packages/contracts/src/utils/transaction_factory.ts index 3a4f48330..941bff96d 100644 --- a/packages/contracts/src/utils/transaction_factory.ts +++ b/packages/contracts/src/utils/transaction_factory.ts @@ -1,4 +1,4 @@ -import { ZeroEx } from '0x.js'; +import { generatePseudoRandomSalt } from '@0xproject/order-utils'; import { BigNumber } from '@0xproject/utils'; import * as ethUtil from 'ethereumjs-util'; @@ -20,7 +20,7 @@ export class TransactionFactory { data: string, signatureType: SignatureType = SignatureType.Ecrecover, ): SignedTransaction { - const salt = ZeroEx.generatePseudoRandomSalt(); + const salt = generatePseudoRandomSalt(); const txHash = crypto.solSHA3([this._exchangeAddress, salt, ethUtil.toBuffer(data)]); const signature = signingUtils.signMessage(txHash, this._privateKey, signatureType); const signedTx = { diff --git a/packages/contracts/src/utils/types.ts b/packages/contracts/src/utils/types.ts index 8d81adece..ef86b4f38 100644 --- a/packages/contracts/src/utils/types.ts +++ b/packages/contracts/src/utils/types.ts @@ -197,3 +197,15 @@ export interface ProxyData { tokenAddress?: string; data?: any; } + +export interface CancelOrder { + order: OrderStruct; + takerAssetCancelAmount: BigNumber; +} + +export interface MatchOrder { + left: OrderStruct; + right: OrderStruct; + leftSignature: string; + rightSignature: string; +} diff --git a/packages/contracts/src/utils/web3_wrapper.ts b/packages/contracts/src/utils/web3_wrapper.ts index ed1c488a2..02595506b 100644 --- a/packages/contracts/src/utils/web3_wrapper.ts +++ b/packages/contracts/src/utils/web3_wrapper.ts @@ -1,12 +1,19 @@ -import { devConstants, web3Factory } from '@0xproject/dev-utils'; +import { devConstants, env, EnvVars, web3Factory } from '@0xproject/dev-utils'; +import { prependSubprovider } from '@0xproject/subproviders'; import { Provider } from '@0xproject/types'; import { Web3Wrapper } from '@0xproject/web3-wrapper'; +import { coverage } from './coverage'; + export const txDefaults = { from: devConstants.TESTRPC_FIRST_ADDRESS, gas: devConstants.GAS_ESTIMATE, }; const providerConfigs = { shouldUseInProcessGanache: true }; -export const web3 = web3Factory.create(providerConfigs); -export const provider = web3.currentProvider; +export const provider = web3Factory.getRpcProvider(providerConfigs); +const isCoverageEnabled = env.parseBoolean(EnvVars.SolidityCoverage); +if (isCoverageEnabled) { + const coverageSubprovider = coverage.getCoverageSubproviderSingleton(); + prependSubprovider(provider, coverageSubprovider); +} export const web3Wrapper = new Web3Wrapper(provider); |