250 lines
10 KiB
TypeScript
250 lines
10 KiB
TypeScript
import {
|
|
artifacts as proxyArtifacts,
|
|
ERC1155ProxyWrapper,
|
|
ERC20Wrapper,
|
|
ERC721Wrapper,
|
|
} from '@0x/contracts-asset-proxy';
|
|
import { artifacts as erc20Artifacts } from '@0x/contracts-erc20';
|
|
import { artifacts as erc721Artifacts } from '@0x/contracts-erc721';
|
|
import { ReferenceFunctions as LibReferenceFunctions } from '@0x/contracts-exchange-libs';
|
|
import {
|
|
constants,
|
|
expect,
|
|
FillEventArgs,
|
|
filterLogsToArguments,
|
|
LogDecoder,
|
|
OrderStatus,
|
|
orderUtils,
|
|
Web3ProviderEngine,
|
|
} from '@0x/contracts-test-utils';
|
|
import { orderHashUtils } from '@0x/order-utils';
|
|
import { FillResults, SignedOrder } from '@0x/types';
|
|
import { BigNumber } from '@0x/utils';
|
|
import { Web3Wrapper } from '@0x/web3-wrapper';
|
|
import { TransactionReceiptWithDecodedLogs, ZeroExProvider } from 'ethereum-types';
|
|
import * as _ from 'lodash';
|
|
|
|
import { artifacts, ExchangeContract } from '../../src';
|
|
import { BalanceStore } from '../balance_stores/balance_store';
|
|
import { BlockchainBalanceStore } from '../balance_stores/blockchain_balance_store';
|
|
import { LocalBalanceStore } from '../balance_stores/local_balance_store';
|
|
|
|
export class FillOrderWrapper {
|
|
private readonly _exchange: ExchangeContract;
|
|
private readonly _blockchainBalanceStore: BlockchainBalanceStore;
|
|
private readonly _web3Wrapper: Web3Wrapper;
|
|
|
|
/**
|
|
* Simulates matching two orders by transferring amounts defined in
|
|
* `transferAmounts` and returns the results.
|
|
* @param orders The orders being matched and their filled states.
|
|
* @param takerAddress Address of taker (the address who matched the two orders)
|
|
* @param tokenBalances Current token balances.
|
|
* @param transferAmounts Amounts to transfer during the simulation.
|
|
* @return The new account balances and fill events that occurred during the match.
|
|
*/
|
|
public static simulateFillOrder(
|
|
signedOrder: SignedOrder,
|
|
takerAddress: string,
|
|
opts: { takerAssetFillAmount?: BigNumber } = {},
|
|
initBalanceStore: BalanceStore,
|
|
): [FillResults, FillEventArgs, BalanceStore] {
|
|
const balanceStore = LocalBalanceStore.create(initBalanceStore);
|
|
const takerAssetFillAmount =
|
|
opts.takerAssetFillAmount !== undefined ? opts.takerAssetFillAmount : signedOrder.takerAssetAmount;
|
|
// TODO(jalextowle): Change this if the integration tests take protocol fees into account.
|
|
const fillResults = LibReferenceFunctions.calculateFillResults(
|
|
signedOrder,
|
|
takerAssetFillAmount,
|
|
constants.ZERO_AMOUNT,
|
|
constants.ZERO_AMOUNT,
|
|
);
|
|
const fillEvent = FillOrderWrapper.simulateFillEvent(signedOrder, takerAddress, fillResults);
|
|
// Taker -> Maker
|
|
balanceStore.transferAsset(
|
|
takerAddress,
|
|
signedOrder.makerAddress,
|
|
fillResults.takerAssetFilledAmount,
|
|
signedOrder.takerAssetData,
|
|
);
|
|
// Maker -> Taker
|
|
balanceStore.transferAsset(
|
|
signedOrder.makerAddress,
|
|
takerAddress,
|
|
fillResults.makerAssetFilledAmount,
|
|
signedOrder.makerAssetData,
|
|
);
|
|
// Taker -> Fee Recipient
|
|
balanceStore.transferAsset(
|
|
takerAddress,
|
|
signedOrder.feeRecipientAddress,
|
|
fillResults.takerFeePaid,
|
|
signedOrder.takerFeeAssetData,
|
|
);
|
|
// Maker -> Fee Recipient
|
|
balanceStore.transferAsset(
|
|
signedOrder.makerAddress,
|
|
signedOrder.feeRecipientAddress,
|
|
fillResults.makerFeePaid,
|
|
signedOrder.makerFeeAssetData,
|
|
);
|
|
return [fillResults, fillEvent, balanceStore];
|
|
}
|
|
|
|
/**
|
|
* Simulates the event emitted by the exchange contract when an order is filled.
|
|
*/
|
|
public static simulateFillEvent(order: SignedOrder, takerAddress: string, fillResults: FillResults): FillEventArgs {
|
|
// prettier-ignore
|
|
return {
|
|
orderHash: orderHashUtils.getOrderHashHex(order),
|
|
makerAddress: order.makerAddress,
|
|
takerAddress,
|
|
makerAssetFilledAmount: fillResults.makerAssetFilledAmount,
|
|
takerAssetFilledAmount: fillResults.takerAssetFilledAmount,
|
|
makerFeePaid: fillResults.makerFeePaid,
|
|
takerFeePaid: fillResults.takerFeePaid,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Extract the exchanges `Fill` event from a transaction receipt.
|
|
*/
|
|
private static _extractFillEventsfromReceipt(receipt: TransactionReceiptWithDecodedLogs): FillEventArgs[] {
|
|
const events = filterLogsToArguments<FillEventArgs>(receipt.logs, 'Fill');
|
|
const fieldsOfInterest = [
|
|
'orderHash',
|
|
'makerAddress',
|
|
'takerAddress',
|
|
'makerAssetFilledAmount',
|
|
'takerAssetFilledAmount',
|
|
'makerFeePaid',
|
|
'takerFeePaid',
|
|
];
|
|
return events.map(event => _.pick(event, fieldsOfInterest)) as FillEventArgs[];
|
|
}
|
|
|
|
/**
|
|
* Constructor.
|
|
* @param exchangeContract Insstance of the deployed exchange contract
|
|
* @param erc20Wrapper The ERC20 Wrapper used to interface with deployed erc20 tokens.
|
|
* @param erc721Wrapper The ERC721 Wrapper used to interface with deployed erc20 tokens.
|
|
* @param erc1155ProxyWrapper The ERC1155 Proxy Wrapper used to interface with deployed erc20 tokens.
|
|
* @param provider Web3 provider to be used by a `Web3Wrapper` instance
|
|
*/
|
|
public constructor(
|
|
exchangeContract: ExchangeContract,
|
|
erc20Wrapper: ERC20Wrapper,
|
|
erc721Wrapper: ERC721Wrapper,
|
|
erc1155ProxyWrapper: ERC1155ProxyWrapper,
|
|
provider: Web3ProviderEngine | ZeroExProvider,
|
|
) {
|
|
this._exchange = exchangeContract;
|
|
this._blockchainBalanceStore = new BlockchainBalanceStore(erc20Wrapper, erc721Wrapper, erc1155ProxyWrapper);
|
|
this._web3Wrapper = new Web3Wrapper(provider);
|
|
}
|
|
|
|
/**
|
|
* Returns the balance store used by this wrapper.
|
|
*/
|
|
public getBlockchainBalanceStore(): BlockchainBalanceStore {
|
|
return this._blockchainBalanceStore;
|
|
}
|
|
|
|
/**
|
|
* Fills an order and asserts the effects. This includes
|
|
* 1. The order info (via `getOrderInfo`)
|
|
* 2. The fill results returned by making an `eth_call` to `exchange.fillOrder`
|
|
* 3. The events emitted by the exchange when the order is filled.
|
|
* 4. The balance changes as a result of filling the order.
|
|
*/
|
|
public async fillOrderAndAssertEffectsAsync(
|
|
signedOrder: SignedOrder,
|
|
from: string,
|
|
opts: { takerAssetFillAmount?: BigNumber } = {},
|
|
): Promise<void> {
|
|
// Get init state
|
|
await this._blockchainBalanceStore.updateBalancesAsync();
|
|
const initTakerAssetFilledAmount = await this._exchange.filled.callAsync(
|
|
orderHashUtils.getOrderHashHex(signedOrder),
|
|
);
|
|
// Assert init state of exchange
|
|
await this._assertOrderStateAsync(signedOrder, initTakerAssetFilledAmount);
|
|
// Simulate and execute fill then assert outputs
|
|
const [
|
|
simulatedFillResults,
|
|
simulatedFillEvent,
|
|
simulatedFinalBalanceStore,
|
|
] = FillOrderWrapper.simulateFillOrder(signedOrder, from, opts, this._blockchainBalanceStore);
|
|
const [fillResults, fillEvent] = await this._fillOrderAsync(signedOrder, from, opts);
|
|
// Assert state transition
|
|
expect(simulatedFillResults, 'Fill Results').to.be.deep.equal(fillResults);
|
|
expect(simulatedFillEvent, 'Fill Events').to.be.deep.equal(fillEvent);
|
|
const areBalancesEqual = BalanceStore.isEqual(simulatedFinalBalanceStore, this._blockchainBalanceStore);
|
|
expect(areBalancesEqual, 'Balances After Fill').to.be.true();
|
|
// Assert end state of exchange
|
|
const finalTakerAssetFilledAmount = initTakerAssetFilledAmount.plus(fillResults.takerAssetFilledAmount);
|
|
await this._assertOrderStateAsync(signedOrder, finalTakerAssetFilledAmount);
|
|
}
|
|
|
|
/**
|
|
* Fills an order on-chain. As an optimization this function auto-updates the blockchain balance store
|
|
* used by this contract.
|
|
*/
|
|
protected async _fillOrderAsync(
|
|
signedOrder: SignedOrder,
|
|
from: string,
|
|
opts: { takerAssetFillAmount?: BigNumber } = {},
|
|
): Promise<[FillResults, FillEventArgs]> {
|
|
const params = orderUtils.createFill(signedOrder, opts.takerAssetFillAmount);
|
|
const fillResults = await this._exchange.fillOrder.callAsync(
|
|
params.order,
|
|
params.takerAssetFillAmount,
|
|
params.signature,
|
|
{ from },
|
|
);
|
|
// @TODO: Replace with `awaitTransactionAsync` once `development` is merged into `3.0` branch
|
|
const txHash = await this._exchange.fillOrder.sendTransactionAsync(
|
|
params.order,
|
|
params.takerAssetFillAmount,
|
|
params.signature,
|
|
{ from },
|
|
);
|
|
const logDecoder = new LogDecoder(this._web3Wrapper, {
|
|
...artifacts,
|
|
...proxyArtifacts,
|
|
...erc20Artifacts,
|
|
...erc721Artifacts,
|
|
});
|
|
const txReceipt = await logDecoder.getTxWithDecodedLogsAsync(txHash);
|
|
const fillEvent = FillOrderWrapper._extractFillEventsfromReceipt(txReceipt)[0];
|
|
await this._blockchainBalanceStore.updateBalancesAsync();
|
|
return [fillResults, fillEvent];
|
|
}
|
|
|
|
/**
|
|
* Asserts that the provided order's fill amount and order status
|
|
* are the expected values.
|
|
* @param order The order to verify for a correct state.
|
|
* @param expectedFilledAmount The amount that the order should
|
|
* have been filled.
|
|
* @param side The side that the provided order should be matched on.
|
|
* @param exchangeWrapper The ExchangeWrapper instance.
|
|
*/
|
|
private async _assertOrderStateAsync(
|
|
order: SignedOrder,
|
|
expectedFilledAmount: BigNumber = new BigNumber(0),
|
|
): Promise<void> {
|
|
const orderInfo = await this._exchange.getOrderInfo.callAsync(order);
|
|
// Check filled amount of order.
|
|
const actualFilledAmount = orderInfo.orderTakerAssetFilledAmount;
|
|
expect(actualFilledAmount, 'order filled amount').to.be.bignumber.equal(expectedFilledAmount);
|
|
// Check status of order.
|
|
const expectedStatus = expectedFilledAmount.isGreaterThanOrEqualTo(order.takerAssetAmount)
|
|
? OrderStatus.FullyFilled
|
|
: OrderStatus.Fillable;
|
|
const actualStatus = orderInfo.orderStatus;
|
|
expect(actualStatus, 'order status').to.equal(expectedStatus);
|
|
}
|
|
}
|