aboutsummaryrefslogtreecommitdiffstats
path: root/packages/contracts/test
diff options
context:
space:
mode:
Diffstat (limited to 'packages/contracts/test')
-rw-r--r--packages/contracts/test/asset_proxy/proxies.ts91
-rw-r--r--packages/contracts/test/exchange/core.ts24
-rw-r--r--packages/contracts/test/exchange/libs.ts16
-rw-r--r--packages/contracts/test/exchange/signature_validator.ts16
-rw-r--r--packages/contracts/test/forwarder/forwarder.ts1265
-rw-r--r--packages/contracts/test/utils/artifacts.ts10
-rw-r--r--packages/contracts/test/utils/constants.ts2
-rw-r--r--packages/contracts/test/utils/forwarder_wrapper.ts226
8 files changed, 825 insertions, 825 deletions
diff --git a/packages/contracts/test/asset_proxy/proxies.ts b/packages/contracts/test/asset_proxy/proxies.ts
index 39674a030..62215f08d 100644
--- a/packages/contracts/test/asset_proxy/proxies.ts
+++ b/packages/contracts/test/asset_proxy/proxies.ts
@@ -1,17 +1,12 @@
import { BlockchainLifecycle } from '@0xproject/dev-utils';
-import { assetDataUtils, generatePseudoRandomSalt } from '@0xproject/order-utils';
+import { assetDataUtils } from '@0xproject/order-utils';
import { RevertReason } from '@0xproject/types';
import { BigNumber } from '@0xproject/utils';
import * as chai from 'chai';
-import { LogWithDecodedArgs } from 'ethereum-types';
-import ethUtil = require('ethereumjs-util');
import * as _ from 'lodash';
import { DummyERC20TokenContract } from '../../generated_contract_wrappers/dummy_erc20_token';
-import {
- DummyERC721ReceiverContract,
- DummyERC721ReceiverTokenReceivedEventArgs,
-} from '../../generated_contract_wrappers/dummy_erc721_receiver';
+import { DummyERC721ReceiverContract } from '../../generated_contract_wrappers/dummy_erc721_receiver';
import { DummyERC721TokenContract } from '../../generated_contract_wrappers/dummy_erc721_token';
import { ERC20ProxyContract } from '../../generated_contract_wrappers/erc20_proxy';
import { ERC721ProxyContract } from '../../generated_contract_wrappers/erc721_proxy';
@@ -23,7 +18,6 @@ import { constants } from '../utils/constants';
import { ERC20Wrapper } from '../utils/erc20_wrapper';
import { ERC721Wrapper } from '../utils/erc721_wrapper';
import { LogDecoder } from '../utils/log_decoder';
-import { typeEncodingUtils } from '../utils/type_encoding_utils';
import { provider, txDefaults, web3Wrapper } from '../utils/web3_wrapper';
chaiSetup.configure();
@@ -253,7 +247,7 @@ describe('Asset Transfer Proxies', () => {
expect(newOwnerMakerAsset).to.be.bignumber.equal(takerAddress);
});
- it('should call onERC721Received when transferring to a smart contract without receiver data', async () => {
+ it('should not call onERC721Received when transferring to a smart contract', async () => {
// Construct ERC721 asset data
const encodedAssetData = assetDataUtils.encodeERC721AssetData(erc721Token.address, erc721MakerTokenId);
// Verify pre-condition
@@ -277,85 +271,12 @@ describe('Asset Transfer Proxies', () => {
}),
);
// Verify that no log was emitted by erc721 receiver
- expect(tx.logs.length).to.be.equal(1);
- const tokenReceivedLog = tx.logs[0] as LogWithDecodedArgs<DummyERC721ReceiverTokenReceivedEventArgs>;
- expect(tokenReceivedLog.args.from).to.be.equal(makerAddress);
- expect(tokenReceivedLog.args.tokenId).to.be.bignumber.equal(erc721MakerTokenId);
- expect(tokenReceivedLog.args.data).to.be.equal(constants.NULL_BYTES);
+ expect(tx.logs.length).to.be.equal(0);
// Verify transfer was successful
const newOwnerMakerAsset = await erc721Token.ownerOf.callAsync(erc721MakerTokenId);
expect(newOwnerMakerAsset).to.be.bignumber.equal(erc721Receiver.address);
});
- it('should call onERC721Received when transferring to a smart contract with receiver data', async () => {
- // Construct ERC721 asset data
- const receiverData = ethUtil.bufferToHex(typeEncodingUtils.encodeUint256(generatePseudoRandomSalt()));
- const encodedAssetData = assetDataUtils.encodeERC721AssetData(
- erc721Token.address,
- erc721MakerTokenId,
- receiverData,
- );
- // Verify pre-condition
- const ownerMakerAsset = await erc721Token.ownerOf.callAsync(erc721MakerTokenId);
- expect(ownerMakerAsset).to.be.bignumber.equal(makerAddress);
- // Perform a transfer from makerAddress to takerAddress
- const amount = new BigNumber(1);
- const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
- encodedAssetData,
- makerAddress,
- erc721Receiver.address,
- amount,
- );
- const logDecoder = new LogDecoder(web3Wrapper, erc721Receiver.address);
- const tx = await logDecoder.getTxWithDecodedLogsAsync(
- await web3Wrapper.sendTransactionAsync({
- to: erc721Proxy.address,
- data,
- from: exchangeAddress,
- gas: constants.MAX_TRANSFER_FROM_GAS,
- }),
- );
- // Validate log emitted by erc721 receiver
- expect(tx.logs.length).to.be.equal(1);
- const tokenReceivedLog = tx.logs[0] as LogWithDecodedArgs<DummyERC721ReceiverTokenReceivedEventArgs>;
- expect(tokenReceivedLog.args.from).to.be.equal(makerAddress);
- expect(tokenReceivedLog.args.tokenId).to.be.bignumber.equal(erc721MakerTokenId);
- expect(tokenReceivedLog.args.data).to.be.equal(receiverData);
- // Verify transfer was successful
- const newOwnerMakerAsset = await erc721Token.ownerOf.callAsync(erc721MakerTokenId);
- expect(newOwnerMakerAsset).to.be.bignumber.equal(erc721Receiver.address);
- });
-
- it('should throw if there is receiver data but contract does not have onERC721Received', async () => {
- // Construct ERC721 asset data
- const receiverData = ethUtil.bufferToHex(typeEncodingUtils.encodeUint256(generatePseudoRandomSalt()));
- const encodedAssetData = assetDataUtils.encodeERC721AssetData(
- erc721Token.address,
- erc721MakerTokenId,
- receiverData,
- );
- // Verify pre-condition
- const ownerMakerAsset = await erc721Token.ownerOf.callAsync(erc721MakerTokenId);
- expect(ownerMakerAsset).to.be.bignumber.equal(makerAddress);
- // Perform a transfer from makerAddress to takerAddress
- const amount = new BigNumber(1);
- const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
- encodedAssetData,
- makerAddress,
- erc20Proxy.address, // the ERC20 proxy does not have an ERC721 receiver
- amount,
- );
- return expectTransactionFailedAsync(
- web3Wrapper.sendTransactionAsync({
- to: erc721Proxy.address,
- data,
- from: exchangeAddress,
- gas: constants.MAX_TRANSFER_FROM_GAS,
- }),
- RevertReason.TransferFailed,
- );
- });
-
it('should throw if transferring 0 amount of a token', async () => {
// Construct ERC721 asset data
const encodedAssetData = assetDataUtils.encodeERC721AssetData(erc721Token.address, erc721MakerTokenId);
@@ -454,9 +375,9 @@ describe('Asset Transfer Proxies', () => {
});
});
- it('should have an id of 0x08e937fa', async () => {
+ it('should have an id of 0x02571792', async () => {
const proxyId = await erc721Proxy.getProxyId.callAsync();
- const expectedProxyId = '0x08e937fa';
+ const expectedProxyId = '0x02571792';
expect(proxyId).to.equal(expectedProxyId);
});
});
diff --git a/packages/contracts/test/exchange/core.ts b/packages/contracts/test/exchange/core.ts
index 33246a681..b324988cc 100644
--- a/packages/contracts/test/exchange/core.ts
+++ b/packages/contracts/test/exchange/core.ts
@@ -456,30 +456,6 @@ describe('Exchange core', () => {
RevertReason.RoundingError,
);
});
-
- it('should throw if assetData has a length < 132', async () => {
- // Construct Exchange parameters
- const makerAssetId = erc721MakerAssetIds[0];
- const takerAssetId = erc721TakerAssetIds[0];
- const makerAssetData = assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId).slice(0, -2);
- signedOrder = await orderFactory.newSignedOrderAsync({
- makerAssetAmount: new BigNumber(1),
- takerAssetAmount: new BigNumber(1),
- makerAssetData,
- 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.LengthGreaterThan131Required,
- );
- });
});
describe('getOrderInfo', () => {
diff --git a/packages/contracts/test/exchange/libs.ts b/packages/contracts/test/exchange/libs.ts
index 2e95fa96c..51794d8a3 100644
--- a/packages/contracts/test/exchange/libs.ts
+++ b/packages/contracts/test/exchange/libs.ts
@@ -4,6 +4,7 @@ import { SignedOrder } from '@0xproject/types';
import { BigNumber } from '@0xproject/utils';
import * as chai from 'chai';
+import { TestConstantsContract } from '../../generated_contract_wrappers/test_constants';
import { TestLibsContract } from '../../generated_contract_wrappers/test_libs';
import { addressUtils } from '../utils/address_utils';
import { artifacts } from '../utils/artifacts';
@@ -21,6 +22,7 @@ describe('Exchange libs', () => {
let signedOrder: SignedOrder;
let orderFactory: OrderFactory;
let libs: TestLibsContract;
+ let testConstants: TestConstantsContract;
before(async () => {
await blockchainLifecycle.startAsync();
@@ -32,6 +34,11 @@ describe('Exchange libs', () => {
const accounts = await web3Wrapper.getAvailableAddressesAsync();
const makerAddress = accounts[0];
libs = await TestLibsContract.deployFrom0xArtifactAsync(artifacts.TestLibs, provider, txDefaults);
+ testConstants = await TestConstantsContract.deployFrom0xArtifactAsync(
+ artifacts.TestConstants,
+ provider,
+ txDefaults,
+ );
const defaultOrderParams = {
...constants.STATIC_ORDER_PARAMS,
@@ -52,6 +59,15 @@ describe('Exchange libs', () => {
await blockchainLifecycle.revertAsync();
});
+ describe('LibConstants', () => {
+ describe('ZRX_ASSET_DATA', () => {
+ it('should have the correct ZRX_ASSET_DATA', async () => {
+ const isValid = await testConstants.assertValidZrxAssetData.callAsync();
+ expect(isValid).to.be.equal(true);
+ });
+ });
+ });
+
describe('LibOrder', () => {
describe('getOrderSchema', () => {
it('should output the correct order schema hash', async () => {
diff --git a/packages/contracts/test/exchange/signature_validator.ts b/packages/contracts/test/exchange/signature_validator.ts
index ef154bf9b..f2bb42c75 100644
--- a/packages/contracts/test/exchange/signature_validator.ts
+++ b/packages/contracts/test/exchange/signature_validator.ts
@@ -9,8 +9,8 @@ import {
TestSignatureValidatorContract,
TestSignatureValidatorSignatureValidatorApprovalEventArgs,
} from '../../generated_contract_wrappers/test_signature_validator';
-import { TestValidatorContract } from '../../generated_contract_wrappers/test_validator';
-import { TestWalletContract } from '../../generated_contract_wrappers/test_wallet';
+import { ValidatorContract } from '../../generated_contract_wrappers/validator';
+import { WalletContract } from '../../generated_contract_wrappers/wallet';
import { addressUtils } from '../utils/address_utils';
import { artifacts } from '../utils/artifacts';
import { expectContractCallFailed } from '../utils/assertions';
@@ -29,8 +29,8 @@ describe('MixinSignatureValidator', () => {
let signedOrder: SignedOrder;
let orderFactory: OrderFactory;
let signatureValidator: TestSignatureValidatorContract;
- let testWallet: TestWalletContract;
- let testValidator: TestValidatorContract;
+ let testWallet: WalletContract;
+ let testValidator: ValidatorContract;
let signerAddress: string;
let signerPrivateKey: Buffer;
let notSignerAddress: string;
@@ -53,14 +53,14 @@ describe('MixinSignatureValidator', () => {
provider,
txDefaults,
);
- testWallet = await TestWalletContract.deployFrom0xArtifactAsync(
- artifacts.TestWallet,
+ testWallet = await WalletContract.deployFrom0xArtifactAsync(
+ artifacts.Wallet,
provider,
txDefaults,
signerAddress,
);
- testValidator = await TestValidatorContract.deployFrom0xArtifactAsync(
- artifacts.TestValidator,
+ testValidator = await ValidatorContract.deployFrom0xArtifactAsync(
+ artifacts.Validator,
provider,
txDefaults,
signerAddress,
diff --git a/packages/contracts/test/forwarder/forwarder.ts b/packages/contracts/test/forwarder/forwarder.ts
index 0256d7d81..19639d3aa 100644
--- a/packages/contracts/test/forwarder/forwarder.ts
+++ b/packages/contracts/test/forwarder/forwarder.ts
@@ -18,7 +18,6 @@ import { constants } from '../utils/constants';
import { ERC20Wrapper } from '../utils/erc20_wrapper';
import { ERC721Wrapper } from '../utils/erc721_wrapper';
import { ExchangeWrapper } from '../utils/exchange_wrapper';
-import { formatters } from '../utils/formatters';
import { ForwarderWrapper } from '../utils/forwarder_wrapper';
import { OrderFactory } from '../utils/order_factory';
import { ContractName, ERC20BalancesByOwner } from '../utils/types';
@@ -28,8 +27,7 @@ chaiSetup.configure();
const expect = chai.expect;
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
const DECIMALS_DEFAULT = 18;
-// Set a gasPrice so when checking balance of msg.sender we can accurately calculate gasPrice*gasUsed
-const DEFAULT_GAS_PRICE = new BigNumber(1);
+const MAX_WETH_FILL_PERCENTAGE = 95;
describe(ContractName.Forwarder, () => {
let makerAddress: string;
@@ -47,25 +45,28 @@ describe(ContractName.Forwarder, () => {
let forwarderWrapper: ForwarderWrapper;
let exchangeWrapper: ExchangeWrapper;
- let signedOrder: SignedOrder;
- let signedOrders: SignedOrder[];
+ let orderWithoutFee: SignedOrder;
let orderWithFee: SignedOrder;
- let signedOrdersWithFee: SignedOrder[];
let feeOrder: SignedOrder;
- let feeOrders: SignedOrder[];
let orderFactory: OrderFactory;
let erc20Wrapper: ERC20Wrapper;
let erc20Balances: ERC20BalancesByOwner;
let tx: TransactionReceiptWithDecodedLogs;
let erc721MakerAssetIds: BigNumber[];
- let feeProportion: number = 0;
+ let takerEthBalanceBefore: BigNumber;
+ let feePercentage: BigNumber;
+ let gasPrice: BigNumber;
before(async () => {
await blockchainLifecycle.startAsync();
const accounts = await web3Wrapper.getAvailableAddressesAsync();
const usedAddresses = ([owner, makerAddress, takerAddress, feeRecipientAddress, otherAddress] = accounts);
+ const txHash = await web3Wrapper.sendTransactionAsync({ from: accounts[0], to: accounts[0], value: 0 });
+ const transaction = await web3Wrapper.getTransactionByHashAsync(txHash);
+ gasPrice = new BigNumber(transaction.gasPrice);
+
const erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner);
erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner);
@@ -135,681 +136,865 @@ describe(ContractName.Forwarder, () => {
wethAssetData,
);
forwarderContract = new ForwarderContract(forwarderInstance.abi, forwarderInstance.address, provider);
- forwarderWrapper = new ForwarderWrapper(forwarderContract, provider, zrxToken.address);
+ forwarderWrapper = new ForwarderWrapper(forwarderContract, provider);
+ const zrxDepositAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(10000), 18);
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await zrxToken.transfer.sendTransactionAsync(forwarderContract.address, zrxDepositAmount),
+ );
erc20Wrapper.addTokenOwnerAddress(forwarderInstance.address);
-
- web3Wrapper.abiDecoder.addABI(forwarderContract.abi);
- web3Wrapper.abiDecoder.addABI(exchangeInstance.abi);
});
after(async () => {
await blockchainLifecycle.revertAsync();
});
beforeEach(async () => {
await blockchainLifecycle.startAsync();
- feeProportion = 0;
erc20Balances = await erc20Wrapper.getBalancesAsync();
- signedOrder = await orderFactory.newSignedOrderAsync();
- signedOrders = [signedOrder];
+ takerEthBalanceBefore = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
+ orderWithoutFee = await orderFactory.newSignedOrderAsync();
feeOrder = await orderFactory.newSignedOrderAsync({
makerAssetData: assetDataUtils.encodeERC20AssetData(zrxToken.address),
takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT),
});
- feeOrders = [feeOrder];
orderWithFee = await orderFactory.newSignedOrderAsync({
takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT),
});
- signedOrdersWithFee = [orderWithFee];
});
afterEach(async () => {
await blockchainLifecycle.revertAsync();
});
- describe('calculations', () => {
- it('throws if partially filled orders passed in are not enough to satisfy requested amount', async () => {
- feeOrders = [feeOrder];
- const makerTokenFillAmount = feeOrder.makerAssetAmount.div(2);
- const fillAmountWei = await forwarderWrapper.calculateMarketBuyFillAmountWeiAsync(
- feeOrders,
- [],
- feeProportion,
- makerTokenFillAmount,
- );
- // Fill the feeOrder
- tx = await forwarderWrapper.marketBuyTokensWithEthAsync(feeOrders, [], makerTokenFillAmount, {
+
+ describe('marketSellOrdersWithEth without extra fees', () => {
+ it('should fill a single order', async () => {
+ const ordersWithoutFee = [orderWithoutFee];
+ const feeOrders: SignedOrder[] = [];
+ const ethValue = orderWithoutFee.takerAssetAmount.dividedToIntegerBy(2);
+
+ tx = await forwarderWrapper.marketSellOrdersWithEthAsync(ordersWithoutFee, feeOrders, {
+ value: ethValue,
from: takerAddress,
- value: fillAmountWei,
});
- return expect(
- forwarderWrapper.calculateMarketBuyFillAmountWeiAsync(
- feeOrders,
- [],
- feeProportion,
- makerTokenFillAmount,
- ),
- ).to.be.rejectedWith('Unable to satisfy makerAssetFillAmount with provided orders');
+ const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
+ const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+
+ const primaryTakerAssetFillAmount = ForwarderWrapper.getPercentageOfValue(
+ ethValue,
+ MAX_WETH_FILL_PERCENTAGE,
+ );
+ const makerAssetFillAmount = primaryTakerAssetFillAmount
+ .times(orderWithoutFee.makerAssetAmount)
+ .dividedToIntegerBy(orderWithoutFee.takerAssetAmount);
+ const totalEthSpent = primaryTakerAssetFillAmount.plus(gasPrice.times(tx.gasUsed));
+
+ expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
+ expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount),
+ );
+ expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFillAmount),
+ );
+ expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount),
+ );
+ expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(newBalances[forwarderContract.address][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ constants.ZERO_AMOUNT,
+ );
+ expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
});
- it('throws if orders passed are cancelled', async () => {
- tx = await exchangeWrapper.cancelOrderAsync(feeOrder, makerAddress);
- // Cancel the feeOrder
- return expect(
- forwarderWrapper.calculateMarketBuyFillAmountWeiAsync(
- feeOrders,
- [],
- feeProportion,
- feeOrder.makerAssetAmount.div(2),
- ),
- ).to.be.rejectedWith('Unable to satisfy makerAssetFillAmount with provided orders');
+ it('should fill multiple orders', async () => {
+ const secondOrderWithoutFee = await orderFactory.newSignedOrderAsync();
+ const ordersWithoutFee = [orderWithoutFee, secondOrderWithoutFee];
+ const feeOrders: SignedOrder[] = [];
+ const ethValue = ordersWithoutFee[0].takerAssetAmount.plus(
+ ordersWithoutFee[1].takerAssetAmount.dividedToIntegerBy(2),
+ );
+
+ tx = await forwarderWrapper.marketSellOrdersWithEthAsync(ordersWithoutFee, feeOrders, {
+ value: ethValue,
+ from: takerAddress,
+ });
+ const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
+ const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+
+ const primaryTakerAssetFillAmount = ForwarderWrapper.getPercentageOfValue(
+ ethValue,
+ MAX_WETH_FILL_PERCENTAGE,
+ );
+ const firstTakerAssetFillAmount = ordersWithoutFee[0].takerAssetAmount;
+ const secondTakerAssetFillAmount = primaryTakerAssetFillAmount.minus(firstTakerAssetFillAmount);
+
+ const makerAssetFillAmount = ordersWithoutFee[0].makerAssetAmount.plus(
+ ordersWithoutFee[1].makerAssetAmount
+ .times(secondTakerAssetFillAmount)
+ .dividedToIntegerBy(ordersWithoutFee[1].takerAssetAmount),
+ );
+ const totalEthSpent = primaryTakerAssetFillAmount.plus(gasPrice.times(tx.gasUsed));
+ expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
+ expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount),
+ );
+ expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFillAmount),
+ );
+ expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount),
+ );
+ expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(newBalances[forwarderContract.address][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ constants.ZERO_AMOUNT,
+ );
+ expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
});
- });
- describe('marketSellEthForERC20 without extra fees', () => {
- it('should fill the order', async () => {
- const fillAmount = signedOrder.takerAssetAmount.div(2);
- const makerBalanceBefore = erc20Balances[makerAddress][defaultMakerAssetAddress];
- const takerBalanceBefore = erc20Balances[takerAddress][defaultMakerAssetAddress];
- feeOrders = [];
- tx = await forwarderWrapper.marketSellEthForERC20Async(signedOrders, feeOrders, {
- value: fillAmount,
+ it('should fill the order and pay ZRX fees from a single feeOrder', async () => {
+ const ordersWithFee = [orderWithFee];
+ const feeOrders = [feeOrder];
+ const ethValue = orderWithFee.takerAssetAmount.dividedToIntegerBy(2);
+
+ tx = await forwarderWrapper.marketSellOrdersWithEthAsync(ordersWithFee, feeOrders, {
+ value: ethValue,
from: takerAddress,
});
+ const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
+ const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
const newBalances = await erc20Wrapper.getBalancesAsync();
- const makerBalanceAfter = newBalances[makerAddress][defaultMakerAssetAddress];
- const takerBalanceAfter = newBalances[takerAddress][defaultMakerAssetAddress];
- const makerTokenFillAmount = fillAmount
- .times(signedOrder.makerAssetAmount)
- .dividedToIntegerBy(signedOrder.takerAssetAmount);
-
- expect(makerBalanceAfter).to.be.bignumber.equal(makerBalanceBefore.minus(makerTokenFillAmount));
- expect(takerBalanceAfter).to.be.bignumber.equal(takerBalanceBefore.plus(makerTokenFillAmount));
- expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(new BigNumber(0));
+
+ const primaryTakerAssetFillAmount = ForwarderWrapper.getPercentageOfValue(
+ ethValue,
+ MAX_WETH_FILL_PERCENTAGE,
+ );
+ const makerAssetFillAmount = primaryTakerAssetFillAmount
+ .times(orderWithoutFee.makerAssetAmount)
+ .dividedToIntegerBy(orderWithoutFee.takerAssetAmount);
+ const feeAmount = ForwarderWrapper.getPercentageOfValue(
+ orderWithFee.takerFee.dividedToIntegerBy(2),
+ MAX_WETH_FILL_PERCENTAGE,
+ );
+ const wethSpentOnFeeOrders = ForwarderWrapper.getWethForFeeOrders(feeAmount, feeOrders);
+ const totalEthSpent = primaryTakerAssetFillAmount
+ .plus(wethSpentOnFeeOrders)
+ .plus(gasPrice.times(tx.gasUsed));
+
+ expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
+ expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount),
+ );
+ expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFillAmount),
+ );
+ expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount).plus(wethSpentOnFeeOrders),
+ );
+ expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(newBalances[forwarderContract.address][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ constants.ZERO_AMOUNT,
+ );
+ expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
});
- it('should fill the order and perform fee abstraction', async () => {
- const fillAmount = signedOrder.takerAssetAmount.div(4);
- const takerBalanceBefore = erc20Balances[takerAddress][defaultMakerAssetAddress];
- tx = await forwarderWrapper.marketSellEthForERC20Async(signedOrdersWithFee, feeOrders, {
- value: fillAmount,
+ it('should fill the orders and pay ZRX from multiple feeOrders', async () => {
+ const ordersWithFee = [orderWithFee];
+ const ethValue = orderWithFee.takerAssetAmount;
+ const makerAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address);
+ const makerAssetAmount = orderWithFee.takerFee.dividedToIntegerBy(2);
+ const takerAssetAmount = feeOrder.takerAssetAmount
+ .times(makerAssetAmount)
+ .dividedToIntegerBy(feeOrder.makerAssetAmount);
+
+ const firstFeeOrder = await orderFactory.newSignedOrderAsync({
+ makerAssetData,
+ makerAssetAmount,
+ takerAssetAmount,
+ });
+ const secondFeeOrder = await orderFactory.newSignedOrderAsync({
+ makerAssetData,
+ makerAssetAmount,
+ takerAssetAmount,
+ });
+ const feeOrders = [firstFeeOrder, secondFeeOrder];
+
+ tx = await forwarderWrapper.marketSellOrdersWithEthAsync(ordersWithFee, feeOrders, {
+ value: ethValue,
from: takerAddress,
});
+ const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
+ const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
const newBalances = await erc20Wrapper.getBalancesAsync();
- const takerBalanceAfter = newBalances[takerAddress][defaultMakerAssetAddress];
- const acceptPercentage = 98;
- const acceptableThreshold = takerBalanceBefore.plus(fillAmount.times(acceptPercentage).dividedBy(100));
- const isWithinThreshold = takerBalanceAfter.greaterThanOrEqualTo(acceptableThreshold);
- expect(isWithinThreshold).to.be.true();
- expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(new BigNumber(0));
+ const primaryTakerAssetFillAmount = ForwarderWrapper.getPercentageOfValue(
+ ethValue,
+ MAX_WETH_FILL_PERCENTAGE,
+ );
+ const makerAssetFillAmount = primaryTakerAssetFillAmount
+ .times(orderWithoutFee.makerAssetAmount)
+ .dividedToIntegerBy(orderWithoutFee.takerAssetAmount);
+ const feeAmount = ForwarderWrapper.getPercentageOfValue(orderWithFee.takerFee, MAX_WETH_FILL_PERCENTAGE);
+ const wethSpentOnFeeOrders = ForwarderWrapper.getWethForFeeOrders(feeAmount, feeOrders);
+ const totalEthSpent = primaryTakerAssetFillAmount
+ .plus(wethSpentOnFeeOrders)
+ .plus(gasPrice.times(tx.gasUsed));
+
+ expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
+ expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount),
+ );
+ expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFillAmount),
+ );
+ expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount).plus(wethSpentOnFeeOrders),
+ );
+ expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(newBalances[forwarderContract.address][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ constants.ZERO_AMOUNT,
+ );
+ expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
});
it('should fill the order when token is ZRX with fees', async () => {
orderWithFee = await orderFactory.newSignedOrderAsync({
makerAssetData: assetDataUtils.encodeERC20AssetData(zrxToken.address),
takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT),
});
- signedOrdersWithFee = [orderWithFee];
- feeOrders = [];
- const fillAmount = signedOrder.takerAssetAmount.div(4);
- const takerBalanceBefore = erc20Balances[takerAddress][zrxToken.address];
- tx = await forwarderWrapper.marketSellEthForERC20Async(signedOrdersWithFee, feeOrders, {
- value: fillAmount,
+ const ordersWithFee = [orderWithFee];
+ const feeOrders: SignedOrder[] = [];
+ const ethValue = orderWithFee.takerAssetAmount.dividedToIntegerBy(2);
+
+ tx = await forwarderWrapper.marketSellOrdersWithEthAsync(ordersWithFee, feeOrders, {
+ value: ethValue,
from: takerAddress,
});
+ const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
+ const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
const newBalances = await erc20Wrapper.getBalancesAsync();
- const takerBalanceAfter = newBalances[takerAddress][zrxToken.address];
- const acceptPercentage = 98;
- const acceptableThreshold = takerBalanceBefore.plus(fillAmount.times(acceptPercentage).dividedBy(100));
- const isWithinThreshold = takerBalanceAfter.greaterThanOrEqualTo(acceptableThreshold);
- expect(isWithinThreshold).to.be.true();
- expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(new BigNumber(0));
+ const makerAssetFillAmount = orderWithFee.makerAssetAmount.dividedToIntegerBy(2);
+ const totalEthSpent = ethValue.plus(gasPrice.times(tx.gasUsed));
+ const takerFeePaid = orderWithFee.takerFee.dividedToIntegerBy(2);
+ const makerFeePaid = orderWithFee.makerFee.dividedToIntegerBy(2);
+
+ expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
+ expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][zrxToken.address].minus(makerAssetFillAmount).minus(makerFeePaid),
+ );
+ expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][zrxToken.address].plus(makerAssetFillAmount).minus(takerFeePaid),
+ );
+ expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][weth.address].plus(ethValue),
+ );
+ expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(newBalances[forwarderContract.address][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[forwarderContract.address][zrxToken.address],
+ );
+ expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
});
- it('should fail if sent an ETH amount too high', async () => {
- signedOrder = await orderFactory.newSignedOrderAsync({
- makerAssetData: assetDataUtils.encodeERC20AssetData(zrxToken.address),
- takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT),
+ it('should refund remaining ETH if amount is greater than takerAssetAmount', async () => {
+ const ordersWithoutFee = [orderWithoutFee];
+ const feeOrders: SignedOrder[] = [];
+ const ethValue = orderWithoutFee.takerAssetAmount.times(2);
+
+ tx = await forwarderWrapper.marketSellOrdersWithEthAsync(ordersWithoutFee, feeOrders, {
+ value: ethValue,
+ from: takerAddress,
});
- const fillAmount = signedOrder.takerAssetAmount.times(2);
- return expectTransactionFailedAsync(
- forwarderWrapper.marketSellEthForERC20Async(signedOrdersWithFee, feeOrders, {
- value: fillAmount,
- from: takerAddress,
- }),
- RevertReason.UnacceptableThreshold,
- );
+ const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
+ const totalEthSpent = orderWithoutFee.takerAssetAmount.plus(gasPrice.times(tx.gasUsed));
+
+ expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
});
- it('should fail if fee abstraction amount is too high', async () => {
+ it('should revert if ZRX cannot be fully repurchased', async () => {
orderWithFee = await orderFactory.newSignedOrderAsync({
takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(50), DECIMALS_DEFAULT),
});
- signedOrdersWithFee = [orderWithFee];
+ const ordersWithFee = [orderWithFee];
feeOrder = await orderFactory.newSignedOrderAsync({
makerAssetData: assetDataUtils.encodeERC20AssetData(zrxToken.address),
makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT),
});
- feeOrders = [feeOrder];
- const fillAmount = signedOrder.takerAssetAmount.div(4);
+ const feeOrders = [feeOrder];
+ const ethValue = orderWithFee.takerAssetAmount;
return expectTransactionFailedAsync(
- forwarderWrapper.marketSellEthForERC20Async(signedOrdersWithFee, feeOrders, {
- value: fillAmount,
+ forwarderWrapper.marketSellOrdersWithEthAsync(ordersWithFee, feeOrders, {
+ value: ethValue,
from: takerAddress,
}),
- RevertReason.TransferFailed,
+ RevertReason.CompleteFillFailed,
);
});
- it('throws when mixed ERC721 and ERC20 assets with ERC20 first', async () => {
+ it('should not fill orders with different makerAssetData than the first order', async () => {
const makerAssetId = erc721MakerAssetIds[0];
const erc721SignedOrder = await orderFactory.newSignedOrderAsync({
makerAssetAmount: new BigNumber(1),
makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId),
});
const erc20SignedOrder = await orderFactory.newSignedOrderAsync();
- signedOrders = [erc20SignedOrder, erc721SignedOrder];
- const fillAmountWei = erc20SignedOrder.takerAssetAmount.plus(erc721SignedOrder.takerAssetAmount);
- return expectTransactionFailedAsync(
- forwarderWrapper.marketSellEthForERC20Async(signedOrders, feeOrders, {
- from: takerAddress,
- value: fillAmountWei,
- }),
- RevertReason.InvalidOrderSignature,
- );
+ const ordersWithoutFee = [erc20SignedOrder, erc721SignedOrder];
+ const feeOrders: SignedOrder[] = [];
+ const ethValue = erc20SignedOrder.takerAssetAmount.plus(erc721SignedOrder.takerAssetAmount);
+
+ tx = await forwarderWrapper.marketSellOrdersWithEthAsync(ordersWithoutFee, feeOrders, {
+ value: ethValue,
+ from: takerAddress,
+ });
+ const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
+ const totalEthSpent = erc20SignedOrder.takerAssetAmount.plus(gasPrice.times(tx.gasUsed));
+
+ expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
});
});
- describe('marketSellEthForERC20 with extra fees', () => {
- it('should fill the order and send fee to fee recipient', async () => {
- const initEthBalance = await web3Wrapper.getBalanceInWeiAsync(feeRecipientAddress);
- const fillAmount = signedOrder.takerAssetAmount.div(2);
- feeProportion = 150; // 1.5%
- feeOrders = [];
- tx = await forwarderWrapper.marketSellEthForERC20Async(
- signedOrders,
+ describe('marketSellOrdersWithEth with extra fees', () => {
+ it('should fill the order and send fee to feeRecipient', async () => {
+ const ordersWithoutFee = [orderWithoutFee];
+ const feeOrders: SignedOrder[] = [];
+ const ethValue = orderWithoutFee.takerAssetAmount.div(2);
+
+ const baseFeePercentage = 2;
+ feePercentage = ForwarderWrapper.getPercentageOfValue(constants.PERCENTAGE_DENOMINATOR, baseFeePercentage);
+ const feeRecipientEthBalanceBefore = await web3Wrapper.getBalanceInWeiAsync(feeRecipientAddress);
+ tx = await forwarderWrapper.marketSellOrdersWithEthAsync(
+ ordersWithoutFee,
feeOrders,
{
+ value: ethValue,
from: takerAddress,
- value: fillAmount,
- gasPrice: DEFAULT_GAS_PRICE,
- },
- {
- feeProportion,
- feeRecipient: feeRecipientAddress,
},
+ { feePercentage, feeRecipient: feeRecipientAddress },
);
+ const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
+ const feeRecipientEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(feeRecipientAddress);
+ const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
const newBalances = await erc20Wrapper.getBalancesAsync();
- const makerBalanceBefore = erc20Balances[makerAddress][defaultMakerAssetAddress];
- const makerBalanceAfter = newBalances[makerAddress][defaultMakerAssetAddress];
- const takerBalanceAfter = newBalances[takerAddress][defaultMakerAssetAddress];
- const afterEthBalance = await web3Wrapper.getBalanceInWeiAsync(feeRecipientAddress);
- const takerBoughtAmount = takerBalanceAfter.minus(erc20Balances[takerAddress][defaultMakerAssetAddress]);
- expect(makerBalanceAfter).to.be.bignumber.equal(makerBalanceBefore.minus(takerBoughtAmount));
- expect(afterEthBalance).to.be.bignumber.equal(
- initEthBalance.plus(fillAmount.times(feeProportion).dividedBy(10000)),
+ const primaryTakerAssetFillAmount = ForwarderWrapper.getPercentageOfValue(
+ ethValue,
+ MAX_WETH_FILL_PERCENTAGE,
+ );
+ const makerAssetFillAmount = primaryTakerAssetFillAmount
+ .times(orderWithoutFee.makerAssetAmount)
+ .dividedToIntegerBy(orderWithoutFee.takerAssetAmount);
+ const ethSpentOnFee = ForwarderWrapper.getPercentageOfValue(primaryTakerAssetFillAmount, baseFeePercentage);
+ const totalEthSpent = primaryTakerAssetFillAmount.plus(ethSpentOnFee).plus(gasPrice.times(tx.gasUsed));
+
+ expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
+ expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount),
);
- expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(new BigNumber(0));
+ expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFillAmount),
+ );
+ expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount),
+ );
+ expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(newBalances[forwarderContract.address][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ constants.ZERO_AMOUNT,
+ );
+ expect(feeRecipientEthBalanceAfter).to.be.bignumber.equal(feeRecipientEthBalanceBefore.plus(ethSpentOnFee));
+ expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
});
it('should fail if the fee is set too high', async () => {
- const initEthBalance = await web3Wrapper.getBalanceInWeiAsync(feeRecipientAddress);
- const fillAmount = signedOrder.takerAssetAmount.div(2);
- feeProportion = 1500; // 15.0%
- feeOrders = [];
+ const ethValue = orderWithoutFee.takerAssetAmount.div(2);
+ const baseFeePercentage = 6;
+ feePercentage = ForwarderWrapper.getPercentageOfValue(ethValue, baseFeePercentage);
+ const ordersWithoutFee = [orderWithoutFee];
+ const feeOrders: SignedOrder[] = [];
await expectTransactionFailedAsync(
- forwarderWrapper.marketSellEthForERC20Async(
- signedOrders,
+ forwarderWrapper.marketSellOrdersWithEthAsync(
+ ordersWithoutFee,
feeOrders,
- { from: takerAddress, value: fillAmount, gasPrice: DEFAULT_GAS_PRICE },
- { feeProportion, feeRecipient: feeRecipientAddress },
+ { from: takerAddress, value: ethValue, gasPrice },
+ { feePercentage, feeRecipient: feeRecipientAddress },
),
- RevertReason.FeeProportionTooLarge,
+ RevertReason.FeePercentageTooLarge,
+ );
+ });
+ it('should fail if there is not enough ETH remaining to pay the fee', async () => {
+ const ethValue = orderWithoutFee.takerAssetAmount.div(2);
+ const baseFeePercentage = 5;
+ feePercentage = ForwarderWrapper.getPercentageOfValue(constants.PERCENTAGE_DENOMINATOR, baseFeePercentage);
+ const ordersWithFee = [orderWithFee];
+ const feeOrders = [feeOrder];
+ await expectTransactionFailedAsync(
+ forwarderWrapper.marketSellOrdersWithEthAsync(
+ ordersWithFee,
+ feeOrders,
+ { from: takerAddress, value: ethValue, gasPrice },
+ { feePercentage, feeRecipient: feeRecipientAddress },
+ ),
+ RevertReason.InsufficientEthRemaining,
);
- const afterEthBalance = await web3Wrapper.getBalanceInWeiAsync(feeRecipientAddress);
- expect(afterEthBalance).to.be.bignumber.equal(initEthBalance);
});
});
- describe('marketBuyTokensWithEth', () => {
- it('should buy the exact amount of assets', async () => {
- const makerAssetAmount = signedOrder.makerAssetAmount.div(2);
- const initEthBalance = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
- const balancesBefore = await erc20Wrapper.getBalancesAsync();
- const rate = signedOrder.makerAssetAmount.dividedBy(signedOrder.takerAssetAmount);
- const fillAmountWei = makerAssetAmount.dividedToIntegerBy(rate);
- feeOrders = [];
- tx = await forwarderWrapper.marketBuyTokensWithEthAsync(signedOrders, feeOrders, makerAssetAmount, {
+ describe('marketBuyOrdersWithEth without extra fees', () => {
+ it('should buy the exact amount of makerAsset in a single order', async () => {
+ const ordersWithoutFee = [orderWithoutFee];
+ const feeOrders: SignedOrder[] = [];
+ const makerAssetFillAmount = orderWithoutFee.makerAssetAmount.dividedToIntegerBy(2);
+ const ethValue = orderWithoutFee.takerAssetAmount.dividedToIntegerBy(2);
+
+ tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(ordersWithoutFee, feeOrders, makerAssetFillAmount, {
+ value: ethValue,
from: takerAddress,
- value: fillAmountWei,
- gasPrice: DEFAULT_GAS_PRICE,
});
+ const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
+ const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
const newBalances = await erc20Wrapper.getBalancesAsync();
- const takerBalanceBefore = balancesBefore[takerAddress][defaultMakerAssetAddress];
- const takerBalanceAfter = newBalances[takerAddress][defaultMakerAssetAddress];
- const afterEthBalance = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
- const expectedEthBalanceAfterGasCosts = initEthBalance.minus(fillAmountWei).minus(tx.gasUsed);
- expect(takerBalanceAfter).to.be.bignumber.eq(takerBalanceBefore.plus(makerAssetAmount));
- expect(afterEthBalance).to.be.bignumber.eq(expectedEthBalanceAfterGasCosts);
+
+ const primaryTakerAssetFillAmount = ethValue;
+ const totalEthSpent = primaryTakerAssetFillAmount.plus(gasPrice.times(tx.gasUsed));
+
+ expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
+ expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount),
+ );
+ expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFillAmount),
+ );
+ expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount),
+ );
+ expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(newBalances[forwarderContract.address][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ constants.ZERO_AMOUNT,
+ );
+ expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
});
- it('should buy the exact amount of assets and return excess ETH', async () => {
- const makerAssetAmount = signedOrder.makerAssetAmount.div(2);
- const initEthBalance = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
- const balancesBefore = await erc20Wrapper.getBalancesAsync();
- const rate = signedOrder.makerAssetAmount.dividedBy(signedOrder.takerAssetAmount);
- const fillAmount = makerAssetAmount.dividedToIntegerBy(rate);
- const excessFillAmount = fillAmount.times(2);
- feeOrders = [];
- tx = await forwarderWrapper.marketBuyTokensWithEthAsync(signedOrders, feeOrders, makerAssetAmount, {
+ it('should buy the exact amount of makerAsset in multiple orders', async () => {
+ const secondOrderWithoutFee = await orderFactory.newSignedOrderAsync();
+ const ordersWithoutFee = [orderWithoutFee, secondOrderWithoutFee];
+ const feeOrders: SignedOrder[] = [];
+ const makerAssetFillAmount = ordersWithoutFee[0].makerAssetAmount.plus(
+ ordersWithoutFee[1].makerAssetAmount.dividedToIntegerBy(2),
+ );
+ const ethValue = ordersWithoutFee[0].takerAssetAmount.plus(
+ ordersWithoutFee[1].takerAssetAmount.dividedToIntegerBy(2),
+ );
+
+ tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(ordersWithoutFee, feeOrders, makerAssetFillAmount, {
+ value: ethValue,
from: takerAddress,
- value: excessFillAmount,
- gasPrice: DEFAULT_GAS_PRICE,
});
+ const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
+ const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
const newBalances = await erc20Wrapper.getBalancesAsync();
- const takerBalanceBefore = balancesBefore[takerAddress][defaultMakerAssetAddress];
- const takerBalanceAfter = newBalances[takerAddress][defaultMakerAssetAddress];
- const afterEthBalance = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
- const expectedEthBalanceAfterGasCosts = initEthBalance.minus(fillAmount).minus(tx.gasUsed);
- expect(takerBalanceAfter).to.be.bignumber.eq(takerBalanceBefore.plus(makerAssetAmount));
- expect(afterEthBalance).to.be.bignumber.eq(expectedEthBalanceAfterGasCosts);
+
+ const primaryTakerAssetFillAmount = ethValue;
+ const totalEthSpent = primaryTakerAssetFillAmount.plus(gasPrice.times(tx.gasUsed));
+
+ expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
+ expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount),
+ );
+ expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFillAmount),
+ );
+ expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount),
+ );
+ expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(newBalances[forwarderContract.address][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ constants.ZERO_AMOUNT,
+ );
+ expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
});
- it('should buy the exact amount of assets with fee abstraction', async () => {
- const makerAssetAmount = signedOrder.makerAssetAmount.div(2);
- const balancesBefore = await erc20Wrapper.getBalancesAsync();
- const rate = signedOrder.makerAssetAmount.dividedBy(signedOrder.takerAssetAmount);
- const fillAmount = makerAssetAmount.dividedToIntegerBy(rate);
- const excessFillAmount = fillAmount.times(2);
- tx = await forwarderWrapper.marketBuyTokensWithEthAsync(signedOrdersWithFee, feeOrders, makerAssetAmount, {
+ it('should buy the exact amount of makerAsset and return excess ETH', async () => {
+ const ordersWithoutFee = [orderWithoutFee];
+ const feeOrders: SignedOrder[] = [];
+ const makerAssetFillAmount = orderWithoutFee.makerAssetAmount.dividedToIntegerBy(2);
+ const ethValue = orderWithoutFee.takerAssetAmount;
+
+ tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(ordersWithoutFee, feeOrders, makerAssetFillAmount, {
+ value: ethValue,
from: takerAddress,
- value: excessFillAmount,
});
+ const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
+ const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
const newBalances = await erc20Wrapper.getBalancesAsync();
- const takerBalanceBefore = balancesBefore[takerAddress][defaultMakerAssetAddress];
- const takerBalanceAfter = newBalances[takerAddress][defaultMakerAssetAddress];
- expect(takerBalanceAfter).to.be.bignumber.eq(takerBalanceBefore.plus(makerAssetAmount));
- });
- it('should buy the exact amount of assets when buying zrx with fee abstraction', async () => {
- signedOrder = await orderFactory.newSignedOrderAsync({
- makerAssetData: assetDataUtils.encodeERC20AssetData(zrxToken.address),
- takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT),
- });
- signedOrdersWithFee = [signedOrder];
- feeOrders = [];
- const makerAssetAmount = signedOrder.makerAssetAmount.div(2);
- const takerWeiBalanceBefore = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
- const balancesBefore = await erc20Wrapper.getBalancesAsync();
- const fillAmountWei = await forwarderWrapper.calculateMarketBuyFillAmountWeiAsync(
- signedOrdersWithFee,
- feeOrders,
- feeProportion,
- makerAssetAmount,
+
+ const primaryTakerAssetFillAmount = ethValue.dividedToIntegerBy(2);
+ const totalEthSpent = primaryTakerAssetFillAmount.plus(gasPrice.times(tx.gasUsed));
+
+ expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
+ expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount),
+ );
+ expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFillAmount),
+ );
+ expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount),
);
- tx = await forwarderWrapper.marketBuyTokensWithEthAsync(signedOrdersWithFee, feeOrders, makerAssetAmount, {
+ expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(newBalances[forwarderContract.address][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ constants.ZERO_AMOUNT,
+ );
+ expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ });
+ it('should buy the exact amount of makerAsset and pay ZRX from feeOrders', async () => {
+ const ordersWithFee = [orderWithFee];
+ const feeOrders = [feeOrder];
+ const makerAssetFillAmount = orderWithFee.makerAssetAmount.dividedToIntegerBy(2);
+ const ethValue = orderWithFee.takerAssetAmount;
+
+ tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(ordersWithFee, feeOrders, makerAssetFillAmount, {
+ value: ethValue,
from: takerAddress,
- value: fillAmountWei,
- gasPrice: DEFAULT_GAS_PRICE,
});
+ const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
+ const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
const newBalances = await erc20Wrapper.getBalancesAsync();
- const takerTokenBalanceBefore = balancesBefore[takerAddress][zrxToken.address];
- const takerTokenBalanceAfter = newBalances[takerAddress][zrxToken.address];
- const takerWeiBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
- const expectedCostAfterGas = fillAmountWei.plus(tx.gasUsed);
- expect(takerTokenBalanceAfter).to.be.bignumber.greaterThan(takerTokenBalanceBefore.plus(makerAssetAmount));
- expect(takerWeiBalanceAfter).to.be.bignumber.equal(takerWeiBalanceBefore.minus(expectedCostAfterGas));
- });
- it('throws if fees are higher than 5% when buying zrx', async () => {
- const highFeeZRXOrder = await orderFactory.newSignedOrderAsync({
- makerAssetData: assetDataUtils.encodeERC20AssetData(zrxToken.address),
- makerAssetAmount: signedOrder.makerAssetAmount,
- takerFee: signedOrder.makerAssetAmount.times(0.06),
- });
- signedOrdersWithFee = [highFeeZRXOrder];
- feeOrders = [];
- const makerAssetAmount = signedOrder.makerAssetAmount.div(2);
- const fillAmountWei = await forwarderWrapper.calculateMarketBuyFillAmountWeiAsync(
- signedOrdersWithFee,
- feeOrders,
- feeProportion,
- makerAssetAmount,
+
+ const primaryTakerAssetFillAmount = orderWithFee.takerAssetAmount.dividedToIntegerBy(2);
+ const feeAmount = orderWithFee.takerFee.dividedToIntegerBy(2);
+ const wethSpentOnFeeOrders = ForwarderWrapper.getWethForFeeOrders(feeAmount, feeOrders);
+ const totalEthSpent = primaryTakerAssetFillAmount
+ .plus(wethSpentOnFeeOrders)
+ .plus(gasPrice.times(tx.gasUsed));
+
+ expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
+ expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount),
);
- return expectTransactionFailedAsync(
- forwarderWrapper.marketBuyTokensWithEthAsync(signedOrdersWithFee, feeOrders, makerAssetAmount, {
- from: takerAddress,
- value: fillAmountWei,
- }),
- RevertReason.UnacceptableThreshold,
+ expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFillAmount),
);
+ expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount).plus(wethSpentOnFeeOrders),
+ );
+ expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(newBalances[forwarderContract.address][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ constants.ZERO_AMOUNT,
+ );
+ expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
});
- it('throws if fees are higher than 5% when buying erc20', async () => {
- const highFeeERC20Order = await orderFactory.newSignedOrderAsync({
- takerFee: signedOrder.makerAssetAmount.times(0.06),
+ it('should buy slightly greater than makerAssetAmount when buying ZRX', async () => {
+ orderWithFee = await orderFactory.newSignedOrderAsync({
+ makerAssetData: assetDataUtils.encodeERC20AssetData(zrxToken.address),
+ takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT),
});
- signedOrdersWithFee = [highFeeERC20Order];
- feeOrders = [feeOrder];
- const makerAssetAmount = signedOrder.makerAssetAmount.div(2);
- const fillAmountWei = await forwarderWrapper.calculateMarketBuyFillAmountWeiAsync(
- signedOrdersWithFee,
- feeOrders,
- feeProportion,
- makerAssetAmount,
+ const ordersWithFee = [orderWithFee];
+ const feeOrders: SignedOrder[] = [];
+ const makerAssetFillAmount = orderWithFee.makerAssetAmount.dividedToIntegerBy(2);
+ const ethValue = orderWithFee.takerAssetAmount;
+ tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(ordersWithFee, feeOrders, makerAssetFillAmount, {
+ value: ethValue,
+ from: takerAddress,
+ });
+ const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
+ const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+
+ const primaryTakerAssetFillAmount = ForwarderWrapper.getWethForFeeOrders(
+ makerAssetFillAmount,
+ ordersWithFee,
);
- return expectTransactionFailedAsync(
- forwarderWrapper.marketBuyTokensWithEthAsync(signedOrdersWithFee, feeOrders, makerAssetAmount, {
- from: takerAddress,
- value: fillAmountWei,
- }),
- RevertReason.UnacceptableThreshold as any,
+ const totalEthSpent = primaryTakerAssetFillAmount.plus(gasPrice.times(tx.gasUsed));
+ const makerAssetFilledAmount = orderWithFee.makerAssetAmount
+ .times(primaryTakerAssetFillAmount)
+ .dividedToIntegerBy(orderWithFee.takerAssetAmount);
+ const takerFeePaid = orderWithFee.takerFee
+ .times(primaryTakerAssetFillAmount)
+ .dividedToIntegerBy(orderWithFee.takerAssetAmount);
+ const makerFeePaid = orderWithFee.makerFee
+ .times(primaryTakerAssetFillAmount)
+ .dividedToIntegerBy(orderWithFee.takerAssetAmount);
+ const totalZrxPurchased = makerAssetFilledAmount.minus(takerFeePaid);
+ // Up to 1 wei worth of ZRX will be overbought per order
+ const maxOverboughtZrx = new BigNumber(1)
+ .times(orderWithFee.makerAssetAmount)
+ .dividedToIntegerBy(orderWithFee.takerAssetAmount);
+
+ expect(totalZrxPurchased).to.be.bignumber.gte(makerAssetFillAmount);
+ expect(totalZrxPurchased).to.be.bignumber.lte(makerAssetFillAmount.plus(maxOverboughtZrx));
+ expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
+ expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][zrxToken.address].minus(makerAssetFilledAmount).minus(makerFeePaid),
);
- });
- it('throws if makerAssetAmount is 0', async () => {
- const makerAssetAmount = new BigNumber(0);
- const fillAmountWei = await forwarderWrapper.calculateMarketBuyFillAmountWeiAsync(
- signedOrdersWithFee,
- feeOrders,
- feeProportion,
- makerAssetAmount,
+ expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][zrxToken.address].plus(totalZrxPurchased),
);
- return expectTransactionFailedAsync(
- forwarderWrapper.marketBuyTokensWithEthAsync(signedOrdersWithFee, feeOrders, makerAssetAmount, {
- from: takerAddress,
- value: fillAmountWei,
- }),
- RevertReason.ValueGreaterThanZero as any,
+ expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount),
+ );
+ expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(newBalances[forwarderContract.address][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[forwarderContract.address][zrxToken.address],
);
+ expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
});
- it('throws if the amount of ETH sent in is less than the takerAssetFilledAmount', async () => {
- const makerAssetAmount = signedOrder.makerAssetAmount;
- const fillAmount = signedOrder.takerAssetAmount.div(2);
- const zero = new BigNumber(0);
- // Deposit enough taker balance to fill the order
- const wethDepositTxHash = await wethContract.deposit.sendTransactionAsync({
+ it('should not change balances if the amount of ETH sent is too low to fill the makerAssetAmount', async () => {
+ const ordersWithoutFee = [orderWithoutFee];
+ const feeOrders: SignedOrder[] = [];
+ const makerAssetFillAmount = orderWithoutFee.makerAssetAmount.dividedToIntegerBy(2);
+ const ethValue = orderWithoutFee.takerAssetAmount.dividedToIntegerBy(4);
+
+ tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(ordersWithoutFee, feeOrders, makerAssetFillAmount, {
+ value: ethValue,
from: takerAddress,
- value: signedOrder.takerAssetAmount,
});
- await web3Wrapper.awaitTransactionSuccessAsync(wethDepositTxHash);
- // Transfer all of this WETH to the forwarding contract
- const wethTransferTxHash = await wethContract.transfer.sendTransactionAsync(
- forwarderContract.address,
- signedOrder.takerAssetAmount,
- { from: takerAddress },
- );
- await web3Wrapper.awaitTransactionSuccessAsync(wethTransferTxHash);
- // We use the contract directly to get around wrapper validations and calculations
- const formattedOrders = formatters.createMarketSellOrders(signedOrders, zero);
- const formattedFeeOrders = formatters.createMarketSellOrders(feeOrders, zero);
- return expectTransactionFailedAsync(
- forwarderContract.marketBuyTokensWithEth.sendTransactionAsync(
- formattedOrders.orders,
- formattedOrders.signatures,
- formattedFeeOrders.orders,
- formattedFeeOrders.signatures,
- makerAssetAmount,
- zero,
- constants.NULL_ADDRESS,
- { value: fillAmount, from: takerAddress },
- ),
- RevertReason.InvalidMsgValue,
- );
+ const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
+ const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+
+ const totalEthSpent = gasPrice.times(tx.gasUsed);
+
+ expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
+ expect(newBalances).to.deep.equal(erc20Balances);
+ expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
});
- });
- describe('marketBuyTokensWithEth - ERC721', async () => {
- it('buys ERC721 assets', async () => {
+ it('should buy an ERC721 asset from a single order', async () => {
const makerAssetId = erc721MakerAssetIds[0];
- signedOrder = await orderFactory.newSignedOrderAsync({
+ orderWithoutFee = await orderFactory.newSignedOrderAsync({
makerAssetAmount: new BigNumber(1),
makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId),
});
- feeOrders = [];
- signedOrders = [signedOrder];
- const makerAssetAmount = new BigNumber(signedOrders.length);
- const fillAmountWei = await forwarderWrapper.calculateMarketBuyFillAmountWeiAsync(
- signedOrders,
- feeOrders,
- feeProportion,
- makerAssetAmount,
- );
- tx = await forwarderWrapper.marketBuyTokensWithEthAsync(signedOrders, feeOrders, makerAssetAmount, {
+ const ordersWithoutFee = [orderWithoutFee];
+ const feeOrders: SignedOrder[] = [];
+ const makerAssetFillAmount = new BigNumber(1);
+ const ethValue = orderWithFee.takerAssetAmount;
+
+ tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(ordersWithoutFee, feeOrders, makerAssetFillAmount, {
from: takerAddress,
- value: fillAmountWei,
+ value: ethValue,
});
- const newOwnerTakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId);
- expect(newOwnerTakerAsset).to.be.bignumber.equal(takerAddress);
+ const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
+ const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
+ const newOwner = await erc721Token.ownerOf.callAsync(makerAssetId);
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+
+ const primaryTakerAssetFillAmount = ethValue;
+ const totalEthSpent = primaryTakerAssetFillAmount.plus(gasPrice.times(tx.gasUsed));
+ expect(newOwner).to.be.bignumber.equal(takerAddress);
+ expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
+ expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount),
+ );
+ expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(newBalances[forwarderContract.address][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ constants.ZERO_AMOUNT,
+ );
+ expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
});
- it('buys ERC721 assets with fee abstraction', async () => {
+ it('should buy an ERC721 asset and ignore later orders with different makerAssetData', async () => {
const makerAssetId = erc721MakerAssetIds[0];
- signedOrder = await orderFactory.newSignedOrderAsync({
+ orderWithoutFee = await orderFactory.newSignedOrderAsync({
makerAssetAmount: new BigNumber(1),
- takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT),
makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId),
});
- signedOrders = [signedOrder];
- const makerAssetAmount = new BigNumber(signedOrders.length);
- const fillAmountWei = await forwarderWrapper.calculateMarketBuyFillAmountWeiAsync(
- signedOrders,
- feeOrders,
- feeProportion,
- makerAssetAmount,
- );
- tx = await forwarderWrapper.marketBuyTokensWithEthAsync(signedOrders, feeOrders, makerAssetAmount, {
+ const differentMakerAssetDataOrder = await orderFactory.newSignedOrderAsync();
+ const ordersWithoutFee = [orderWithoutFee, differentMakerAssetDataOrder];
+ const feeOrders: SignedOrder[] = [];
+ const makerAssetFillAmount = new BigNumber(1).plus(differentMakerAssetDataOrder.makerAssetAmount);
+ const ethValue = orderWithFee.takerAssetAmount;
+
+ tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(ordersWithoutFee, feeOrders, makerAssetFillAmount, {
from: takerAddress,
- value: fillAmountWei,
+ value: ethValue,
});
- const newOwnerTakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId);
- expect(newOwnerTakerAsset).to.be.bignumber.equal(takerAddress);
- });
- it('buys ERC721 assets with fee abstraction and pays fee to fee recipient', async () => {
- const makerAssetId = erc721MakerAssetIds[0];
- signedOrder = await orderFactory.newSignedOrderAsync({
- makerAssetAmount: new BigNumber(1),
- takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT),
- makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId),
- });
- signedOrders = [signedOrder];
- feeProportion = 100;
- const initTakerBalanceWei = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
- const initFeeRecipientBalanceWei = await web3Wrapper.getBalanceInWeiAsync(feeRecipientAddress);
- const makerAssetAmount = new BigNumber(signedOrders.length);
- const fillAmountWei = await forwarderWrapper.calculateMarketBuyFillAmountWeiAsync(
- signedOrders,
- feeOrders,
- feeProportion,
- makerAssetAmount,
+ const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
+ const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
+ const newOwner = await erc721Token.ownerOf.callAsync(makerAssetId);
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+
+ const primaryTakerAssetFillAmount = ethValue;
+ const totalEthSpent = primaryTakerAssetFillAmount.plus(gasPrice.times(tx.gasUsed));
+ expect(newOwner).to.be.bignumber.equal(takerAddress);
+ expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
+ expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount),
);
- tx = await forwarderWrapper.marketBuyTokensWithEthAsync(
- signedOrders,
- feeOrders,
- makerAssetAmount,
- {
- from: takerAddress,
- value: fillAmountWei,
- gasPrice: DEFAULT_GAS_PRICE,
- },
- {
- feeProportion,
- feeRecipient: feeRecipientAddress,
- },
+ expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(newBalances[forwarderContract.address][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ constants.ZERO_AMOUNT,
);
- const afterFeeRecipientEthBalance = await web3Wrapper.getBalanceInWeiAsync(feeRecipientAddress);
- const afterTakerBalanceWei = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
- const takerFilledAmount = initTakerBalanceWei.minus(afterTakerBalanceWei).plus(tx.gasUsed);
- const newOwnerTakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId);
- expect(newOwnerTakerAsset).to.be.bignumber.equal(takerAddress);
- const balanceDiff = afterFeeRecipientEthBalance.minus(initFeeRecipientBalanceWei);
- expect(takerFilledAmount.dividedToIntegerBy(balanceDiff)).to.be.bignumber.equal(101);
- expect(takerFilledAmount.minus(balanceDiff).dividedToIntegerBy(balanceDiff)).to.be.bignumber.equal(100);
- });
- it('buys multiple ERC721 assets with fee abstraction and pays fee to fee recipient', async () => {
- const makerAssetId1 = erc721MakerAssetIds[0];
- const makerAssetId2 = erc721MakerAssetIds[1];
- const signedOrder1 = await orderFactory.newSignedOrderAsync({
- makerAssetAmount: new BigNumber(1),
- takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(3), DECIMALS_DEFAULT),
- makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId1),
- });
- const signedOrder2 = await orderFactory.newSignedOrderAsync({
- makerAssetAmount: new BigNumber(1),
- takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(4), DECIMALS_DEFAULT),
- makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId2),
- });
- signedOrders = [signedOrder1, signedOrder2];
- feeProportion = 10;
- const makerAssetAmount = new BigNumber(signedOrders.length);
- const fillAmountWei = await forwarderWrapper.calculateMarketBuyFillAmountWeiAsync(
- signedOrders,
- feeOrders,
- feeProportion,
- makerAssetAmount,
+ expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][defaultMakerAssetAddress],
+ );
+ expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][defaultMakerAssetAddress],
);
- tx = await forwarderWrapper.marketBuyTokensWithEthAsync(signedOrders, feeOrders, makerAssetAmount, {
- from: takerAddress,
- value: fillAmountWei,
- });
- const newOwnerTakerAsset1 = await erc721Token.ownerOf.callAsync(makerAssetId1);
- expect(newOwnerTakerAsset1).to.be.bignumber.equal(takerAddress);
- const newOwnerTakerAsset2 = await erc721Token.ownerOf.callAsync(makerAssetId2);
- expect(newOwnerTakerAsset2).to.be.bignumber.equal(takerAddress);
});
- it('buys ERC721 assets with fee abstraction and handles fee orders filled and excess eth', async () => {
+ it('should buy an ERC721 asset and pay ZRX fees from a single fee order', async () => {
const makerAssetId = erc721MakerAssetIds[0];
- feeProportion = 0;
- // In this scenario a total of 6 ZRX fees need to be paid.
- // There are two fee orders, but the first fee order is partially filled while
- // the Forwarding contract tx is in the mempool.
- const erc721MakerAssetAmount = new BigNumber(1);
- signedOrder = await orderFactory.newSignedOrderAsync({
- makerAssetAmount: erc721MakerAssetAmount,
- takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), DECIMALS_DEFAULT),
- takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(6), DECIMALS_DEFAULT),
+ orderWithFee = await orderFactory.newSignedOrderAsync({
+ makerAssetAmount: new BigNumber(1),
makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId),
+ takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT),
});
- signedOrders = [signedOrder];
- const firstFeeOrder = await orderFactory.newSignedOrderAsync({
- makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(8), DECIMALS_DEFAULT),
- takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(0.1), DECIMALS_DEFAULT),
- makerAssetData: assetDataUtils.encodeERC20AssetData(zrxToken.address),
- takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(0), DECIMALS_DEFAULT),
- });
- const secondFeeOrder = await orderFactory.newSignedOrderAsync({
- makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(8), DECIMALS_DEFAULT),
- takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(0.12), DECIMALS_DEFAULT),
- makerAssetData: assetDataUtils.encodeERC20AssetData(zrxToken.address),
- takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(0), DECIMALS_DEFAULT),
- });
- feeOrders = [firstFeeOrder, secondFeeOrder];
- const makerAssetAmount = new BigNumber(signedOrders.length);
- const fillAmountWei = await forwarderWrapper.calculateMarketBuyFillAmountWeiAsync(
- signedOrders,
- feeOrders,
- feeProportion,
- erc721MakerAssetAmount,
- );
- // Simulate another otherAddress user partially filling firstFeeOrder
- const firstFeeOrderFillAmount = firstFeeOrder.makerAssetAmount.div(2);
- tx = await forwarderWrapper.marketBuyTokensWithEthAsync([firstFeeOrder], [], firstFeeOrderFillAmount, {
- from: otherAddress,
- value: fillAmountWei,
- });
- // For tests we calculate how much this should've cost given that firstFeeOrder was filled
- const expectedFillAmountWei = await forwarderWrapper.calculateMarketBuyFillAmountWeiAsync(
- signedOrders,
- feeOrders,
- feeProportion,
- erc721MakerAssetAmount,
- );
- // With 4 ZRX remaining in firstFeeOrder, the secondFeeOrder will need to be filled to make up
- // the total amount of fees required (6)
- // Since the fee orders can be filled while the transaction is pending the user safely sends in
- // extra ether to cover any slippage
- const initEthBalance = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
- const slippageFillAmountWei = fillAmountWei.times(2);
- tx = await forwarderWrapper.marketBuyTokensWithEthAsync(signedOrders, feeOrders, makerAssetAmount, {
+ const ordersWithFee = [orderWithFee];
+ const feeOrders = [feeOrder];
+ const makerAssetFillAmount = orderWithFee.makerAssetAmount;
+ const primaryTakerAssetFillAmount = orderWithFee.takerAssetAmount;
+ const feeAmount = orderWithFee.takerFee;
+ const wethSpentOnFeeOrders = ForwarderWrapper.getWethForFeeOrders(feeAmount, feeOrders);
+ const ethValue = primaryTakerAssetFillAmount.plus(wethSpentOnFeeOrders);
+
+ tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(ordersWithFee, feeOrders, makerAssetFillAmount, {
+ value: ethValue,
from: takerAddress,
- value: slippageFillAmountWei,
- gasPrice: DEFAULT_GAS_PRICE,
});
- const afterEthBalance = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
- const expectedEthBalanceAfterGasCosts = initEthBalance.minus(expectedFillAmountWei).minus(tx.gasUsed);
- const newOwnerTakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId);
- expect(newOwnerTakerAsset).to.be.bignumber.equal(takerAddress);
- expect(afterEthBalance).to.be.bignumber.equal(expectedEthBalanceAfterGasCosts);
+ const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
+ const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
+ const newOwner = await erc721Token.ownerOf.callAsync(makerAssetId);
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+
+ const totalEthSpent = ethValue.plus(gasPrice.times(tx.gasUsed));
+
+ expect(newOwner).to.be.bignumber.equal(takerAddress);
+ expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
+ expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount).plus(wethSpentOnFeeOrders),
+ );
+ expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(newBalances[forwarderContract.address][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ constants.ZERO_AMOUNT,
+ );
+ expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
});
- it('buys ERC721 assets with fee abstraction and handles fee orders filled', async () => {
+ it('should buy an ERC721 asset and pay ZRX fees from multiple fee orders', async () => {
const makerAssetId = erc721MakerAssetIds[0];
- feeProportion = 0;
- // In this scenario a total of 6 ZRX fees need to be paid.
- // There are two fee orders, but the first fee order is partially filled while
- // the Forwarding contract tx is in the mempool.
- const erc721MakerAssetAmount = new BigNumber(1);
- signedOrder = await orderFactory.newSignedOrderAsync({
- makerAssetAmount: erc721MakerAssetAmount,
- takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), DECIMALS_DEFAULT),
- takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(6), DECIMALS_DEFAULT),
+ orderWithFee = await orderFactory.newSignedOrderAsync({
+ makerAssetAmount: new BigNumber(1),
makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId),
+ takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT),
});
- const zrxMakerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(8), DECIMALS_DEFAULT);
- signedOrders = [signedOrder];
+ const ordersWithFee = [orderWithFee];
+ const makerAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address);
+ const makerAssetAmount = orderWithFee.takerFee.dividedToIntegerBy(2);
+ const takerAssetAmount = feeOrder.takerAssetAmount
+ .times(makerAssetAmount)
+ .dividedToIntegerBy(feeOrder.makerAssetAmount);
+
const firstFeeOrder = await orderFactory.newSignedOrderAsync({
- makerAssetAmount: zrxMakerAssetAmount,
- takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(0.1), DECIMALS_DEFAULT),
- makerAssetData: assetDataUtils.encodeERC20AssetData(zrxToken.address),
- takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(0), DECIMALS_DEFAULT),
+ makerAssetData,
+ makerAssetAmount,
+ takerAssetAmount,
});
const secondFeeOrder = await orderFactory.newSignedOrderAsync({
- makerAssetAmount: zrxMakerAssetAmount,
- takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(0.12), DECIMALS_DEFAULT),
- makerAssetData: assetDataUtils.encodeERC20AssetData(zrxToken.address),
- takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(0), DECIMALS_DEFAULT),
- });
- feeOrders = [firstFeeOrder, secondFeeOrder];
- const makerAssetAmount = new BigNumber(signedOrders.length);
- const fillAmountWei = await forwarderWrapper.calculateMarketBuyFillAmountWeiAsync(
- signedOrders,
- feeOrders,
- feeProportion,
- erc721MakerAssetAmount,
- );
- // Simulate another otherAddress user partially filling firstFeeOrder
- const firstFeeOrderFillAmount = firstFeeOrder.makerAssetAmount.div(2);
- tx = await forwarderWrapper.marketBuyTokensWithEthAsync([firstFeeOrder], [], firstFeeOrderFillAmount, {
- from: otherAddress,
- value: fillAmountWei,
+ makerAssetData,
+ makerAssetAmount,
+ takerAssetAmount,
});
- const expectedFillAmountWei = await forwarderWrapper.calculateMarketBuyFillAmountWeiAsync(
- signedOrders,
- feeOrders,
- feeProportion,
- erc721MakerAssetAmount,
- );
- tx = await forwarderWrapper.marketBuyTokensWithEthAsync(signedOrders, feeOrders, makerAssetAmount, {
+ const feeOrders = [firstFeeOrder, secondFeeOrder];
+
+ const makerAssetFillAmount = orderWithFee.makerAssetAmount;
+ const primaryTakerAssetFillAmount = orderWithFee.takerAssetAmount;
+ const feeAmount = orderWithFee.takerFee;
+ const wethSpentOnFeeOrders = ForwarderWrapper.getWethForFeeOrders(feeAmount, feeOrders);
+ const ethValue = primaryTakerAssetFillAmount.plus(wethSpentOnFeeOrders);
+
+ tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(ordersWithFee, feeOrders, makerAssetFillAmount, {
+ value: ethValue,
from: takerAddress,
- value: expectedFillAmountWei,
});
- const newOwnerTakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId);
- expect(newOwnerTakerAsset).to.be.bignumber.equal(takerAddress);
+ const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
+ const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
+ const newOwner = await erc721Token.ownerOf.callAsync(makerAssetId);
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+
+ const totalEthSpent = ethValue.plus(gasPrice.times(tx.gasUsed));
+
+ expect(newOwner).to.be.bignumber.equal(takerAddress);
+ expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
+ expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount).plus(wethSpentOnFeeOrders),
+ );
+ expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(newBalances[forwarderContract.address][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ constants.ZERO_AMOUNT,
+ );
+ expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
});
- it('throws when mixed ERC721 and ERC20 assets', async () => {
- const makerAssetId = erc721MakerAssetIds[0];
- const erc721SignedOrder = await orderFactory.newSignedOrderAsync({
- makerAssetAmount: new BigNumber(1),
- makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId),
- });
- const erc20SignedOrder = await orderFactory.newSignedOrderAsync();
- signedOrders = [erc721SignedOrder, erc20SignedOrder];
- const makerAssetAmount = new BigNumber(signedOrders.length);
- const fillAmountWei = erc20SignedOrder.takerAssetAmount.plus(erc721SignedOrder.takerAssetAmount);
- return expectTransactionFailedAsync(
- forwarderWrapper.marketBuyTokensWithEthAsync(signedOrders, feeOrders, makerAssetAmount, {
+ });
+ describe('marketBuyOrdersWithEth with extra fees', () => {
+ it('should buy an asset and send fee to feeRecipient', async () => {
+ const ordersWithoutFee = [orderWithoutFee];
+ const feeOrders: SignedOrder[] = [];
+ const makerAssetFillAmount = orderWithoutFee.makerAssetAmount.dividedToIntegerBy(2);
+ const ethValue = orderWithoutFee.takerAssetAmount;
+
+ const baseFeePercentage = 2;
+ feePercentage = ForwarderWrapper.getPercentageOfValue(constants.PERCENTAGE_DENOMINATOR, baseFeePercentage);
+ const feeRecipientEthBalanceBefore = await web3Wrapper.getBalanceInWeiAsync(feeRecipientAddress);
+ tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(
+ ordersWithoutFee,
+ feeOrders,
+ makerAssetFillAmount,
+ {
+ value: ethValue,
from: takerAddress,
- value: fillAmountWei,
- }),
- RevertReason.LibBytesGreaterOrEqualTo32LengthRequired,
+ },
+ { feePercentage, feeRecipient: feeRecipientAddress },
);
+ const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
+ const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
+ const feeRecipientEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(feeRecipientAddress);
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+
+ const primaryTakerAssetFillAmount = orderWithoutFee.takerAssetAmount.dividedToIntegerBy(2);
+ const ethSpentOnFee = ForwarderWrapper.getPercentageOfValue(primaryTakerAssetFillAmount, baseFeePercentage);
+ const totalEthSpent = primaryTakerAssetFillAmount.plus(ethSpentOnFee).plus(gasPrice.times(tx.gasUsed));
+
+ expect(feeRecipientEthBalanceAfter).to.be.bignumber.equal(feeRecipientEthBalanceBefore.plus(ethSpentOnFee));
+ expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
+ expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount),
+ );
+ expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFillAmount),
+ );
+ expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount),
+ );
+ expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(newBalances[forwarderContract.address][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ constants.ZERO_AMOUNT,
+ );
+ expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
});
- it('throws when mixed ERC721 and ERC20 assets with ERC20 first', async () => {
- const makerAssetId = erc721MakerAssetIds[0];
- const erc721SignedOrder = await orderFactory.newSignedOrderAsync({
- makerAssetAmount: new BigNumber(1),
- makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId),
- });
- const erc20SignedOrder = await orderFactory.newSignedOrderAsync();
- signedOrders = [erc20SignedOrder, erc721SignedOrder];
- const makerAssetAmount = new BigNumber(signedOrders.length);
- const fillAmountWei = erc20SignedOrder.takerAssetAmount.plus(erc721SignedOrder.takerAssetAmount);
- return expectTransactionFailedAsync(
- forwarderWrapper.marketBuyTokensWithEthAsync(signedOrders, feeOrders, makerAssetAmount, {
- from: takerAddress,
- value: fillAmountWei,
- }),
- RevertReason.InvalidTakerAmount,
+ it('should fail if the fee is set too high', async () => {
+ const ordersWithoutFee = [orderWithoutFee];
+ const feeOrders: SignedOrder[] = [];
+ const makerAssetFillAmount = orderWithoutFee.makerAssetAmount.dividedToIntegerBy(2);
+ const ethValue = orderWithoutFee.takerAssetAmount;
+
+ const baseFeePercentage = 6;
+ feePercentage = ForwarderWrapper.getPercentageOfValue(constants.PERCENTAGE_DENOMINATOR, baseFeePercentage);
+ await expectTransactionFailedAsync(
+ forwarderWrapper.marketBuyOrdersWithEthAsync(
+ ordersWithoutFee,
+ feeOrders,
+ makerAssetFillAmount,
+ {
+ value: ethValue,
+ from: takerAddress,
+ },
+ { feePercentage, feeRecipient: feeRecipientAddress },
+ ),
+ RevertReason.FeePercentageTooLarge,
+ );
+ });
+ it('should fail if there is not enough ETH remaining to pay the fee', async () => {
+ const ordersWithoutFee = [orderWithoutFee];
+ const feeOrders: SignedOrder[] = [];
+ const makerAssetFillAmount = orderWithoutFee.makerAssetAmount.dividedToIntegerBy(2);
+ const ethValue = orderWithoutFee.takerAssetAmount.dividedToIntegerBy(2);
+
+ const baseFeePercentage = 2;
+ feePercentage = ForwarderWrapper.getPercentageOfValue(constants.PERCENTAGE_DENOMINATOR, baseFeePercentage);
+ await expectTransactionFailedAsync(
+ forwarderWrapper.marketBuyOrdersWithEthAsync(
+ ordersWithoutFee,
+ feeOrders,
+ makerAssetFillAmount,
+ {
+ value: ethValue,
+ from: takerAddress,
+ },
+ { feePercentage, feeRecipient: feeRecipientAddress },
+ ),
+ RevertReason.InsufficientEthRemaining,
);
});
});
diff --git a/packages/contracts/test/utils/artifacts.ts b/packages/contracts/test/utils/artifacts.ts
index d3f808218..63bd555a4 100644
--- a/packages/contracts/test/utils/artifacts.ts
+++ b/packages/contracts/test/utils/artifacts.ts
@@ -15,12 +15,13 @@ import * as MultiSigWallet from '../../artifacts/MultiSigWallet.json';
import * as MultiSigWalletWithTimeLock from '../../artifacts/MultiSigWalletWithTimeLock.json';
import * as TestAssetProxyDispatcher from '../../artifacts/TestAssetProxyDispatcher.json';
import * as TestAssetProxyOwner from '../../artifacts/TestAssetProxyOwner.json';
+import * as TestConstants from '../../artifacts/TestConstants.json';
import * as TestLibBytes from '../../artifacts/TestLibBytes.json';
import * as TestLibs from '../../artifacts/TestLibs.json';
import * as TestSignatureValidator from '../../artifacts/TestSignatureValidator.json';
-import * as TestValidator from '../../artifacts/TestValidator.json';
-import * as TestWallet from '../../artifacts/TestWallet.json';
import * as TokenRegistry from '../../artifacts/TokenRegistry.json';
+import * as Validator from '../../artifacts/Validator.json';
+import * as Wallet from '../../artifacts/Wallet.json';
import * as EtherToken from '../../artifacts/WETH9.json';
import * as Whitelist from '../../artifacts/Whitelist.json';
import * as ZRX from '../../artifacts/ZRXToken.json';
@@ -42,11 +43,12 @@ export const artifacts = {
MultiSigWalletWithTimeLock: (MultiSigWalletWithTimeLock as any) as ContractArtifact,
TestAssetProxyOwner: (TestAssetProxyOwner as any) as ContractArtifact,
TestAssetProxyDispatcher: (TestAssetProxyDispatcher as any) as ContractArtifact,
+ TestConstants: (TestConstants as any) as ContractArtifact,
TestLibBytes: (TestLibBytes as any) as ContractArtifact,
TestLibs: (TestLibs as any) as ContractArtifact,
TestSignatureValidator: (TestSignatureValidator as any) as ContractArtifact,
- TestValidator: (TestValidator as any) as ContractArtifact,
- TestWallet: (TestWallet as any) as ContractArtifact,
+ Validator: (Validator as any) as ContractArtifact,
+ Wallet: (Wallet as any) as ContractArtifact,
TokenRegistry: (TokenRegistry as any) as ContractArtifact,
Whitelist: (Whitelist as any) as ContractArtifact,
ZRX: (ZRX as any) as ContractArtifact,
diff --git a/packages/contracts/test/utils/constants.ts b/packages/contracts/test/utils/constants.ts
index 7dac38f56..65eaee398 100644
--- a/packages/contracts/test/utils/constants.ts
+++ b/packages/contracts/test/utils/constants.ts
@@ -49,4 +49,6 @@ export const constants = {
takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 18),
},
WORD_LENGTH: 32,
+ ZERO_AMOUNT: new BigNumber(0),
+ PERCENTAGE_DENOMINATOR: new BigNumber(10).pow(18),
};
diff --git a/packages/contracts/test/utils/forwarder_wrapper.ts b/packages/contracts/test/utils/forwarder_wrapper.ts
index e39df14b1..ef7476e36 100644
--- a/packages/contracts/test/utils/forwarder_wrapper.ts
+++ b/packages/contracts/test/utils/forwarder_wrapper.ts
@@ -1,5 +1,4 @@
-import { assetDataUtils } from '@0xproject/order-utils';
-import { AssetProxyId, SignedOrder } from '@0xproject/types';
+import { SignedOrder } from '@0xproject/types';
import { BigNumber } from '@0xproject/utils';
import { Web3Wrapper } from '@0xproject/web3-wrapper';
import { Provider, TransactionReceiptWithDecodedLogs, TxDataPayable } from 'ethereum-types';
@@ -12,209 +11,108 @@ import { formatters } from './formatters';
import { LogDecoder } from './log_decoder';
import { MarketSellOrders } from './types';
-const DEFAULT_FEE_PROPORTION = 0;
-const PERCENTAGE_DENOMINATOR = 10000;
-const ZERO_AMOUNT = new BigNumber(0);
-const INSUFFICENT_ORDERS_FOR_MAKER_AMOUNT = 'Unable to satisfy makerAssetFillAmount with provided orders';
-
export class ForwarderWrapper {
private readonly _web3Wrapper: Web3Wrapper;
private readonly _forwarderContract: ForwarderContract;
private readonly _logDecoder: LogDecoder;
- private readonly _zrxAddress: string;
- private static _createOptimizedSellOrders(signedOrders: SignedOrder[]): MarketSellOrders {
- const marketSellOrders = formatters.createMarketSellOrders(signedOrders, ZERO_AMOUNT);
- const assetDataId = assetDataUtils.decodeAssetProxyId(signedOrders[0].makerAssetData);
- // Contract will fill this in for us as all of the assetData is assumed to be the same
- for (let i = 0; i < signedOrders.length; i++) {
- if (i !== 0 && assetDataId === AssetProxyId.ERC20) {
- // Forwarding contract will fill this in from the first order
- marketSellOrders.orders[i].makerAssetData = constants.NULL_BYTES;
+ public static getPercentageOfValue(value: BigNumber, percentage: number): BigNumber {
+ const numerator = constants.PERCENTAGE_DENOMINATOR.times(percentage).dividedToIntegerBy(100);
+ const newValue = value.times(numerator).dividedToIntegerBy(constants.PERCENTAGE_DENOMINATOR);
+ return newValue;
+ }
+ public static getWethForFeeOrders(feeAmount: BigNumber, feeOrders: SignedOrder[]): BigNumber {
+ let wethAmount = new BigNumber(0);
+ let remainingFeeAmount = feeAmount;
+ _.forEach(feeOrders, feeOrder => {
+ const feeAvailable = feeOrder.makerAssetAmount.minus(feeOrder.takerFee);
+ if (!remainingFeeAmount.isZero() && feeAvailable.gt(remainingFeeAmount)) {
+ wethAmount = wethAmount
+ .plus(feeOrder.takerAssetAmount.times(remainingFeeAmount).dividedToIntegerBy(feeAvailable))
+ .plus(1);
+ remainingFeeAmount = new BigNumber(0);
+ } else if (!remainingFeeAmount.isZero()) {
+ wethAmount = wethAmount.plus(feeOrder.takerAssetAmount);
+ remainingFeeAmount = remainingFeeAmount.minus(feeAvailable);
}
- marketSellOrders.orders[i].takerAssetData = constants.NULL_BYTES;
- }
- return marketSellOrders;
+ });
+ return wethAmount;
}
- private static _createOptimizedZRXSellOrders(signedOrders: SignedOrder[]): MarketSellOrders {
- const marketSellOrders = formatters.createMarketSellOrders(signedOrders, ZERO_AMOUNT);
- // Contract will fill this in for us as all of the assetData is assumed to be the same
- for (let i = 0; i < signedOrders.length; i++) {
- marketSellOrders.orders[i].makerAssetData = constants.NULL_BYTES;
- marketSellOrders.orders[i].takerAssetData = constants.NULL_BYTES;
- }
- return marketSellOrders;
+ private static _createOptimizedOrders(signedOrders: SignedOrder[]): MarketSellOrders {
+ _.forEach(signedOrders, (signedOrder, index) => {
+ signedOrder.takerAssetData = constants.NULL_BYTES;
+ if (index > 0) {
+ signedOrder.makerAssetData = constants.NULL_BYTES;
+ }
+ });
+ const params = formatters.createMarketSellOrders(signedOrders, constants.ZERO_AMOUNT);
+ return params;
}
- private static _calculateAdditionalFeeProportionAmount(feeProportion: number, fillAmountWei: BigNumber): BigNumber {
- if (feeProportion > 0) {
- // Add to the total ETH transaction to ensure all NFTs can be filled after fees
- // 150 = 1.5% = 0.015
- const denominator = new BigNumber(1).minus(new BigNumber(feeProportion).dividedBy(PERCENTAGE_DENOMINATOR));
- return fillAmountWei.dividedBy(denominator).round(0, BigNumber.ROUND_FLOOR);
- }
- return fillAmountWei;
+ private static _createOptimizedZrxOrders(signedOrders: SignedOrder[]): MarketSellOrders {
+ _.forEach(signedOrders, signedOrder => {
+ signedOrder.makerAssetData = constants.NULL_BYTES;
+ signedOrder.takerAssetData = constants.NULL_BYTES;
+ });
+ const params = formatters.createMarketSellOrders(signedOrders, constants.ZERO_AMOUNT);
+ return params;
}
- constructor(contractInstance: ForwarderContract, provider: Provider, zrxAddress: string) {
+ constructor(contractInstance: ForwarderContract, provider: Provider) {
this._forwarderContract = contractInstance;
this._web3Wrapper = new Web3Wrapper(provider);
this._logDecoder = new LogDecoder(this._web3Wrapper, this._forwarderContract.address);
- // this._web3Wrapper.abiDecoder.addABI(contractInstance.abi);
- this._zrxAddress = zrxAddress;
}
- public async marketBuyTokensWithEthAsync(
+ public async marketSellOrdersWithEthAsync(
orders: SignedOrder[],
feeOrders: SignedOrder[],
- makerTokenBuyAmount: BigNumber,
txData: TxDataPayable,
- opts: { feeProportion?: number; feeRecipient?: string } = {},
+ opts: { feePercentage?: BigNumber; feeRecipient?: string } = {},
): Promise<TransactionReceiptWithDecodedLogs> {
- const params = ForwarderWrapper._createOptimizedSellOrders(orders);
- const feeParams = ForwarderWrapper._createOptimizedZRXSellOrders(feeOrders);
- const feeProportion = _.isUndefined(opts.feeProportion) ? DEFAULT_FEE_PROPORTION : opts.feeProportion;
+ const params = ForwarderWrapper._createOptimizedOrders(orders);
+ const feeParams = ForwarderWrapper._createOptimizedZrxOrders(feeOrders);
+ const feePercentage = _.isUndefined(opts.feePercentage) ? constants.ZERO_AMOUNT : opts.feePercentage;
const feeRecipient = _.isUndefined(opts.feeRecipient) ? constants.NULL_ADDRESS : opts.feeRecipient;
- const txHash: string = await this._forwarderContract.marketBuyTokensWithEth.sendTransactionAsync(
+ const txHash = await this._forwarderContract.marketSellOrdersWithEth.sendTransactionAsync(
params.orders,
params.signatures,
feeParams.orders,
feeParams.signatures,
- makerTokenBuyAmount,
- feeProportion,
+ feePercentage,
feeRecipient,
txData,
);
const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash);
return tx;
}
- public async marketSellEthForERC20Async(
+ public async marketBuyOrdersWithEthAsync(
orders: SignedOrder[],
feeOrders: SignedOrder[],
+ makerAssetFillAmount: BigNumber,
txData: TxDataPayable,
- opts: { feeProportion?: number; feeRecipient?: string } = {},
+ opts: { feePercentage?: BigNumber; feeRecipient?: string } = {},
): Promise<TransactionReceiptWithDecodedLogs> {
- const assetDataId = assetDataUtils.decodeAssetProxyId(orders[0].makerAssetData);
- if (assetDataId !== AssetProxyId.ERC20) {
- throw new Error('Asset type not supported by marketSellEthForERC20');
- }
- const params = ForwarderWrapper._createOptimizedSellOrders(orders);
- const feeParams = ForwarderWrapper._createOptimizedZRXSellOrders(feeOrders);
- const feeProportion = _.isUndefined(opts.feeProportion) ? DEFAULT_FEE_PROPORTION : opts.feeProportion;
+ const params = ForwarderWrapper._createOptimizedOrders(orders);
+ const feeParams = ForwarderWrapper._createOptimizedZrxOrders(feeOrders);
+ const feePercentage = _.isUndefined(opts.feePercentage) ? constants.ZERO_AMOUNT : opts.feePercentage;
const feeRecipient = _.isUndefined(opts.feeRecipient) ? constants.NULL_ADDRESS : opts.feeRecipient;
- const txHash: string = await this._forwarderContract.marketSellEthForERC20.sendTransactionAsync(
+ const txHash = await this._forwarderContract.marketBuyOrdersWithEth.sendTransactionAsync(
params.orders,
+ makerAssetFillAmount,
params.signatures,
feeParams.orders,
feeParams.signatures,
- feeProportion,
+ feePercentage,
feeRecipient,
txData,
);
const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash);
return tx;
}
- public async calculateMarketBuyFillAmountWeiAsync(
- orders: SignedOrder[],
- feeOrders: SignedOrder[],
- feeProportion: number,
- makerAssetFillAmount: BigNumber,
- ): Promise<BigNumber> {
- const assetProxyId = assetDataUtils.decodeAssetProxyId(orders[0].makerAssetData);
- switch (assetProxyId) {
- case AssetProxyId.ERC20: {
- const fillAmountWei = this._calculateMarketBuyERC20FillAmountAsync(
- orders,
- feeOrders,
- feeProportion,
- makerAssetFillAmount,
- );
- return fillAmountWei;
- }
- case AssetProxyId.ERC721: {
- const fillAmountWei = await this._calculateMarketBuyERC721FillAmountAsync(
- orders,
- feeOrders,
- feeProportion,
- );
- return fillAmountWei;
- }
- default:
- throw new Error(`Invalid Asset Proxy Id: ${assetProxyId}`);
- }
- }
- private async _calculateMarketBuyERC20FillAmountAsync(
- orders: SignedOrder[],
- feeOrders: SignedOrder[],
- feeProportion: number,
- makerAssetFillAmount: BigNumber,
- ): Promise<BigNumber> {
- const makerAssetData = assetDataUtils.decodeAssetDataOrThrow(orders[0].makerAssetData);
- const makerAssetToken = makerAssetData.tokenAddress;
- const params = formatters.createMarketBuyOrders(orders, makerAssetFillAmount);
-
- let fillAmountWei;
- if (makerAssetToken === this._zrxAddress) {
- // If buying ZRX we buy the tokens and fees from the ZRX order in one step
- const expectedBuyFeeTokensFillResults = await this._forwarderContract.calculateMarketBuyZrxResults.callAsync(
- params.orders,
- makerAssetFillAmount,
- );
- if (expectedBuyFeeTokensFillResults.makerAssetFilledAmount.lessThan(makerAssetFillAmount)) {
- throw new Error(INSUFFICENT_ORDERS_FOR_MAKER_AMOUNT);
- }
- fillAmountWei = expectedBuyFeeTokensFillResults.takerAssetFilledAmount;
- } else {
- const expectedMarketBuyFillResults = await this._forwarderContract.calculateMarketBuyResults.callAsync(
- params.orders,
- makerAssetFillAmount,
- );
- if (expectedMarketBuyFillResults.makerAssetFilledAmount.lessThan(makerAssetFillAmount)) {
- throw new Error(INSUFFICENT_ORDERS_FOR_MAKER_AMOUNT);
- }
- fillAmountWei = expectedMarketBuyFillResults.takerAssetFilledAmount;
- const expectedFeeAmount = expectedMarketBuyFillResults.takerFeePaid;
- if (expectedFeeAmount.greaterThan(ZERO_AMOUNT)) {
- const expectedFeeFillFillAmountWei = await this._calculateMarketBuyERC20FillAmountAsync(
- feeOrders,
- [],
- DEFAULT_FEE_PROPORTION,
- expectedFeeAmount,
- );
- fillAmountWei = fillAmountWei.plus(expectedFeeFillFillAmountWei);
- }
- }
- fillAmountWei = ForwarderWrapper._calculateAdditionalFeeProportionAmount(feeProportion, fillAmountWei);
- return fillAmountWei;
- }
- private async _calculateMarketBuyERC721FillAmountAsync(
- orders: SignedOrder[],
- feeOrders: SignedOrder[],
- feeProportion: number,
- ): Promise<BigNumber> {
- // Total cost when buying ERC721 is the total cost of all ERC721 orders + any fee abstraction
- let fillAmountWei = _.reduce(
- orders,
- (totalAmount: BigNumber, order: SignedOrder) => {
- return totalAmount.plus(order.takerAssetAmount);
- },
- ZERO_AMOUNT,
- );
- const totalFees = _.reduce(
- orders,
- (totalAmount: BigNumber, order: SignedOrder) => {
- return totalAmount.plus(order.takerFee);
- },
- ZERO_AMOUNT,
- );
- if (totalFees.greaterThan(ZERO_AMOUNT)) {
- // Calculate the ZRX fee abstraction cost
- const emptyFeeOrders: SignedOrder[] = [];
- const expectedFeeAmountWei = await this._calculateMarketBuyERC20FillAmountAsync(
- feeOrders,
- emptyFeeOrders,
- DEFAULT_FEE_PROPORTION,
- totalFees,
- );
- fillAmountWei = fillAmountWei.plus(expectedFeeAmountWei);
- }
- fillAmountWei = ForwarderWrapper._calculateAdditionalFeeProportionAmount(feeProportion, fillAmountWei);
- return fillAmountWei;
+ public async withdrawERC20Async(
+ tokenAddress: string,
+ amount: BigNumber,
+ txData: TxDataPayable,
+ ): Promise<TransactionReceiptWithDecodedLogs> {
+ const txHash = await this._forwarderContract.withdrawERC20.sendTransactionAsync(tokenAddress, amount, txData);
+ const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash);
+ return tx;
}
}