handle reverts and ERC721 in ForwarderTestFactory + refactoring

This commit is contained in:
Michael Zhu 2019-07-23 22:15:36 -07:00
parent 673a341626
commit af6243afb0

View File

@ -1,5 +1,13 @@
import { ERC20Wrapper, ERC721Wrapper } from '@0x/contracts-asset-proxy'; import { ERC20Wrapper } from '@0x/contracts-asset-proxy';
import { chaiSetup, constants, ERC20BalancesByOwner, expectTransactionFailedAsync, web3Wrapper } from '@0x/contracts-test-utils'; import { DummyERC20TokenContract } from '@0x/contracts-erc20';
import { DummyERC721TokenContract } from '@0x/contracts-erc721';
import {
chaiSetup,
constants,
ERC20BalancesByOwner,
expectTransactionFailedAsync,
web3Wrapper,
} from '@0x/contracts-test-utils';
import { RevertReason, SignedOrder } from '@0x/types'; import { RevertReason, SignedOrder } from '@0x/types';
import { BigNumber } from '@0x/utils'; import { BigNumber } from '@0x/utils';
import * as chai from 'chai'; import * as chai from 'chai';
@ -20,10 +28,7 @@ interface ForwarderFillState {
maxOverboughtMakerAsset: BigNumber; maxOverboughtMakerAsset: BigNumber;
} }
function computeExpectedResults( function computeExpectedResults(orders: SignedOrder[], fractionalNumberOfOrdersToFill: BigNumber): ForwarderFillState {
orders: SignedOrder[],
fractionalNumberOfOrdersToFill: BigNumber,
): ForwarderFillState {
const currentState = { const currentState = {
takerAssetFillAmount: constants.ZERO_AMOUNT, takerAssetFillAmount: constants.ZERO_AMOUNT,
makerAssetFillAmount: constants.ZERO_AMOUNT, makerAssetFillAmount: constants.ZERO_AMOUNT,
@ -34,7 +39,9 @@ function computeExpectedResults(
}; };
let remainingOrdersToFill = fractionalNumberOfOrdersToFill; let remainingOrdersToFill = fractionalNumberOfOrdersToFill;
_.forEach(orders, (order: SignedOrder): void => { _.forEach(
orders,
(order: SignedOrder): void => {
if (remainingOrdersToFill.isEqualTo(constants.ZERO_AMOUNT)) { if (remainingOrdersToFill.isEqualTo(constants.ZERO_AMOUNT)) {
return; return;
} }
@ -44,8 +51,12 @@ function computeExpectedResults(
let takerFee; let takerFee;
if (remainingOrdersToFill.isLessThan(new BigNumber(1))) { if (remainingOrdersToFill.isLessThan(new BigNumber(1))) {
const [partialFillNumerator, partialFillDenominator] = remainingOrdersToFill.toFraction(); const [partialFillNumerator, partialFillDenominator] = remainingOrdersToFill.toFraction();
makerAssetAmount = order.makerAssetAmount.times(partialFillNumerator).dividedToIntegerBy(partialFillDenominator); makerAssetAmount = order.makerAssetAmount
takerAssetAmount = order.takerAssetAmount.times(partialFillNumerator).dividedToIntegerBy(partialFillDenominator); .times(partialFillNumerator)
.dividedToIntegerBy(partialFillDenominator);
takerAssetAmount = order.takerAssetAmount
.times(partialFillNumerator)
.dividedToIntegerBy(partialFillDenominator);
takerFee = order.takerFee.times(partialFillNumerator).dividedToIntegerBy(partialFillDenominator); takerFee = order.takerFee.times(partialFillNumerator).dividedToIntegerBy(partialFillDenominator);
} else { } else {
makerAssetAmount = order.makerAssetAmount; makerAssetAmount = order.makerAssetAmount;
@ -68,8 +79,9 @@ function computeExpectedResults(
.dividedToIntegerBy(takerAssetAmount); .dividedToIntegerBy(takerAssetAmount);
} }
remainingOrdersToFill = BigNumber.min(remainingOrdersToFill.minus(new BigNumber(1)), constants.ZERO_AMOUNT); remainingOrdersToFill = BigNumber.max(remainingOrdersToFill.minus(new BigNumber(1)), constants.ZERO_AMOUNT);
}); },
);
return currentState; return currentState;
} }
@ -121,21 +133,32 @@ export class ForwarderTestFactory {
public async marketBuyTestAsync( public async marketBuyTestAsync(
orders: SignedOrder[], orders: SignedOrder[],
fractionalNumberOfOrdersToFill: BigNumber, fractionalNumberOfOrdersToFill: BigNumber,
makerAssetAddress: string, makerAssetContract: DummyERC20TokenContract | DummyERC721TokenContract,
takerEthBalanceBefore: BigNumber, options: {
erc20Balances: ERC20BalancesByOwner, ethValueAdjustment?: BigNumber;
ethValueAdjustment: BigNumber = constants.ZERO_AMOUNT, forwarderFeePercentage?: BigNumber;
forwarderFeeOptions: { makerAssetId?: BigNumber;
baseFeePercentage: BigNumber; revertReason?: RevertReason;
forwarderFeeRecipientEthBalanceBefore: BigNumber; } = {},
} = { baseFeePercentage: constants.ZERO_AMOUNT, forwarderFeeRecipientEthBalanceBefore: constants.ZERO_AMOUNT },
): Promise<void> { ): Promise<void> {
const ethValueAdjustment = options.ethValueAdjustment || constants.ZERO_AMOUNT;
const forwarderFeePercentage = options.forwarderFeePercentage || constants.ZERO_AMOUNT;
const erc20Balances = await this._erc20Wrapper.getBalancesAsync();
const takerEthBalanceBefore = await web3Wrapper.getBalanceInWeiAsync(this._takerAddress);
const forwarderFeeRecipientEthBalanceBefore = await web3Wrapper.getBalanceInWeiAsync(
this._forwarderFeeRecipientAddress,
);
const expectedResults = computeExpectedResults(orders, fractionalNumberOfOrdersToFill); const expectedResults = computeExpectedResults(orders, fractionalNumberOfOrdersToFill);
const ethSpentOnForwarderFee = ForwarderTestFactory.getPercentageOfValue( const ethSpentOnForwarderFee = ForwarderTestFactory.getPercentageOfValue(
expectedResults.takerAssetFillAmount, expectedResults.takerAssetFillAmount,
forwarderFeeOptions.baseFeePercentage, forwarderFeePercentage,
);
const feePercentage = ForwarderTestFactory.getPercentageOfValue(
constants.PERCENTAGE_DENOMINATOR,
forwarderFeePercentage,
); );
const feePercentage = ForwarderTestFactory.getPercentageOfValue(constants.PERCENTAGE_DENOMINATOR, forwarderFeeOptions.baseFeePercentage);
const ethValue = expectedResults.takerAssetFillAmount const ethValue = expectedResults.takerAssetFillAmount
.plus(expectedResults.wethFees) .plus(expectedResults.wethFees)
@ -143,16 +166,20 @@ export class ForwarderTestFactory {
.plus(ethSpentOnForwarderFee) .plus(ethSpentOnForwarderFee)
.plus(ethValueAdjustment); .plus(ethValueAdjustment);
if (ethValueAdjustment.isNegative()) { if (options.revertReason !== undefined) {
return expectTransactionFailedAsync( await expectTransactionFailedAsync(
this._forwarderWrapper.marketBuyOrdersWithEthAsync( this._forwarderWrapper.marketBuyOrdersWithEthAsync(
orders, orders,
expectedResults.makerAssetFillAmount, { expectedResults.makerAssetFillAmount,
{
value: ethValue, value: ethValue,
from: this._takerAddress, from: this._takerAddress,
}), },
RevertReason.CompleteFillFailed, { feePercentage, feeRecipient: this._forwarderFeeRecipientAddress },
),
options.revertReason,
); );
return;
} }
const tx = await this._forwarderWrapper.marketBuyOrdersWithEthAsync( const tx = await this._forwarderWrapper.marketBuyOrdersWithEthAsync(
@ -165,40 +192,60 @@ export class ForwarderTestFactory {
{ feePercentage, feeRecipient: this._forwarderFeeRecipientAddress }, { feePercentage, feeRecipient: this._forwarderFeeRecipientAddress },
); );
await this._checkResultsAsync( await this._checkResultsAsync(tx, expectedResults, takerEthBalanceBefore, erc20Balances, makerAssetContract, {
tx, forwarderFeePercentage,
expectedResults, forwarderFeeRecipientEthBalanceBefore,
forwarderFeeOptions.forwarderFeeRecipientEthBalanceBefore, makerAssetId: options.makerAssetId,
takerEthBalanceBefore, });
erc20Balances,
ethSpentOnForwarderFee,
makerAssetAddress,
);
} }
public async marketSellTestAsync( public async marketSellTestAsync(
orders: SignedOrder[], orders: SignedOrder[],
fractionalNumberOfOrdersToFill: BigNumber, fractionalNumberOfOrdersToFill: BigNumber,
makerAssetAddress: string, makerAssetContract: DummyERC20TokenContract,
takerEthBalanceBefore: BigNumber, options: {
erc20Balances: ERC20BalancesByOwner, forwarderFeePercentage?: BigNumber;
forwarderFeeOptions: { revertReason?: RevertReason;
baseFeePercentage: BigNumber; } = {},
forwarderFeeRecipientEthBalanceBefore: BigNumber;
} = { baseFeePercentage: constants.ZERO_AMOUNT, forwarderFeeRecipientEthBalanceBefore: constants.ZERO_AMOUNT },
): Promise<void> { ): Promise<void> {
const forwarderFeePercentage = options.forwarderFeePercentage || constants.ZERO_AMOUNT;
const erc20Balances = await this._erc20Wrapper.getBalancesAsync();
const takerEthBalanceBefore = await web3Wrapper.getBalanceInWeiAsync(this._takerAddress);
const forwarderFeeRecipientEthBalanceBefore = await web3Wrapper.getBalanceInWeiAsync(
this._forwarderFeeRecipientAddress,
);
const expectedResults = computeExpectedResults(orders, fractionalNumberOfOrdersToFill); const expectedResults = computeExpectedResults(orders, fractionalNumberOfOrdersToFill);
const ethSpentOnForwarderFee = ForwarderTestFactory.getPercentageOfValue( const ethSpentOnForwarderFee = ForwarderTestFactory.getPercentageOfValue(
expectedResults.takerAssetFillAmount, expectedResults.takerAssetFillAmount,
forwarderFeeOptions.baseFeePercentage, forwarderFeePercentage,
);
const feePercentage = ForwarderTestFactory.getPercentageOfValue(
constants.PERCENTAGE_DENOMINATOR,
forwarderFeePercentage,
); );
const feePercentage = ForwarderTestFactory.getPercentageOfValue(constants.PERCENTAGE_DENOMINATOR, forwarderFeeOptions.baseFeePercentage);
const ethValue = expectedResults.takerAssetFillAmount const ethValue = expectedResults.takerAssetFillAmount
.plus(expectedResults.wethFees) .plus(expectedResults.wethFees)
.plus(expectedResults.maxOversoldWeth) .plus(expectedResults.maxOversoldWeth)
.plus(ethSpentOnForwarderFee); .plus(ethSpentOnForwarderFee);
if (options.revertReason !== undefined) {
await expectTransactionFailedAsync(
this._forwarderWrapper.marketSellOrdersWithEthAsync(
orders,
{
value: ethValue,
from: this._takerAddress,
},
{ feePercentage, feeRecipient: this._forwarderFeeRecipientAddress },
),
options.revertReason,
);
return;
}
const tx = await this._forwarderWrapper.marketSellOrdersWithEthAsync( const tx = await this._forwarderWrapper.marketSellOrdersWithEthAsync(
orders, orders,
{ {
@ -208,33 +255,65 @@ export class ForwarderTestFactory {
{ feePercentage, feeRecipient: this._forwarderFeeRecipientAddress }, { feePercentage, feeRecipient: this._forwarderFeeRecipientAddress },
); );
await this._checkResultsAsync( await this._checkResultsAsync(tx, expectedResults, takerEthBalanceBefore, erc20Balances, makerAssetContract, {
tx, forwarderFeePercentage,
expectedResults, forwarderFeeRecipientEthBalanceBefore,
forwarderFeeOptions.forwarderFeeRecipientEthBalanceBefore, });
takerEthBalanceBefore, }
erc20Balances,
ethSpentOnForwarderFee, private _checkErc20Balances(
makerAssetAddress, oldBalances: ERC20BalancesByOwner,
newBalances: ERC20BalancesByOwner,
expectedResults: ForwarderFillState,
makerAssetContract: DummyERC20TokenContract,
): void {
const makerAssetAddress = makerAssetContract.address;
expectBalanceWithin(
newBalances[this._makerAddress][makerAssetAddress],
oldBalances[this._makerAddress][makerAssetAddress]
.minus(expectedResults.makerAssetFillAmount)
.minus(expectedResults.maxOverboughtMakerAsset),
oldBalances[this._makerAddress][makerAssetAddress].minus(expectedResults.makerAssetFillAmount),
); );
expectBalanceWithin(
newBalances[this._takerAddress][makerAssetAddress],
oldBalances[this._takerAddress][makerAssetAddress]
.plus(expectedResults.makerAssetFillAmount)
.minus(expectedResults.percentageFees),
oldBalances[this._takerAddress][makerAssetAddress]
.plus(expectedResults.makerAssetFillAmount)
.minus(expectedResults.percentageFees)
.plus(expectedResults.maxOverboughtMakerAsset),
);
expect(newBalances[this._orderFeeRecipientAddress][makerAssetAddress]).to.be.bignumber.equal(
oldBalances[this._orderFeeRecipientAddress][makerAssetAddress].plus(expectedResults.percentageFees),
);
expect(newBalances[this._forwarderAddress][makerAssetAddress]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
} }
private async _checkResultsAsync( private async _checkResultsAsync(
tx: TransactionReceiptWithDecodedLogs, tx: TransactionReceiptWithDecodedLogs,
expectedResults: ForwarderFillState, expectedResults: ForwarderFillState,
forwarderFeeRecipientEthBalanceBefore: BigNumber,
takerEthBalanceBefore: BigNumber, takerEthBalanceBefore: BigNumber,
erc20Balances: ERC20BalancesByOwner, erc20Balances: ERC20BalancesByOwner,
ethSpentOnForwarderFee: BigNumber, makerAssetContract: DummyERC20TokenContract | DummyERC721TokenContract,
makerAssetAddress: string, options: {
forwarderFeePercentage?: BigNumber;
forwarderFeeRecipientEthBalanceBefore?: BigNumber;
makerAssetId?: BigNumber;
} = {},
): Promise<void> { ): Promise<void> {
const ethSpentOnForwarderFee = ForwarderTestFactory.getPercentageOfValue(
expectedResults.takerAssetFillAmount,
options.forwarderFeePercentage || constants.ZERO_AMOUNT,
);
const totalEthSpent = expectedResults.takerAssetFillAmount const totalEthSpent = expectedResults.takerAssetFillAmount
.plus(expectedResults.wethFees) .plus(expectedResults.wethFees)
.plus(ethSpentOnForwarderFee) .plus(ethSpentOnForwarderFee)
.plus(this._gasPrice.times(tx.gasUsed)); .plus(this._gasPrice.times(tx.gasUsed));
const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(this._takerAddress); const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(this._takerAddress);
const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(this._forwarderAddress); const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(this._forwarderAddress);
const fowarderFeeRecipientEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(this._forwarderFeeRecipientAddress);
const newBalances = await this._erc20Wrapper.getBalancesAsync(); const newBalances = await this._erc20Wrapper.getBalancesAsync();
expectBalanceWithin( expectBalanceWithin(
@ -242,32 +321,21 @@ export class ForwarderTestFactory {
takerEthBalanceBefore.minus(totalEthSpent).minus(expectedResults.maxOversoldWeth), takerEthBalanceBefore.minus(totalEthSpent).minus(expectedResults.maxOversoldWeth),
takerEthBalanceBefore.minus(totalEthSpent), takerEthBalanceBefore.minus(totalEthSpent),
); );
if (ethSpentOnForwarderFee.gt(0)) { if (options.forwarderFeeRecipientEthBalanceBefore !== undefined) {
const fowarderFeeRecipientEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(
this._forwarderFeeRecipientAddress,
);
expect(fowarderFeeRecipientEthBalanceAfter).to.be.bignumber.equal( expect(fowarderFeeRecipientEthBalanceAfter).to.be.bignumber.equal(
forwarderFeeRecipientEthBalanceBefore.plus(ethSpentOnForwarderFee), options.forwarderFeeRecipientEthBalanceBefore.plus(ethSpentOnForwarderFee),
); );
} }
expectBalanceWithin( if (makerAssetContract instanceof DummyERC20TokenContract) {
newBalances[this._makerAddress][makerAssetAddress], await this._checkErc20Balances(erc20Balances, newBalances, expectedResults, makerAssetContract);
erc20Balances[this._makerAddress][makerAssetAddress] } else if (options.makerAssetId !== undefined) {
.minus(expectedResults.makerAssetFillAmount) const newOwner = await makerAssetContract.ownerOf.callAsync(options.makerAssetId);
.minus(expectedResults.maxOverboughtMakerAsset), expect(newOwner).to.be.bignumber.equal(this._takerAddress);
erc20Balances[this._makerAddress][makerAssetAddress].minus(expectedResults.makerAssetFillAmount), }
);
expectBalanceWithin(
newBalances[this._takerAddress][makerAssetAddress],
erc20Balances[this._takerAddress][makerAssetAddress]
.plus(expectedResults.makerAssetFillAmount)
.minus(expectedResults.percentageFees),
erc20Balances[this._takerAddress][makerAssetAddress]
.plus(expectedResults.makerAssetFillAmount)
.minus(expectedResults.percentageFees)
.plus(expectedResults.maxOverboughtMakerAsset),
);
expect(newBalances[this._orderFeeRecipientAddress][makerAssetAddress]).to.be.bignumber.equal(
erc20Balances[this._orderFeeRecipientAddress][makerAssetAddress].plus(expectedResults.percentageFees),
);
expectBalanceWithin( expectBalanceWithin(
newBalances[this._makerAddress][this._wethAddress], newBalances[this._makerAddress][this._wethAddress],
@ -281,7 +349,6 @@ export class ForwarderTestFactory {
); );
expect(newBalances[this._forwarderAddress][this._wethAddress]).to.be.bignumber.equal(constants.ZERO_AMOUNT); expect(newBalances[this._forwarderAddress][this._wethAddress]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
expect(newBalances[this._forwarderAddress][makerAssetAddress]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
} }
} }