import { artifacts as erc20Artifacts, DummyERC20TokenContract } from '@0x/contracts-erc20'; import { ForwarderContract, IExchangeV2Contract } from '@0x/contracts-exchange-forwarder'; import { blockchainTests, constants, expect, getLatestBlockTimestampAsync, orderHashUtils, signingUtils, } from '@0x/contracts-test-utils'; import { assetDataUtils, generatePseudoRandomSalt } from '@0x/order-utils'; import { Order, SignatureType, SignedOrder } from '@0x/types'; import { BigNumber } from '@0x/utils'; import { Web3Wrapper } from '@0x/web3-wrapper'; import * as ethUtil from 'ethereumjs-util'; import { contractAddresses } from '../mainnet_fork_utils'; import { SignedV2Order } from './types'; blockchainTests.fork.resets('Forwarder mainnet tests', env => { const forwarder = new ForwarderContract(contractAddresses.forwarder, env.provider, env.txDefaults); const exchangeV2 = new IExchangeV2Contract(contractAddresses.exchangeV2, env.provider, env.txDefaults); const wethAssetData = assetDataUtils.encodeERC20AssetData(contractAddresses.etherToken); const v2OrderId = '0x770501f8'; let makerAddress: string; let takerAddress: string; let makerAssetData: string; let makerToken: DummyERC20TokenContract; let makerPrivateKey: Buffer; before(async () => { [makerAddress, takerAddress] = await env.web3Wrapper.getAvailableAddressesAsync(); makerToken = await DummyERC20TokenContract.deployFrom0xArtifactAsync( erc20Artifacts.DummyERC20Token, env.provider, env.txDefaults, erc20Artifacts, constants.DUMMY_TOKEN_NAME, constants.DUMMY_TOKEN_SYMBOL, constants.DUMMY_TOKEN_DECIMALS, constants.DUMMY_TOKEN_TOTAL_SUPPLY, ); await makerToken.setBalance(makerAddress, constants.INITIAL_ERC20_BALANCE).awaitTransactionSuccessAsync(); await makerToken .approve(contractAddresses.erc20Proxy, constants.INITIAL_ERC20_ALLOWANCE) .awaitTransactionSuccessAsync({ from: makerAddress }); makerAssetData = assetDataUtils.encodeERC20AssetData(makerToken.address); makerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[0]; }); async function createOrderAsync(orderParams: Partial = {}): Promise { const currentBlockTimestamp = await getLatestBlockTimestampAsync(); const fifteenMinutesInSeconds = 15 * 60; const order = { chainId: 1, exchangeAddress: contractAddresses.exchange, makerAddress, takerAddress: constants.NULL_ADDRESS, senderAddress: constants.NULL_ADDRESS, feeRecipientAddress: constants.NULL_ADDRESS, expirationTimeSeconds: new BigNumber(currentBlockTimestamp).plus(fifteenMinutesInSeconds), salt: generatePseudoRandomSalt(), makerAssetData, takerAssetData: wethAssetData, makerFeeAssetData: makerAssetData, takerFeeAssetData: wethAssetData, makerAssetAmount: constants.INITIAL_ERC20_BALANCE.dividedToIntegerBy(2), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(0.001, 18), makerFee: constants.ZERO_AMOUNT, takerFee: constants.ZERO_AMOUNT, ...orderParams, }; const orderHashHex = order.makerFeeAssetData === v2OrderId ? (await exchangeV2.getOrderInfo(order).callAsync()).orderHash : orderHashUtils.getOrderHashHex(order); const signature = `0x${signingUtils .signMessage(ethUtil.toBuffer(orderHashHex), makerPrivateKey, SignatureType.EthSign) .toString('hex')}`; return { ...order, signature, }; } describe('marketSellOrdersWithEth', () => { it('should fill a single v2 order with no fees', async () => { const order = await createOrderAsync({ makerFeeAssetData: v2OrderId }); const [wethSpentAmount, makerAssetAcquiredAmount] = await forwarder .marketSellOrdersWithEth([order], [order.signature], [], []) .callAsync({ from: takerAddress, value: order.takerAssetAmount, }); expect(wethSpentAmount).to.bignumber.eq(order.takerAssetAmount); expect(makerAssetAcquiredAmount).to.bignumber.eq(order.makerAssetAmount); }); it('should fill multiple v2 orders', async () => { const orders = [ await createOrderAsync({ makerFeeAssetData: v2OrderId }), await createOrderAsync({ makerFeeAssetData: v2OrderId }), ]; const ethSellAmount = BigNumber.sum( orders[0].takerAssetAmount, orders[1].takerAssetAmount.dividedToIntegerBy(2), ); const [wethSpentAmount, makerAssetAcquiredAmount] = await forwarder .marketSellOrdersWithEth(orders, orders.map(o => o.signature), [], []) .callAsync({ from: takerAddress, value: ethSellAmount, }); expect(wethSpentAmount).to.bignumber.eq(ethSellAmount); expect(makerAssetAcquiredAmount).to.bignumber.eq( BigNumber.sum(orders[0].makerAssetAmount, orders[1].makerAssetAmount.dividedToIntegerBy(2)), ); }); it.skip('should fill multiple v2/v3 orders', async () => { const v2Order = await createOrderAsync({ makerFeeAssetData: v2OrderId }); const v3Order = await createOrderAsync(); const protocolFee = new BigNumber(150000).times(constants.DEFAULT_GAS_PRICE); const ethSellAmount = BigNumber.sum( v2Order.takerAssetAmount, v3Order.takerAssetAmount.dividedToIntegerBy(2), ); const [wethSpentAmount, makerAssetAcquiredAmount] = await forwarder .marketSellOrdersWithEth([v2Order, v3Order], [v2Order.signature, v3Order.signature], [], []) .callAsync({ from: takerAddress, value: ethSellAmount.plus(protocolFee), gasPrice: constants.DEFAULT_GAS_PRICE, }); expect(wethSpentAmount).to.bignumber.eq(ethSellAmount.plus(protocolFee)); expect(makerAssetAcquiredAmount).to.bignumber.eq( BigNumber.sum(v2Order.makerAssetAmount, v3Order.makerAssetAmount.dividedToIntegerBy(2)), ); }); }); describe('marketBuyOrdersWithEth', () => { it('should fill a single v2 order', async () => { const order = await createOrderAsync({ makerFeeAssetData: v2OrderId }); const [wethSpentAmount, makerAssetAcquiredAmount] = await forwarder .marketBuyOrdersWithEth([order], order.makerAssetAmount, [order.signature], [], []) .callAsync({ from: takerAddress, value: order.takerAssetAmount }); expect(wethSpentAmount).to.bignumber.eq(order.takerAssetAmount); expect(makerAssetAcquiredAmount).to.bignumber.eq(order.makerAssetAmount); }); it('should fill multiple v2 orders', async () => { const orders = [ await createOrderAsync({ makerFeeAssetData: v2OrderId }), await createOrderAsync({ makerFeeAssetData: v2OrderId }), ]; const ethSellAmount = BigNumber.sum( orders[0].takerAssetAmount, orders[1].takerAssetAmount.dividedToIntegerBy(2), ); const makerAssetBuyAmount = BigNumber.sum( orders[0].makerAssetAmount, orders[1].makerAssetAmount.dividedToIntegerBy(2), ); const [wethSpentAmount, makerAssetAcquiredAmount] = await forwarder .marketBuyOrdersWithEth(orders, makerAssetBuyAmount, orders.map(o => o.signature), [], []) .callAsync({ from: takerAddress, value: ethSellAmount, }); expect(wethSpentAmount).to.bignumber.eq(ethSellAmount); expect(makerAssetAcquiredAmount).to.bignumber.eq(makerAssetBuyAmount); }); it.skip('should fill multiple v2/v3 orders', async () => { const v2Order = await createOrderAsync({ makerFeeAssetData: v2OrderId }); const v3Order = await createOrderAsync(); const protocolFee = new BigNumber(150000).times(constants.DEFAULT_GAS_PRICE); const ethSellAmount = BigNumber.sum( v2Order.takerAssetAmount, v3Order.takerAssetAmount.dividedToIntegerBy(2), ); const makerAssetBuyAmount = BigNumber.sum( v2Order.makerAssetAmount, v3Order.makerAssetAmount.dividedToIntegerBy(2), ); const [wethSpentAmount, makerAssetAcquiredAmount] = await forwarder .marketBuyOrdersWithEth( [v2Order, v3Order], makerAssetBuyAmount, [v2Order.signature, v3Order.signature], [], [], ) .callAsync({ from: takerAddress, value: ethSellAmount.plus(protocolFee), gasPrice: constants.DEFAULT_GAS_PRICE, }); expect(wethSpentAmount).to.bignumber.eq(ethSellAmount.plus(protocolFee)); expect(makerAssetAcquiredAmount).to.bignumber.eq(makerAssetBuyAmount); }); }); });