@0x/contracts-exchange
: Create reference functions test util.
`@0x/contracts-exchange`: Use reference functions to assert fill results in `isolated_fill_order` tests.
This commit is contained in:
parent
38a1f08413
commit
c54d69e5ae
@ -23,7 +23,7 @@ const expect = chai.expect;
|
|||||||
|
|
||||||
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
|
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
|
||||||
|
|
||||||
const MAX_UINT256 = new BigNumber(2).pow(256).minus(1);
|
const { MAX_UINT256 } = constants;
|
||||||
|
|
||||||
const emptyOrder: Order = {
|
const emptyOrder: Order = {
|
||||||
senderAddress: constants.NULL_ADDRESS,
|
senderAddress: constants.NULL_ADDRESS,
|
||||||
|
@ -1,59 +1,117 @@
|
|||||||
import { blockchainTests, constants, expect, hexRandom } from '@0x/contracts-test-utils';
|
import {
|
||||||
|
blockchainTests,
|
||||||
|
constants,
|
||||||
|
expect,
|
||||||
|
FillResults,
|
||||||
|
hexRandom,
|
||||||
|
} from '@0x/contracts-test-utils';
|
||||||
import { BigNumber } from '@0x/utils';
|
import { BigNumber } from '@0x/utils';
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
import { IsolatedExchangeWrapper, Order } from './utils/isolated_exchange_wrapper';
|
import { AssetBalances, IsolatedExchangeWrapper, Orderish } from './utils/isolated_exchange_wrapper';
|
||||||
|
import { calculateFillResults } from './utils/reference_functions';
|
||||||
|
|
||||||
blockchainTests.resets.only('Isolated fillOrder() tests', env => {
|
blockchainTests.resets.only('Isolated fillOrder() tests', env => {
|
||||||
|
const { ZERO_AMOUNT } = constants;
|
||||||
const TOMORROW = Math.floor(_.now() / 1000) + 60 * 60 * 24;
|
const TOMORROW = Math.floor(_.now() / 1000) + 60 * 60 * 24;
|
||||||
const ERC20_ASSET_DATA_LENGTH = 24;
|
const ERC20_ASSET_DATA_LENGTH = 24;
|
||||||
const DEFAULT_ORDER: Order = {
|
const randomAddress = () => hexRandom(constants.ADDRESS_LENGTH);
|
||||||
|
const DEFAULT_ORDER: Orderish = {
|
||||||
senderAddress: constants.NULL_ADDRESS,
|
senderAddress: constants.NULL_ADDRESS,
|
||||||
makerAddress: randomAddress(),
|
makerAddress: randomAddress(),
|
||||||
takerAddress: constants.NULL_ADDRESS,
|
takerAddress: constants.NULL_ADDRESS,
|
||||||
makerFee: constants.ZERO_AMOUNT,
|
makerFee: ZERO_AMOUNT,
|
||||||
takerFee: constants.ZERO_AMOUNT,
|
takerFee: ZERO_AMOUNT,
|
||||||
makerAssetAmount: constants.ZERO_AMOUNT,
|
makerAssetAmount: ZERO_AMOUNT,
|
||||||
takerAssetAmount: constants.ZERO_AMOUNT,
|
takerAssetAmount: ZERO_AMOUNT,
|
||||||
salt: constants.ZERO_AMOUNT,
|
salt: ZERO_AMOUNT,
|
||||||
feeRecipientAddress: constants.NULL_ADDRESS,
|
feeRecipientAddress: constants.NULL_ADDRESS,
|
||||||
expirationTimeSeconds: toBN(TOMORROW),
|
expirationTimeSeconds: new BigNumber(TOMORROW),
|
||||||
makerAssetData: hexRandom(ERC20_ASSET_DATA_LENGTH),
|
makerAssetData: hexRandom(ERC20_ASSET_DATA_LENGTH),
|
||||||
takerAssetData: hexRandom(ERC20_ASSET_DATA_LENGTH),
|
takerAssetData: hexRandom(ERC20_ASSET_DATA_LENGTH),
|
||||||
makerFeeAssetData: hexRandom(ERC20_ASSET_DATA_LENGTH),
|
makerFeeAssetData: hexRandom(ERC20_ASSET_DATA_LENGTH),
|
||||||
takerFeeAssetData: hexRandom(ERC20_ASSET_DATA_LENGTH),
|
takerFeeAssetData: hexRandom(ERC20_ASSET_DATA_LENGTH),
|
||||||
};
|
};
|
||||||
let takerAddress: string;
|
let takerAddress: string;
|
||||||
let testExchange: IsolatedExchangeWrapper;
|
let exchange: IsolatedExchangeWrapper;
|
||||||
let nextSaltValue = 1;
|
let nextSaltValue = 1;
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
[takerAddress] = await env.getAccountAddressesAsync();
|
[ takerAddress ] = await env.getAccountAddressesAsync();
|
||||||
testExchange = await IsolatedExchangeWrapper.deployAsync(
|
exchange = await IsolatedExchangeWrapper.deployAsync(
|
||||||
env.web3Wrapper,
|
env.web3Wrapper,
|
||||||
_.assign(env.txDefaults, { from: takerAddress }),
|
_.assign(env.txDefaults, { from: takerAddress }),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
function createOrder(details: Partial<Order> = {}): Order {
|
function createOrder(details: Partial<Orderish> = {}): Orderish {
|
||||||
return _.assign({}, DEFAULT_ORDER, { salt: toBN(nextSaltValue++) }, details);
|
return _.assign({}, DEFAULT_ORDER, { salt: new BigNumber(nextSaltValue++) }, details);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const i of _.times(100)) {
|
async function fillOrderAndAssertResultsAsync(
|
||||||
it('works', async () => {
|
order: Orderish,
|
||||||
const order = createOrder({
|
takerAssetFillAmount: BigNumber,
|
||||||
makerAssetAmount: toBN(1),
|
): Promise<FillResults> {
|
||||||
takerAssetAmount: toBN(2),
|
const efr = await calculateExpectedFillResultsAsync(order, takerAssetFillAmount);
|
||||||
});
|
const efb = calculateExpectedFillBalances(order, efr);
|
||||||
const results = await testExchange.fillOrderAsync(order, 2);
|
const fillResults = await exchange.fillOrderAsync(order, takerAssetFillAmount);
|
||||||
|
// Check returned fillResults.
|
||||||
|
expect(fillResults.makerAssetFilledAmount)
|
||||||
|
.to.bignumber.eq(efr.makerAssetFilledAmount);
|
||||||
|
expect(fillResults.takerAssetFilledAmount)
|
||||||
|
.to.bignumber.eq(efr.takerAssetFilledAmount);
|
||||||
|
expect(fillResults.makerFeePaid)
|
||||||
|
.to.bignumber.eq(efr.makerFeePaid);
|
||||||
|
expect(fillResults.takerFeePaid)
|
||||||
|
.to.bignumber.eq(efr.takerFeePaid);
|
||||||
|
// Check balances.
|
||||||
|
for (const assetData of Object.keys(efb)) {
|
||||||
|
for (const address of Object.keys(efb[assetData])) {
|
||||||
|
expect(exchange.getBalanceChange(assetData, address))
|
||||||
|
.to.bignumber.eq(efb[assetData][address], `assetData: ${assetData}, address: ${address}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fillResults;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function calculateExpectedFillResultsAsync(
|
||||||
|
order: Orderish,
|
||||||
|
takerAssetFillAmount: BigNumber,
|
||||||
|
): Promise<FillResults> {
|
||||||
|
const takerAssetFilledAmount = await exchange.getTakerAssetFilledAmountAsync(order);
|
||||||
|
const remainingTakerAssetAmount = order.takerAssetAmount.minus(takerAssetFilledAmount);
|
||||||
|
return calculateFillResults(
|
||||||
|
order,
|
||||||
|
BigNumber.min(takerAssetFillAmount, remainingTakerAssetAmount),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculateExpectedFillBalances(
|
||||||
|
order: Orderish,
|
||||||
|
fillResults: FillResults,
|
||||||
|
): AssetBalances {
|
||||||
|
const balances: AssetBalances = {};
|
||||||
|
const addBalance = (assetData: string, address: string, amount: BigNumber) => {
|
||||||
|
balances[assetData] = balances[assetData] || {};
|
||||||
|
const balance = balances[assetData][address] || ZERO_AMOUNT;
|
||||||
|
balances[assetData][address] = balance.plus(amount);
|
||||||
|
};
|
||||||
|
addBalance(order.makerAssetData, order.makerAddress, fillResults.makerAssetFilledAmount.negated());
|
||||||
|
addBalance(order.makerAssetData, takerAddress, fillResults.makerAssetFilledAmount);
|
||||||
|
addBalance(order.takerAssetData, order.makerAddress, fillResults.takerAssetFilledAmount);
|
||||||
|
addBalance(order.takerAssetData, takerAddress, fillResults.takerAssetFilledAmount.negated());
|
||||||
|
addBalance(order.makerFeeAssetData, order.makerAddress, fillResults.makerFeePaid.negated());
|
||||||
|
addBalance(order.makerFeeAssetData, order.feeRecipientAddress, fillResults.makerFeePaid);
|
||||||
|
addBalance(order.takerFeeAssetData, takerAddress, fillResults.takerFeePaid.negated());
|
||||||
|
addBalance(order.takerFeeAssetData, order.feeRecipientAddress, fillResults.takerFeePaid);
|
||||||
|
return balances;
|
||||||
|
}
|
||||||
|
|
||||||
|
it('can fully fill an order', async () => {
|
||||||
|
const order = createOrder({
|
||||||
|
makerAssetAmount: new BigNumber(1),
|
||||||
|
takerAssetAmount: new BigNumber(2),
|
||||||
});
|
});
|
||||||
}
|
return fillOrderAndAssertResultsAsync(order, order.takerAssetAmount);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function toBN(num: BigNumber | string | number): BigNumber {
|
|
||||||
return new BigNumber(num);
|
|
||||||
}
|
|
||||||
|
|
||||||
function randomAddress(): string {
|
|
||||||
return hexRandom(constants.ADDRESS_LENGTH);
|
|
||||||
}
|
|
||||||
|
@ -22,7 +22,8 @@ export interface IsolatedExchangeEvents {
|
|||||||
transferFromCalls: DispatchTransferFromCallArgs[];
|
transferFromCalls: DispatchTransferFromCallArgs[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Order = OrderWithoutDomain;
|
export type Orderish = OrderWithoutDomain;
|
||||||
|
export type Numberish = string | number | BigNumber;
|
||||||
|
|
||||||
export const DEFAULT_GOOD_SIGNATURE = createGoodSignature();
|
export const DEFAULT_GOOD_SIGNATURE = createGoodSignature();
|
||||||
export const DEFAULT_BAD_SIGNATURE = createBadSignature();
|
export const DEFAULT_BAD_SIGNATURE = createBadSignature();
|
||||||
@ -65,9 +66,13 @@ export class IsolatedExchangeWrapper {
|
|||||||
this.logDecoder = new LogDecoder(web3Wrapper, artifacts);
|
this.logDecoder = new LogDecoder(web3Wrapper, artifacts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async getTakerAssetFilledAmountAsync(order: Orderish): Promise<BigNumber> {
|
||||||
|
return this.instance.filled.callAsync(this.getOrderHash(order));
|
||||||
|
}
|
||||||
|
|
||||||
public async fillOrderAsync(
|
public async fillOrderAsync(
|
||||||
order: Order,
|
order: Orderish,
|
||||||
takerAssetFillAmount: BigNumber | number,
|
takerAssetFillAmount: Numberish,
|
||||||
signature: string = DEFAULT_GOOD_SIGNATURE,
|
signature: string = DEFAULT_GOOD_SIGNATURE,
|
||||||
txOpts?: TxData,
|
txOpts?: TxData,
|
||||||
): Promise<FillResults> {
|
): Promise<FillResults> {
|
||||||
@ -80,7 +85,7 @@ export class IsolatedExchangeWrapper {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getOrderHash(order: Order): string {
|
public getOrderHash(order: Orderish): string {
|
||||||
const domain = {
|
const domain = {
|
||||||
verifyingContractAddress: this.instance.address,
|
verifyingContractAddress: this.instance.address,
|
||||||
chainId: IsolatedExchangeWrapper.CHAIN_ID,
|
chainId: IsolatedExchangeWrapper.CHAIN_ID,
|
||||||
@ -125,14 +130,14 @@ interface TransactionContractFunction<TResult> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dev Create a signature for the `TestIsolatedExchange` contract that will pass.
|
* Create a signature for the `TestIsolatedExchange` contract that will pass.
|
||||||
*/
|
*/
|
||||||
export function createGoodSignature(type: SignatureType = SignatureType.EIP712): string {
|
export function createGoodSignature(type: SignatureType = SignatureType.EIP712): string {
|
||||||
return `0x01${Buffer.from([type]).toString('hex')}`;
|
return `0x01${Buffer.from([type]).toString('hex')}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dev Create a signature for the `TestIsolatedExchange` contract that will fail.
|
* Create a signature for the `TestIsolatedExchange` contract that will fail.
|
||||||
*/
|
*/
|
||||||
export function createBadSignature(type: SignatureType = SignatureType.EIP712): string {
|
export function createBadSignature(type: SignatureType = SignatureType.EIP712): string {
|
||||||
return `0x00${Buffer.from([type]).toString('hex')}`;
|
return `0x00${Buffer.from([type]).toString('hex')}`;
|
||||||
|
107
contracts/exchange/test/utils/reference_functions.ts
Normal file
107
contracts/exchange/test/utils/reference_functions.ts
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
import { constants, FillResults } from '@0x/contracts-test-utils';
|
||||||
|
import { LibMathRevertErrors } from '@0x/order-utils';
|
||||||
|
import { OrderWithoutDomain } from '@0x/types';
|
||||||
|
import { AnyRevertError, BigNumber, SafeMathRevertErrors } from '@0x/utils';
|
||||||
|
|
||||||
|
const { MAX_UINT256 } = constants;
|
||||||
|
|
||||||
|
export function isRoundingErrorFloor(
|
||||||
|
numerator: BigNumber,
|
||||||
|
denominator: BigNumber,
|
||||||
|
target: BigNumber,
|
||||||
|
): boolean {
|
||||||
|
if (denominator.eq(0)) {
|
||||||
|
throw new LibMathRevertErrors.DivisionByZeroError();
|
||||||
|
}
|
||||||
|
if (numerator.eq(0)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (target.eq(0)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const product = numerator.multipliedBy(target);
|
||||||
|
const remainder = product.mod(denominator);
|
||||||
|
const remainderTimes1000 = remainder.multipliedBy('1000');
|
||||||
|
const isError = remainderTimes1000.gte(product);
|
||||||
|
if (remainderTimes1000.isGreaterThan(MAX_UINT256)) {
|
||||||
|
// Solidity implementation won't actually throw.
|
||||||
|
throw new AnyRevertError();
|
||||||
|
}
|
||||||
|
return isError;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function IsRoundingErrorCeil(
|
||||||
|
numerator: BigNumber,
|
||||||
|
denominator: BigNumber,
|
||||||
|
target: BigNumber,
|
||||||
|
): boolean {
|
||||||
|
if (denominator.eq(0)) {
|
||||||
|
throw new LibMathRevertErrors.DivisionByZeroError();
|
||||||
|
}
|
||||||
|
if (numerator.eq(0)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (target.eq(0)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const product = numerator.multipliedBy(target);
|
||||||
|
const remainder = product.mod(denominator);
|
||||||
|
const error = denominator.minus(remainder).mod(denominator);
|
||||||
|
const errorTimes1000 = error.multipliedBy('1000');
|
||||||
|
const isError = errorTimes1000.gte(product);
|
||||||
|
if (errorTimes1000.isGreaterThan(MAX_UINT256)) {
|
||||||
|
// Solidity implementation won't actually throw.
|
||||||
|
throw new AnyRevertError();
|
||||||
|
}
|
||||||
|
return isError;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function safeGetPartialAmountFloor(
|
||||||
|
numerator: BigNumber,
|
||||||
|
denominator: BigNumber,
|
||||||
|
target: BigNumber,
|
||||||
|
): BigNumber {
|
||||||
|
if (denominator.eq(0)) {
|
||||||
|
throw new LibMathRevertErrors.DivisionByZeroError();
|
||||||
|
}
|
||||||
|
const isRoundingError = isRoundingErrorFloor(numerator, denominator, target);
|
||||||
|
if (isRoundingError) {
|
||||||
|
throw new LibMathRevertErrors.RoundingError(numerator, denominator, target);
|
||||||
|
}
|
||||||
|
const product = numerator.multipliedBy(target);
|
||||||
|
if (product.isGreaterThan(MAX_UINT256)) {
|
||||||
|
throw new SafeMathRevertErrors.SafeMathError(
|
||||||
|
SafeMathRevertErrors.SafeMathErrorCodes.Uint256MultiplicationOverflow,
|
||||||
|
numerator,
|
||||||
|
denominator,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return product.dividedToIntegerBy(denominator);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function calculateFillResults(
|
||||||
|
order: OrderWithoutDomain,
|
||||||
|
takerAssetFilledAmount: BigNumber,
|
||||||
|
): FillResults {
|
||||||
|
const makerAssetFilledAmount = safeGetPartialAmountFloor(
|
||||||
|
takerAssetFilledAmount,
|
||||||
|
order.takerAssetAmount,
|
||||||
|
order.makerAssetAmount,
|
||||||
|
);
|
||||||
|
const makerFeePaid = safeGetPartialAmountFloor(
|
||||||
|
makerAssetFilledAmount,
|
||||||
|
order.makerAssetAmount,
|
||||||
|
order.makerFee,
|
||||||
|
);
|
||||||
|
const takerFeePaid = safeGetPartialAmountFloor(
|
||||||
|
takerAssetFilledAmount,
|
||||||
|
order.takerAssetAmount,
|
||||||
|
order.takerFee,
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
makerAssetFilledAmount,
|
||||||
|
takerAssetFilledAmount,
|
||||||
|
makerFeePaid,
|
||||||
|
takerFeePaid,
|
||||||
|
};
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user