Merge pull request #2275 from 0xProject/refactor/integrations/coordinator-tests-part2

Coordinator test refactor [2/2]
This commit is contained in:
mzhu25 2019-10-22 17:37:21 -07:00 committed by GitHub
commit 096950729e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 479 additions and 573 deletions

View File

@ -1,4 +1,4 @@
import { constants } from '@0x/contracts-test-utils'; import { constants, Numberish } from '@0x/contracts-test-utils';
import { assetDataUtils } from '@0x/order-utils'; import { assetDataUtils } from '@0x/order-utils';
import { AssetProxyId } from '@0x/types'; import { AssetProxyId } from '@0x/types';
import { BigNumber } from '@0x/utils'; import { BigNumber } from '@0x/utils';
@ -34,11 +34,24 @@ export class LocalBalanceStore extends BalanceStore {
/** /**
* Decreases the ETH balance of an address to simulate gas usage. * Decreases the ETH balance of an address to simulate gas usage.
* @param senderAddress Address whose ETH balance to decrease.
* @param amount Amount to decrease the balance by.
*/ */
public burnGas(senderAddress: string, amount: BigNumber | number): void { public burnGas(senderAddress: string, amount: Numberish): void {
this._balances.eth[senderAddress] = this._balances.eth[senderAddress].minus(amount); this._balances.eth[senderAddress] = this._balances.eth[senderAddress].minus(amount);
} }
/**
* Sends ETH from `fromAddress` to `toAddress`.
* @param fromAddress Sender of ETH.
* @param toAddress Receiver of ETH.
* @param amount Amount of ETH to transfer.
*/
public sendEth(fromAddress: string, toAddress: string, amount: Numberish): void {
this._balances.eth[fromAddress] = this._balances.eth[fromAddress].minus(amount);
this._balances.eth[toAddress] = this._balances.eth[toAddress].plus(amount);
}
/** /**
* Transfers assets from `fromAddress` to `toAddress`. * Transfers assets from `fromAddress` to `toAddress`.
* @param fromAddress Sender of asset(s) * @param fromAddress Sender of asset(s)

View File

@ -1,5 +1,5 @@
import { ERC1155MintableContract } from '@0x/contracts-erc1155'; import { ERC1155MintableContract } from '@0x/contracts-erc1155';
import { DummyERC20TokenContract, DummyNoReturnERC20TokenContract } from '@0x/contracts-erc20'; import { DummyERC20TokenContract, DummyNoReturnERC20TokenContract, WETH9Contract } from '@0x/contracts-erc20';
import { DummyERC721TokenContract } from '@0x/contracts-erc721'; import { DummyERC721TokenContract } from '@0x/contracts-erc721';
import { BigNumber } from '@0x/utils'; import { BigNumber } from '@0x/utils';
@ -15,7 +15,7 @@ interface TokenData<TERC20, TERC721, TERC1155> {
export type TokenAddresses = TokenData<address[], address[], address[]>; export type TokenAddresses = TokenData<address[], address[], address[]>;
export type TokenContracts = TokenData< export type TokenContracts = TokenData<
Array<DummyERC20TokenContract | DummyNoReturnERC20TokenContract>, Array<DummyERC20TokenContract | DummyNoReturnERC20TokenContract | WETH9Contract>,
DummyERC721TokenContract[], DummyERC721TokenContract[],
ERC1155MintableContract[] ERC1155MintableContract[]
>; >;
@ -29,7 +29,7 @@ export type TokenOwnersByName = Named<address>;
export type TokenAddressesByName = TokenData<Named<address>, Named<address>, Named<address>>; export type TokenAddressesByName = TokenData<Named<address>, Named<address>, Named<address>>;
export type TokenContractsByName = TokenData< export type TokenContractsByName = TokenData<
Named<DummyERC20TokenContract | DummyNoReturnERC20TokenContract>, Named<DummyERC20TokenContract | DummyNoReturnERC20TokenContract | WETH9Contract>,
Named<DummyERC721TokenContract>, Named<DummyERC721TokenContract>,
Named<ERC1155MintableContract> Named<ERC1155MintableContract>
>; >;

View File

@ -1,5 +1,6 @@
import { DummyERC20TokenContract, WETH9Contract } from '@0x/contracts-erc20'; import { DummyERC20TokenContract, WETH9Contract } from '@0x/contracts-erc20';
import { constants } from '@0x/contracts-test-utils'; import { constants, TransactionFactory } from '@0x/contracts-test-utils';
import { SignatureType, SignedZeroExTransaction, ZeroExTransaction } from '@0x/types';
import { BigNumber } from '@0x/utils'; import { BigNumber } from '@0x/utils';
import { DeploymentManager } from '../utils/deployment_manager'; import { DeploymentManager } from '../utils/deployment_manager';
@ -7,21 +8,30 @@ import { DeploymentManager } from '../utils/deployment_manager';
export type Constructor<T = {}> = new (...args: any[]) => T; export type Constructor<T = {}> = new (...args: any[]) => T;
export interface ActorConfig { export interface ActorConfig {
address: string;
name?: string; name?: string;
deployment: DeploymentManager; deployment: DeploymentManager;
[mixinProperty: string]: any; [mixinProperty: string]: any;
} }
export class Actor { export class Actor {
public static count: number = 0;
public readonly address: string; public readonly address: string;
public readonly name: string; public readonly name: string;
public readonly privateKey: Buffer;
public readonly deployment: DeploymentManager; public readonly deployment: DeploymentManager;
protected readonly transactionFactory: TransactionFactory;
constructor(config: ActorConfig) { constructor(config: ActorConfig) {
this.address = config.address; Actor.count++;
this.name = config.name || config.address; this.address = config.deployment.accounts[Actor.count];
this.name = config.name || this.address;
this.deployment = config.deployment; this.deployment = config.deployment;
this.privateKey = constants.TESTRPC_PRIVATE_KEYS[config.deployment.accounts.indexOf(this.address)];
this.transactionFactory = new TransactionFactory(
this.privateKey,
config.deployment.exchange.address,
config.deployment.chainId,
);
} }
/** /**
@ -51,4 +61,14 @@ export class Actor {
{ from: this.address }, { from: this.address },
); );
} }
/**
* Signs a transaction.
*/
public async signTransactionAsync(
customTransactionParams: Partial<ZeroExTransaction>,
signatureType: SignatureType = SignatureType.EthSign,
): Promise<SignedZeroExTransaction> {
return this.transactionFactory.newSignedTransactionAsync(customTransactionParams, signatureType);
}
} }

View File

@ -0,0 +1,47 @@
import { BaseContract } from '@0x/base-contract';
import { ApprovalFactory, SignedCoordinatorApproval } from '@0x/contracts-coordinator';
import { SignatureType, SignedZeroExTransaction } from '@0x/types';
import { Actor, ActorConfig, Constructor } from './base';
export interface FeeRecipientConfig extends ActorConfig {
verifyingContract?: BaseContract;
}
export function FeeRecipientMixin<TBase extends Constructor>(Base: TBase) {
return class extends Base {
public readonly actor: Actor;
public readonly approvalFactory?: ApprovalFactory;
/**
* The mixin pattern requires that this constructor uses `...args: any[]`, but this class
* really expects a single `FeeRecipientConfig` parameter (assuming `Actor` is used as the
* base class).
*/
constructor(...args: any[]) {
super(...args);
this.actor = (this as any) as Actor;
const { verifyingContract } = args[0] as FeeRecipientConfig;
if (verifyingContract !== undefined) {
this.approvalFactory = new ApprovalFactory(this.actor.privateKey, verifyingContract.address);
}
}
/**
* Signs an coordinator transaction.
*/
public signCoordinatorApproval(
transaction: SignedZeroExTransaction,
txOrigin: string,
signatureType: SignatureType = SignatureType.EthSign,
): SignedCoordinatorApproval {
if (this.approvalFactory === undefined) {
throw new Error('No verifying contract provided in FeeRecipient constructor');
}
return this.approvalFactory.newSignedApproval(transaction, txOrigin, signatureType);
}
};
}
export class FeeRecipient extends FeeRecipientMixin(Actor) {}

View File

@ -1,3 +1,6 @@
export { Actor } from './base';
export { Maker } from './maker'; export { Maker } from './maker';
export { PoolOperator } from './pool_operator'; export { PoolOperator } from './pool_operator';
export { FeeRecipient } from './fee_recipient';
export * from './hybrids'; export * from './hybrids';
export * from './utils';

View File

@ -31,9 +31,7 @@ export function MakerMixin<TBase extends Constructor>(Base: TBase) {
chainId: this.actor.deployment.chainId, chainId: this.actor.deployment.chainId,
...orderConfig, ...orderConfig,
}; };
const privateKey = this.orderFactory = new OrderFactory(this.actor.privateKey, defaultOrderParams);
constants.TESTRPC_PRIVATE_KEYS[this.actor.deployment.accounts.indexOf(this.actor.address)];
this.orderFactory = new OrderFactory(privateKey, defaultOrderParams);
} }
/** /**

View File

@ -0,0 +1,8 @@
import { TokenOwnersByName } from '@0x/contracts-exchange';
import * as _ from 'lodash';
import { Actor } from './base';
export function actorAddressesByName(actors: Actor[]): TokenOwnersByName {
return _.zipObject(actors.map(actor => actor.name), actors.map(actor => actor.address));
}

View File

@ -1,419 +1,457 @@
import { ERC20ProxyContract, ERC20Wrapper } from '@0x/contracts-asset-proxy'; import { CoordinatorContract, SignedCoordinatorApproval } from '@0x/contracts-coordinator';
import { ApprovalFactory, artifacts, CoordinatorContract } from '@0x/contracts-coordinator';
import { artifacts as erc20Artifacts, DummyERC20TokenContract, WETH9Contract } from '@0x/contracts-erc20';
import { import {
artifacts as exchangeArtifacts, BlockchainBalanceStore,
LocalBalanceStore,
constants as exchangeConstants, constants as exchangeConstants,
ExchangeContract, ExchangeCancelEventArgs,
ExchangeCancelUpToEventArgs,
exchangeDataEncoder, exchangeDataEncoder,
ExchangeEvents,
ExchangeFillEventArgs,
ExchangeFunctionName, ExchangeFunctionName,
TestProtocolFeeCollectorContract,
} from '@0x/contracts-exchange'; } from '@0x/contracts-exchange';
import { import { blockchainTests, expect, hexConcat, hexSlice, verifyEvents } from '@0x/contracts-test-utils';
blockchainTests, import { assetDataUtils, CoordinatorRevertErrors, orderHashUtils, transactionHashUtils } from '@0x/order-utils';
constants, import { SignedOrder, SignedZeroExTransaction } from '@0x/types';
hexConcat,
hexSlice,
OrderFactory,
TransactionFactory,
} from '@0x/contracts-test-utils';
import { assetDataUtils, CoordinatorRevertErrors, transactionHashUtils } from '@0x/order-utils';
import { BigNumber } from '@0x/utils'; import { BigNumber } from '@0x/utils';
import { TransactionReceiptWithDecodedLogs } from 'ethereum-types';
import { CoordinatorTestFactory } from './coordinator_test_factory'; import { Actor, actorAddressesByName, FeeRecipient, Maker } from '../actors';
import { deployCoordinatorAsync } from './deploy_coordinator';
import { DeploymentManager } from '../utils/deployment_manager';
// tslint:disable:no-unnecessary-type-assertion // tslint:disable:no-unnecessary-type-assertion
blockchainTests.resets('Coordinator tests', env => { blockchainTests.resets('Coordinator tests', env => {
let chainId: number; let deployment: DeploymentManager;
let makerAddress: string; let coordinator: CoordinatorContract;
let owner: string; let balanceStore: BlockchainBalanceStore;
let takerAddress: string;
let feeRecipientAddress: string;
let erc20Proxy: ERC20ProxyContract; let maker: Maker;
let erc20TokenA: DummyERC20TokenContract; let taker: Actor;
let erc20TokenB: DummyERC20TokenContract; let feeRecipient: FeeRecipient;
let makerFeeToken: DummyERC20TokenContract;
let takerFeeToken: DummyERC20TokenContract;
let coordinatorContract: CoordinatorContract;
let exchange: ExchangeContract;
let protocolFeeCollector: TestProtocolFeeCollectorContract;
let wethContract: WETH9Contract;
let erc20Wrapper: ERC20Wrapper;
let orderFactory: OrderFactory;
let takerTransactionFactory: TransactionFactory;
let makerTransactionFactory: TransactionFactory;
let approvalFactory: ApprovalFactory;
let testFactory: CoordinatorTestFactory;
const GAS_PRICE = new BigNumber(env.txDefaults.gasPrice || constants.DEFAULT_GAS_PRICE);
const PROTOCOL_FEE_MULTIPLIER = new BigNumber(150000);
const PROTOCOL_FEE = GAS_PRICE.times(PROTOCOL_FEE_MULTIPLIER);
before(async () => { before(async () => {
chainId = await env.getChainIdAsync(); deployment = await DeploymentManager.deployAsync(env, {
const accounts = await env.getAccountAddressesAsync(); numErc20TokensToDeploy: 4,
const usedAddresses = ([owner, makerAddress, takerAddress, feeRecipientAddress] = accounts); numErc721TokensToDeploy: 0,
numErc1155TokensToDeploy: 0,
});
coordinator = await deployCoordinatorAsync(deployment, env);
// Deploy Exchange const [makerToken, takerToken, makerFeeToken, takerFeeToken] = deployment.tokens.erc20;
exchange = await ExchangeContract.deployFrom0xArtifactAsync(
exchangeArtifacts.Exchange, taker = new Actor({ name: 'Taker', deployment });
env.provider, feeRecipient = new FeeRecipient({
env.txDefaults, name: 'Fee recipient',
deployment,
verifyingContract: coordinator,
});
maker = new Maker({
name: 'Maker',
deployment,
orderConfig: {
senderAddress: coordinator.address,
feeRecipientAddress: feeRecipient.address,
makerAssetData: assetDataUtils.encodeERC20AssetData(makerToken.address),
takerAssetData: assetDataUtils.encodeERC20AssetData(takerToken.address),
makerFeeAssetData: assetDataUtils.encodeERC20AssetData(makerFeeToken.address),
takerFeeAssetData: assetDataUtils.encodeERC20AssetData(takerFeeToken.address),
},
});
taker.configureERC20TokenAsync(takerToken);
taker.configureERC20TokenAsync(takerFeeToken);
taker.configureERC20TokenAsync(deployment.tokens.weth, deployment.staking.stakingProxy.address);
maker.configureERC20TokenAsync(makerToken);
maker.configureERC20TokenAsync(makerFeeToken);
balanceStore = new BlockchainBalanceStore(
{
...actorAddressesByName([maker, taker, feeRecipient]),
Coordinator: coordinator.address,
StakingProxy: deployment.staking.stakingProxy.address,
},
{ erc20: { makerToken, takerToken, makerFeeToken, takerFeeToken, wETH: deployment.tokens.weth } },
{}, {},
new BigNumber(chainId),
);
// Set up ERC20
erc20Wrapper = new ERC20Wrapper(env.provider, usedAddresses, owner);
erc20Proxy = await erc20Wrapper.deployProxyAsync();
const numDummyErc20ToDeploy = 4;
[erc20TokenA, erc20TokenB, makerFeeToken, takerFeeToken] = await erc20Wrapper.deployDummyTokensAsync(
numDummyErc20ToDeploy,
constants.DUMMY_TOKEN_DECIMALS,
);
await erc20Proxy.addAuthorizedAddress.awaitTransactionSuccessAsync(exchange.address, { from: owner });
await exchange.registerAssetProxy.awaitTransactionSuccessAsync(erc20Proxy.address, { from: owner });
// Set up WETH
wethContract = await WETH9Contract.deployFrom0xArtifactAsync(
erc20Artifacts.WETH9,
env.provider,
env.txDefaults,
{},
);
const weth = new DummyERC20TokenContract(wethContract.address, env.provider);
erc20Wrapper.addDummyTokenContract(weth);
await erc20Wrapper.setBalancesAndAllowancesAsync();
// Set up Protocol Fee Collector
protocolFeeCollector = await TestProtocolFeeCollectorContract.deployFrom0xArtifactAsync(
exchangeArtifacts.TestProtocolFeeCollector,
env.provider,
env.txDefaults,
{},
weth.address,
);
await exchange.setProtocolFeeMultiplier.awaitTransactionSuccessAsync(PROTOCOL_FEE_MULTIPLIER);
await exchange.setProtocolFeeCollectorAddress.awaitTransactionSuccessAsync(protocolFeeCollector.address);
for (const account of usedAddresses) {
await wethContract.deposit.awaitTransactionSuccessAsync({
from: account,
value: constants.ONE_ETHER,
});
await wethContract.approve.awaitTransactionSuccessAsync(
protocolFeeCollector.address,
constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS,
{
from: account,
},
);
}
erc20Wrapper.addTokenOwnerAddress(protocolFeeCollector.address);
// Deploy Coordinator
coordinatorContract = await CoordinatorContract.deployFrom0xArtifactAsync(
artifacts.Coordinator,
env.provider,
env.txDefaults,
{ ...exchangeArtifacts, ...artifacts },
exchange.address,
new BigNumber(chainId),
);
erc20Wrapper.addTokenOwnerAddress(coordinatorContract.address);
// Configure order defaults
const defaultOrderParams = {
...constants.STATIC_ORDER_PARAMS,
senderAddress: coordinatorContract.address,
makerAddress,
feeRecipientAddress,
makerAssetData: assetDataUtils.encodeERC20AssetData(erc20TokenA.address),
takerAssetData: assetDataUtils.encodeERC20AssetData(erc20TokenB.address),
makerFeeAssetData: assetDataUtils.encodeERC20AssetData(makerFeeToken.address),
takerFeeAssetData: assetDataUtils.encodeERC20AssetData(takerFeeToken.address),
exchangeAddress: exchange.address,
chainId,
};
const makerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddress)];
const takerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(takerAddress)];
const feeRecipientPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(feeRecipientAddress)];
orderFactory = new OrderFactory(makerPrivateKey, defaultOrderParams);
makerTransactionFactory = new TransactionFactory(makerPrivateKey, exchange.address, chainId);
takerTransactionFactory = new TransactionFactory(takerPrivateKey, exchange.address, chainId);
approvalFactory = new ApprovalFactory(feeRecipientPrivateKey, coordinatorContract.address);
testFactory = new CoordinatorTestFactory(
coordinatorContract,
erc20Wrapper,
makerAddress,
takerAddress,
feeRecipientAddress,
protocolFeeCollector.address,
erc20TokenA.address,
erc20TokenB.address,
makerFeeToken.address,
takerFeeToken.address,
weth.address,
GAS_PRICE,
PROTOCOL_FEE_MULTIPLIER,
); );
}); });
describe('single order fills', () => { function simulateFills(
for (const fnName of exchangeConstants.SINGLE_FILL_FN_NAMES) { orders: SignedOrder[],
it(`${fnName} should fill the order with a signed approval`, async () => { txReceipt: TransactionReceiptWithDecodedLogs,
const order = await orderFactory.newSignedOrderAsync(); msgValue: BigNumber = new BigNumber(0),
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, [order]); ): LocalBalanceStore {
const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data }); const localBalanceStore = LocalBalanceStore.create(balanceStore);
const approval = approvalFactory.newSignedApproval(transaction, takerAddress); // Transaction gas cost
const txData = { from: takerAddress, value: PROTOCOL_FEE }; localBalanceStore.burnGas(txReceipt.from, DeploymentManager.gasPrice.times(txReceipt.gasUsed));
await testFactory.executeFillTransactionTestAsync(
[order], for (const order of orders) {
transaction, // Taker -> Maker
takerAddress, localBalanceStore.transferAsset(taker.address, maker.address, order.takerAssetAmount, order.takerAssetData);
[approval.signature], // Maker -> Taker
txData, localBalanceStore.transferAsset(maker.address, taker.address, order.makerAssetAmount, order.makerAssetData);
// Taker -> Fee Recipient
localBalanceStore.transferAsset(
taker.address,
feeRecipient.address,
order.takerFee,
order.takerFeeAssetData,
);
// Maker -> Fee Recipient
localBalanceStore.transferAsset(
maker.address,
feeRecipient.address,
order.makerFee,
order.makerFeeAssetData,
);
// Protocol fee
if (msgValue.isGreaterThanOrEqualTo(DeploymentManager.protocolFee)) {
localBalanceStore.sendEth(
txReceipt.from,
deployment.staking.stakingProxy.address,
DeploymentManager.protocolFee,
); );
msgValue = msgValue.minus(DeploymentManager.protocolFee);
} else {
localBalanceStore.transferAsset(
taker.address,
deployment.staking.stakingProxy.address,
DeploymentManager.protocolFee,
assetDataUtils.encodeERC20AssetData(deployment.tokens.weth.address),
);
}
}
return localBalanceStore;
}
function expectedFillEvent(order: SignedOrder): ExchangeFillEventArgs {
return {
makerAddress: order.makerAddress,
takerAddress: taker.address,
senderAddress: order.senderAddress,
feeRecipientAddress: order.feeRecipientAddress,
makerAssetData: order.makerAssetData,
takerAssetData: order.takerAssetData,
makerFeeAssetData: order.makerFeeAssetData,
takerFeeAssetData: order.takerFeeAssetData,
makerAssetFilledAmount: order.makerAssetAmount,
takerAssetFilledAmount: order.takerAssetAmount,
makerFeePaid: order.makerFee,
takerFeePaid: order.takerFee,
protocolFeePaid: DeploymentManager.protocolFee,
orderHash: orderHashUtils.getOrderHashHex(order),
};
}
describe('single order fills', () => {
let order: SignedOrder;
let data: string;
let transaction: SignedZeroExTransaction;
let approval: SignedCoordinatorApproval;
for (const fnName of exchangeConstants.SINGLE_FILL_FN_NAMES) {
before(async () => {
order = await maker.signOrderAsync();
data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, [order]);
transaction = await taker.signTransactionAsync({
data,
gasPrice: DeploymentManager.gasPrice,
});
approval = feeRecipient.signCoordinatorApproval(transaction, taker.address);
});
it(`${fnName} should fill the order with a signed approval`, async () => {
await balanceStore.updateBalancesAsync();
const txReceipt = await coordinator.executeTransaction.awaitTransactionSuccessAsync(
transaction,
taker.address,
transaction.signature,
[approval.signature],
{ from: taker.address, value: DeploymentManager.protocolFee },
);
const expectedBalances = simulateFills([order], txReceipt, DeploymentManager.protocolFee);
await balanceStore.updateBalancesAsync();
balanceStore.assertEquals(expectedBalances);
verifyEvents(txReceipt, [expectedFillEvent(order)], ExchangeEvents.Fill);
}); });
it(`${fnName} should fill the order if called by approver (eth protocol fee, no refund)`, async () => { it(`${fnName} should fill the order if called by approver (eth protocol fee, no refund)`, async () => {
const order = await orderFactory.newSignedOrderAsync(); await balanceStore.updateBalancesAsync();
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, [order]); const txReceipt = await coordinator.executeTransaction.awaitTransactionSuccessAsync(
const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data });
const txData = { from: feeRecipientAddress, value: PROTOCOL_FEE };
await testFactory.executeFillTransactionTestAsync(
[order],
transaction, transaction,
feeRecipientAddress, feeRecipient.address,
transaction.signature,
[], [],
txData, { from: feeRecipient.address, value: DeploymentManager.protocolFee },
); );
const expectedBalances = simulateFills([order], txReceipt, DeploymentManager.protocolFee);
await balanceStore.updateBalancesAsync();
balanceStore.assertEquals(expectedBalances);
verifyEvents(txReceipt, [expectedFillEvent(order)], ExchangeEvents.Fill);
}); });
it(`${fnName} should fill the order if called by approver (eth protocol fee, refund)`, async () => { it(`${fnName} should fill the order if called by approver (eth protocol fee, refund)`, async () => {
const order = await orderFactory.newSignedOrderAsync(); await balanceStore.updateBalancesAsync();
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, [order]); const txReceipt = await coordinator.executeTransaction.awaitTransactionSuccessAsync(
const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data });
const txData = { from: feeRecipientAddress, value: PROTOCOL_FEE.plus(1) };
await testFactory.executeFillTransactionTestAsync(
[order],
transaction, transaction,
feeRecipientAddress, feeRecipient.address,
transaction.signature,
[], [],
txData, { from: feeRecipient.address, value: DeploymentManager.protocolFee.plus(1) },
); );
const expectedBalances = simulateFills([order], txReceipt, DeploymentManager.protocolFee.plus(1));
await balanceStore.updateBalancesAsync();
balanceStore.assertEquals(expectedBalances);
verifyEvents(txReceipt, [expectedFillEvent(order)], ExchangeEvents.Fill);
}); });
it(`${fnName} should fill the order if called by approver (weth protocol fee, no refund)`, async () => { it(`${fnName} should fill the order if called by approver (weth protocol fee, no refund)`, async () => {
const order = await orderFactory.newSignedOrderAsync(); await balanceStore.updateBalancesAsync();
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, [order]); const txReceipt = await coordinator.executeTransaction.awaitTransactionSuccessAsync(
const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data });
const txData = { from: feeRecipientAddress };
await testFactory.executeFillTransactionTestAsync(
[order],
transaction, transaction,
feeRecipientAddress, feeRecipient.address,
transaction.signature,
[], [],
txData, { from: feeRecipient.address },
); );
const expectedBalances = simulateFills([order], txReceipt);
await balanceStore.updateBalancesAsync();
balanceStore.assertEquals(expectedBalances);
verifyEvents(txReceipt, [expectedFillEvent(order)], ExchangeEvents.Fill);
}); });
it(`${fnName} should fill the order if called by approver (weth protocol fee, refund)`, async () => { it(`${fnName} should fill the order if called by approver (weth protocol fee, refund)`, async () => {
const order = await orderFactory.newSignedOrderAsync(); await balanceStore.updateBalancesAsync();
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, [order]); const txReceipt = await coordinator.executeTransaction.awaitTransactionSuccessAsync(
const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data });
const txData = { from: feeRecipientAddress, value: new BigNumber(1) };
await testFactory.executeFillTransactionTestAsync(
[order],
transaction, transaction,
feeRecipientAddress, feeRecipient.address,
transaction.signature,
[], [],
txData, { from: feeRecipient.address, value: new BigNumber(1) },
);
});
it(`${fnName} should fill the order if called by approver`, async () => {
const order = await orderFactory.newSignedOrderAsync();
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, [order]);
const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data });
const txData = { from: feeRecipientAddress, value: PROTOCOL_FEE };
await testFactory.executeFillTransactionTestAsync(
[order],
transaction,
feeRecipientAddress,
[],
txData,
); );
const expectedBalances = simulateFills([order], txReceipt, new BigNumber(1));
await balanceStore.updateBalancesAsync();
balanceStore.assertEquals(expectedBalances);
verifyEvents(txReceipt, [expectedFillEvent(order)], ExchangeEvents.Fill);
}); });
it(`${fnName} should revert with no approval signature`, async () => { it(`${fnName} should revert with no approval signature`, async () => {
const orders = [await orderFactory.newSignedOrderAsync()];
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data });
const transactionHash = transactionHashUtils.getTransactionHashHex(transaction); const transactionHash = transactionHashUtils.getTransactionHashHex(transaction);
await testFactory.executeFillTransactionTestAsync( const tx = coordinator.executeTransaction.awaitTransactionSuccessAsync(
orders,
transaction, transaction,
takerAddress, taker.address,
transaction.signature,
[], [],
{ { from: taker.address, value: DeploymentManager.protocolFee },
from: takerAddress,
gas: constants.MAX_EXECUTE_TRANSACTION_GAS,
value: PROTOCOL_FEE,
},
new CoordinatorRevertErrors.InvalidApprovalSignatureError(transactionHash, feeRecipientAddress),
); );
const expectedError = new CoordinatorRevertErrors.InvalidApprovalSignatureError(
transactionHash,
feeRecipient.address,
);
return expect(tx).to.revertWith(expectedError);
}); });
it(`${fnName} should revert with an invalid approval signature`, async () => { it(`${fnName} should revert with an invalid approval signature`, async () => {
const orders = [await orderFactory.newSignedOrderAsync()]; const approvalSignature = hexConcat(
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data });
const approval = approvalFactory.newSignedApproval(transaction, takerAddress);
const signature = hexConcat(
hexSlice(approval.signature, 0, 2), hexSlice(approval.signature, 0, 2),
'0xFFFFFFFF', '0xFFFFFFFF',
hexSlice(approval.signature, 6), hexSlice(approval.signature, 6),
); );
const transactionHash = transactionHashUtils.getTransactionHashHex(transaction); const transactionHash = transactionHashUtils.getTransactionHashHex(transaction);
await testFactory.executeFillTransactionTestAsync( const tx = coordinator.executeTransaction.awaitTransactionSuccessAsync(
orders,
transaction, transaction,
takerAddress, taker.address,
[signature], transaction.signature,
{ from: takerAddress, value: PROTOCOL_FEE }, [approvalSignature],
new CoordinatorRevertErrors.InvalidApprovalSignatureError(transactionHash, feeRecipientAddress), { from: taker.address, value: DeploymentManager.protocolFee },
); );
const expectedError = new CoordinatorRevertErrors.InvalidApprovalSignatureError(
transactionHash,
feeRecipient.address,
);
return expect(tx).to.revertWith(expectedError);
}); });
it(`${fnName} should revert if not called by tx signer or approver`, async () => { it(`${fnName} should revert if not called by tx signer or approver`, async () => {
const orders = [await orderFactory.newSignedOrderAsync()]; const tx = coordinator.executeTransaction.awaitTransactionSuccessAsync(
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data });
const approval = approvalFactory.newSignedApproval(transaction, takerAddress);
await testFactory.executeFillTransactionTestAsync(
orders,
transaction, transaction,
takerAddress, taker.address,
transaction.signature,
[approval.signature], [approval.signature],
{ from: owner, value: PROTOCOL_FEE }, { from: maker.address, value: DeploymentManager.protocolFee },
new CoordinatorRevertErrors.InvalidOriginError(takerAddress),
); );
const expectedError = new CoordinatorRevertErrors.InvalidOriginError(taker.address);
return expect(tx).to.revertWith(expectedError);
}); });
} }
}); });
describe('batch order fills', () => { describe('batch order fills', () => {
let orders: SignedOrder[];
let data: string;
let transaction: SignedZeroExTransaction;
let approval: SignedCoordinatorApproval;
for (const fnName of [...exchangeConstants.MARKET_FILL_FN_NAMES, ...exchangeConstants.BATCH_FILL_FN_NAMES]) { for (const fnName of [...exchangeConstants.MARKET_FILL_FN_NAMES, ...exchangeConstants.BATCH_FILL_FN_NAMES]) {
before(async () => {
orders = [await maker.signOrderAsync(), await maker.signOrderAsync()];
data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
transaction = await taker.signTransactionAsync({
data,
gasPrice: DeploymentManager.gasPrice,
});
approval = feeRecipient.signCoordinatorApproval(transaction, taker.address);
});
it(`${fnName} should fill the orders with a signed approval`, async () => { it(`${fnName} should fill the orders with a signed approval`, async () => {
const orders = [await orderFactory.newSignedOrderAsync(), await orderFactory.newSignedOrderAsync()]; await balanceStore.updateBalancesAsync();
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders); const value = DeploymentManager.protocolFee.times(orders.length);
const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data }); const txReceipt = await coordinator.executeTransaction.awaitTransactionSuccessAsync(
const approval = approvalFactory.newSignedApproval(transaction, takerAddress);
await testFactory.executeFillTransactionTestAsync(
orders,
transaction, transaction,
takerAddress, taker.address,
transaction.signature,
[approval.signature], [approval.signature],
{ { from: taker.address, value },
from: takerAddress,
gas: constants.MAX_EXECUTE_TRANSACTION_GAS,
value: PROTOCOL_FEE.times(orders.length),
},
); );
const expectedBalances = simulateFills(orders, txReceipt, value);
await balanceStore.updateBalancesAsync();
balanceStore.assertEquals(expectedBalances);
verifyEvents(txReceipt, orders.map(order => expectedFillEvent(order)), ExchangeEvents.Fill);
}); });
it(`${fnName} should fill the orders if called by approver (eth fee, no refund)`, async () => { it(`${fnName} should fill the orders if called by approver (eth fee, no refund)`, async () => {
const orders = [await orderFactory.newSignedOrderAsync(), await orderFactory.newSignedOrderAsync()]; await balanceStore.updateBalancesAsync();
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders); const value = DeploymentManager.protocolFee.times(orders.length);
const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data }); const txReceipt = await coordinator.executeTransaction.awaitTransactionSuccessAsync(
await testFactory.executeFillTransactionTestAsync(orders, transaction, feeRecipientAddress, [], { transaction,
from: feeRecipientAddress, feeRecipient.address,
gas: constants.MAX_EXECUTE_TRANSACTION_GAS, transaction.signature,
value: PROTOCOL_FEE.times(orders.length), [],
}); { from: feeRecipient.address, value },
);
const expectedBalances = simulateFills(orders, txReceipt, value);
await balanceStore.updateBalancesAsync();
balanceStore.assertEquals(expectedBalances);
verifyEvents(txReceipt, orders.map(order => expectedFillEvent(order)), ExchangeEvents.Fill);
}); });
it(`${fnName} should fill the orders if called by approver (mixed fees, refund)`, async () => { it(`${fnName} should fill the orders if called by approver (mixed fees, refund)`, async () => {
const orders = [await orderFactory.newSignedOrderAsync(), await orderFactory.newSignedOrderAsync()]; await balanceStore.updateBalancesAsync();
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders); const value = DeploymentManager.protocolFee.plus(1);
const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data }); const txReceipt = await coordinator.executeTransaction.awaitTransactionSuccessAsync(
await testFactory.executeFillTransactionTestAsync(orders, transaction, feeRecipientAddress, [], { transaction,
from: feeRecipientAddress, feeRecipient.address,
gas: constants.MAX_EXECUTE_TRANSACTION_GAS, transaction.signature,
value: PROTOCOL_FEE.times(orders.length).plus(1), [],
}); { from: feeRecipient.address, value },
);
const expectedBalances = simulateFills(orders, txReceipt, value);
await balanceStore.updateBalancesAsync();
balanceStore.assertEquals(expectedBalances);
verifyEvents(txReceipt, orders.map(order => expectedFillEvent(order)), ExchangeEvents.Fill);
}); });
it(`${fnName} should revert with an invalid approval signature`, async () => { it(`${fnName} should revert with an invalid approval signature`, async () => {
const orders = [await orderFactory.newSignedOrderAsync(), await orderFactory.newSignedOrderAsync()]; const approvalSignature = hexConcat(
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data });
const approval = approvalFactory.newSignedApproval(transaction, takerAddress);
const signature = hexConcat(
hexSlice(approval.signature, 0, 2), hexSlice(approval.signature, 0, 2),
'0xFFFFFFFF', '0xFFFFFFFF',
hexSlice(approval.signature, 6), hexSlice(approval.signature, 6),
); );
const transactionHash = transactionHashUtils.getTransactionHashHex(transaction); const transactionHash = transactionHashUtils.getTransactionHashHex(transaction);
await testFactory.executeFillTransactionTestAsync( const tx = coordinator.executeTransaction.awaitTransactionSuccessAsync(
orders,
transaction, transaction,
takerAddress, taker.address,
[signature], transaction.signature,
{ from: takerAddress, value: PROTOCOL_FEE.times(orders.length) }, [approvalSignature],
new CoordinatorRevertErrors.InvalidApprovalSignatureError(transactionHash, feeRecipientAddress), { from: taker.address, value: DeploymentManager.protocolFee.times(orders.length) },
); );
const expectedError = new CoordinatorRevertErrors.InvalidApprovalSignatureError(
transactionHash,
feeRecipient.address,
);
return expect(tx).to.revertWith(expectedError);
}); });
it(`${fnName} should revert if not called by tx signer or approver`, async () => { it(`${fnName} should revert if not called by tx signer or approver`, async () => {
const orders = [await orderFactory.newSignedOrderAsync(), await orderFactory.newSignedOrderAsync()]; const tx = coordinator.executeTransaction.awaitTransactionSuccessAsync(
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
const transaction = await takerTransactionFactory.newSignedTransactionAsync({ data });
const approval = approvalFactory.newSignedApproval(transaction, takerAddress);
await testFactory.executeFillTransactionTestAsync(
orders,
transaction, transaction,
takerAddress, taker.address,
transaction.signature,
[approval.signature], [approval.signature],
{ from: owner, value: PROTOCOL_FEE.times(orders.length) }, { from: maker.address, value: DeploymentManager.protocolFee.times(orders.length) },
new CoordinatorRevertErrors.InvalidOriginError(takerAddress),
); );
const expectedError = new CoordinatorRevertErrors.InvalidOriginError(taker.address);
return expect(tx).to.revertWith(expectedError);
}); });
} }
}); });
describe('cancels', () => { describe('cancels', () => {
function expectedCancelEvent(order: SignedOrder): ExchangeCancelEventArgs {
return {
makerAddress: order.makerAddress,
senderAddress: order.senderAddress,
feeRecipientAddress: order.feeRecipientAddress,
makerAssetData: order.makerAssetData,
takerAssetData: order.takerAssetData,
orderHash: orderHashUtils.getOrderHashHex(order),
};
}
it('cancelOrder call should be successful without an approval', async () => { it('cancelOrder call should be successful without an approval', async () => {
const orders = [await orderFactory.newSignedOrderAsync()]; const order = await maker.signOrderAsync();
const data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.CancelOrder, orders); const data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.CancelOrder, [order]);
const transaction = await makerTransactionFactory.newSignedTransactionAsync({ data }); const transaction = await maker.signTransactionAsync({
await testFactory.executeCancelTransactionTestAsync( data,
ExchangeFunctionName.CancelOrder, gasPrice: DeploymentManager.gasPrice,
orders, });
const txReceipt = await coordinator.executeTransaction.awaitTransactionSuccessAsync(
transaction, transaction,
makerAddress, maker.address,
transaction.signature,
[], [],
{ { from: maker.address },
from: makerAddress,
},
); );
verifyEvents(txReceipt, [expectedCancelEvent(order)], ExchangeEvents.Cancel);
}); });
it('batchCancelOrders call should be successful without an approval', async () => { it('batchCancelOrders call should be successful without an approval', async () => {
const orders = [await orderFactory.newSignedOrderAsync(), await orderFactory.newSignedOrderAsync()]; const orders = [await maker.signOrderAsync(), await maker.signOrderAsync()];
const data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.BatchCancelOrders, orders); const data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.BatchCancelOrders, orders);
const transaction = await makerTransactionFactory.newSignedTransactionAsync({ data }); const transaction = await maker.signTransactionAsync({
await testFactory.executeCancelTransactionTestAsync( data,
ExchangeFunctionName.BatchCancelOrders, gasPrice: DeploymentManager.gasPrice,
orders, });
const txReceipt = await coordinator.executeTransaction.awaitTransactionSuccessAsync(
transaction, transaction,
makerAddress, maker.address,
transaction.signature,
[], [],
{ { from: maker.address },
from: makerAddress,
},
); );
verifyEvents(txReceipt, orders.map(order => expectedCancelEvent(order)), ExchangeEvents.Cancel);
}); });
it('cancelOrdersUpTo call should be successful without an approval', async () => { it('cancelOrdersUpTo call should be successful without an approval', async () => {
const data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.CancelOrdersUpTo, []); const data = exchangeDataEncoder.encodeOrdersToExchangeData(ExchangeFunctionName.CancelOrdersUpTo, []);
const transaction = await makerTransactionFactory.newSignedTransactionAsync({ data }); const transaction = await maker.signTransactionAsync({
await testFactory.executeCancelTransactionTestAsync( data,
ExchangeFunctionName.CancelOrdersUpTo, gasPrice: DeploymentManager.gasPrice,
[], });
const txReceipt = await coordinator.executeTransaction.awaitTransactionSuccessAsync(
transaction, transaction,
makerAddress, maker.address,
transaction.signature,
[], [],
{ { from: maker.address },
from: makerAddress,
},
); );
const expectedEvent: ExchangeCancelUpToEventArgs = {
makerAddress: maker.address,
orderSenderAddress: coordinator.address,
orderEpoch: new BigNumber(1),
};
verifyEvents(txReceipt, [expectedEvent], ExchangeEvents.CancelUpTo);
}); });
}); });
}); });

View File

@ -1,247 +0,0 @@
import { ERC20Wrapper } from '@0x/contracts-asset-proxy';
import { CoordinatorContract } from '@0x/contracts-coordinator';
import {
ExchangeCancelEventArgs,
ExchangeCancelUpToEventArgs,
ExchangeEvents,
ExchangeFillEventArgs,
ExchangeFunctionName,
} from '@0x/contracts-exchange';
import { expect, Numberish, TokenBalances, verifyEvents, web3Wrapper } from '@0x/contracts-test-utils';
import { assetDataUtils, orderHashUtils } from '@0x/order-utils';
import { SignedOrder, SignedZeroExTransaction } from '@0x/types';
import { BigNumber, RevertError } from '@0x/utils';
import { TransactionReceiptWithDecodedLogs, TxData } from 'ethereum-types';
import * as _ from 'lodash';
export class CoordinatorTestFactory {
private readonly _addresses: string[];
private readonly _protocolFee: BigNumber;
private static _expectedCancelEvent(order: SignedOrder): ExchangeCancelEventArgs {
return {
makerAddress: order.makerAddress,
senderAddress: order.senderAddress,
feeRecipientAddress: order.feeRecipientAddress,
makerAssetData: order.makerAssetData,
takerAssetData: order.takerAssetData,
orderHash: orderHashUtils.getOrderHashHex(order),
};
}
constructor(
private readonly _coordinatorContract: CoordinatorContract,
private readonly _erc20Wrapper: ERC20Wrapper,
private readonly _makerAddress: string,
private readonly _takerAddress: string,
private readonly _feeRecipientAddress: string,
private readonly _protocolFeeCollectorAddress: string,
private readonly _makerAssetAddress: string,
private readonly _takerAssetAddress: string,
private readonly _makerFeeAssetAddress: string,
private readonly _takerFeeAssetAddress: string,
private readonly _wethAddress: string,
private readonly _gasPrice: BigNumber,
_protocolFeeMultiplier: BigNumber,
) {
this._addresses = [
_makerAddress,
_takerAddress,
_coordinatorContract.address,
_feeRecipientAddress,
_protocolFeeCollectorAddress,
];
this._protocolFee = _gasPrice.times(_protocolFeeMultiplier);
}
public async executeFillTransactionTestAsync(
orders: SignedOrder[],
transaction: SignedZeroExTransaction,
txOrigin: string,
approvalSignatures: string[],
txData: Partial<TxData>,
revertError?: RevertError,
): Promise<void> {
const initBalances = await this._getTokenBalancesAsync();
const tx = this._coordinatorContract.executeTransaction.awaitTransactionSuccessAsync(
transaction,
txOrigin,
transaction.signature,
approvalSignatures,
txData,
);
if (revertError !== undefined) {
return expect(tx).to.revertWith(revertError);
}
const transactionReceipt = await tx;
verifyEvents(transactionReceipt, orders.map(order => this._expectedFillEvent(order)), ExchangeEvents.Fill);
const expectedBalances = this._getExpectedBalances(initBalances, orders, transactionReceipt, txData.value);
await this._verifyBalancesAsync(expectedBalances);
}
public async executeCancelTransactionTestAsync(
fnName: ExchangeFunctionName,
orders: SignedOrder[],
transaction: SignedZeroExTransaction,
txOrigin: string,
approvalSignatures: string[],
txData: Partial<TxData>,
): Promise<void> {
const transactionReceipt = await this._coordinatorContract.executeTransaction.awaitTransactionSuccessAsync(
transaction,
txOrigin,
transaction.signature,
approvalSignatures,
txData,
);
if (fnName === ExchangeFunctionName.CancelOrdersUpTo) {
const expectedEvent: ExchangeCancelUpToEventArgs = {
makerAddress: this._makerAddress,
orderSenderAddress: this._coordinatorContract.address,
orderEpoch: new BigNumber(1),
};
verifyEvents(transactionReceipt, [expectedEvent], ExchangeEvents.CancelUpTo);
} else {
verifyEvents(
transactionReceipt,
orders.map(order => CoordinatorTestFactory._expectedCancelEvent(order)),
ExchangeEvents.Cancel,
);
}
}
private async _getTokenBalancesAsync(): Promise<TokenBalances> {
const erc20Balances = await this._erc20Wrapper.getBalancesAsync();
const ethBalances = _.zipObject(
this._addresses,
await Promise.all(this._addresses.map(address => web3Wrapper.getBalanceInWeiAsync(address))),
);
return {
erc20: erc20Balances,
erc721: {},
erc1155: {},
eth: ethBalances,
};
}
private _getExpectedBalances(
initBalances: TokenBalances,
orders: SignedOrder[],
txReceipt: TransactionReceiptWithDecodedLogs,
txValue?: Numberish,
): TokenBalances {
const { erc20: erc20Balances, eth: ethBalances } = initBalances;
let remainingValue = new BigNumber(txValue || 0);
ethBalances[txReceipt.from] = ethBalances[txReceipt.from].minus(this._gasPrice.times(txReceipt.gasUsed));
for (const order of orders) {
const [makerAssetAddress, takerAssetAddress, makerFeeAssetAddress, takerFeeAssetAddress] = [
order.makerAssetData,
order.takerAssetData,
order.makerFeeAssetData,
order.takerFeeAssetData,
].map(assetData => assetDataUtils.decodeERC20AssetData(assetData).tokenAddress);
erc20Balances[order.makerAddress][makerAssetAddress] = erc20Balances[order.makerAddress][
makerAssetAddress
].minus(order.makerAssetAmount);
erc20Balances[this._takerAddress][makerAssetAddress] = erc20Balances[this._takerAddress][
makerAssetAddress
].plus(order.makerAssetAmount);
erc20Balances[order.makerAddress][takerAssetAddress] = erc20Balances[order.makerAddress][
takerAssetAddress
].plus(order.takerAssetAmount);
erc20Balances[this._takerAddress][takerAssetAddress] = erc20Balances[this._takerAddress][
takerAssetAddress
].minus(order.takerAssetAmount);
erc20Balances[order.makerAddress][makerFeeAssetAddress] = erc20Balances[order.makerAddress][
makerFeeAssetAddress
].minus(order.makerFee);
erc20Balances[this._takerAddress][takerFeeAssetAddress] = erc20Balances[this._takerAddress][
takerFeeAssetAddress
].minus(order.takerFee);
erc20Balances[order.feeRecipientAddress][makerFeeAssetAddress] = erc20Balances[order.feeRecipientAddress][
makerFeeAssetAddress
].plus(order.makerFee);
erc20Balances[order.feeRecipientAddress][takerFeeAssetAddress] = erc20Balances[order.feeRecipientAddress][
takerFeeAssetAddress
].plus(order.takerFee);
if (remainingValue.isGreaterThanOrEqualTo(this._protocolFee)) {
ethBalances[txReceipt.from] = ethBalances[txReceipt.from].minus(this._protocolFee);
ethBalances[this._protocolFeeCollectorAddress] = ethBalances[this._protocolFeeCollectorAddress].plus(
this._protocolFee,
);
remainingValue = remainingValue.minus(this._protocolFee);
} else {
erc20Balances[this._takerAddress][this._wethAddress] = erc20Balances[this._takerAddress][
this._wethAddress
].minus(this._protocolFee);
erc20Balances[this._protocolFeeCollectorAddress][this._wethAddress] = erc20Balances[
this._protocolFeeCollectorAddress
][this._wethAddress].plus(this._protocolFee);
}
}
return {
erc20: erc20Balances,
erc721: {},
erc1155: {},
eth: ethBalances,
};
}
private async _verifyBalancesAsync(expectedBalances: TokenBalances): Promise<void> {
const { erc20: expectedErc20Balances, eth: expectedEthBalances } = expectedBalances;
const { erc20: actualErc20Balances, eth: actualEthBalances } = await this._getTokenBalancesAsync();
const ownersByName = {
maker: this._makerAddress,
taker: this._takerAddress,
feeRecipient: this._feeRecipientAddress,
coordinator: this._coordinatorContract.address,
protocolFeeCollector: this._protocolFeeCollectorAddress,
};
const tokensByName = {
makerAsset: this._makerAssetAddress,
takerAsset: this._takerAssetAddress,
makerFeeAsset: this._makerFeeAssetAddress,
takerFeeAsset: this._takerFeeAssetAddress,
weth: this._wethAddress,
};
_.forIn(ownersByName, (ownerAddress, ownerName) => {
expect(actualEthBalances[ownerAddress], `${ownerName} eth balance`).to.bignumber.equal(
expectedEthBalances[ownerAddress],
);
_.forIn(tokensByName, (tokenAddress, tokenName) => {
expect(
actualErc20Balances[ownerAddress][tokenAddress],
`${ownerName} ${tokenName} balance`,
).to.bignumber.equal(expectedErc20Balances[ownerAddress][tokenAddress]);
});
});
}
private _expectedFillEvent(order: SignedOrder): ExchangeFillEventArgs {
return {
makerAddress: order.makerAddress,
takerAddress: this._takerAddress,
senderAddress: order.senderAddress,
feeRecipientAddress: order.feeRecipientAddress,
makerAssetData: order.makerAssetData,
takerAssetData: order.takerAssetData,
makerFeeAssetData: order.makerFeeAssetData,
takerFeeAssetData: order.takerFeeAssetData,
makerAssetFilledAmount: order.makerAssetAmount,
takerAssetFilledAmount: order.takerAssetAmount,
makerFeePaid: order.makerFee,
takerFeePaid: order.takerFee,
protocolFeePaid: this._protocolFee,
orderHash: orderHashUtils.getOrderHashHex(order),
};
}
}

View File

@ -0,0 +1,20 @@
import { artifacts, CoordinatorContract } from '@0x/contracts-coordinator';
import { artifacts as exchangeArtifacts } from '@0x/contracts-exchange';
import { BlockchainTestsEnvironment } from '@0x/contracts-test-utils';
import { BigNumber } from '@0x/utils';
import { DeploymentManager } from '../utils/deployment_manager';
export async function deployCoordinatorAsync(
deployment: DeploymentManager,
environment: BlockchainTestsEnvironment,
): Promise<CoordinatorContract> {
return await CoordinatorContract.deployFrom0xArtifactAsync(
artifacts.Coordinator,
environment.provider,
deployment.txDefaults,
{ ...exchangeArtifacts, ...artifacts },
deployment.exchange.address,
new BigNumber(deployment.chainId),
);
}

View File

@ -122,7 +122,9 @@ export interface DeploymentOptions {
} }
export class DeploymentManager { export class DeploymentManager {
public static protocolFeeMultiplier = new BigNumber(150000); public static readonly protocolFeeMultiplier = new BigNumber(150000);
public static readonly gasPrice = new BigNumber(1e9); // 1 Gwei
public static readonly protocolFee = DeploymentManager.gasPrice.times(DeploymentManager.protocolFeeMultiplier);
/** /**
* Fully deploy the 0x exchange and staking contracts and configure the system with the * Fully deploy the 0x exchange and staking contracts and configure the system with the
@ -141,6 +143,7 @@ export class DeploymentManager {
const txDefaults = { const txDefaults = {
...environment.txDefaults, ...environment.txDefaults,
from: owner, from: owner,
gasPrice: DeploymentManager.gasPrice,
}; };
// Deploy the contracts using the same owner and environment. // Deploy the contracts using the same owner and environment.
@ -148,7 +151,7 @@ export class DeploymentManager {
const exchange = await ExchangeContract.deployFrom0xArtifactAsync( const exchange = await ExchangeContract.deployFrom0xArtifactAsync(
exchangeArtifacts.Exchange, exchangeArtifacts.Exchange,
environment.provider, environment.provider,
environment.txDefaults, txDefaults,
{ ...ERC20Artifacts, ...exchangeArtifacts }, { ...ERC20Artifacts, ...exchangeArtifacts },
new BigNumber(chainId), new BigNumber(chainId),
); );
@ -200,7 +203,7 @@ export class DeploymentManager {
staking.stakingProxy, staking.stakingProxy,
]); ]);
return new DeploymentManager(assetProxies, governor, exchange, staking, tokens, chainId, accounts); return new DeploymentManager(assetProxies, governor, exchange, staking, tokens, chainId, accounts, txDefaults);
} }
/** /**
@ -493,5 +496,6 @@ export class DeploymentManager {
public tokens: TokenContracts, public tokens: TokenContracts,
public chainId: number, public chainId: number,
public accounts: string[], public accounts: string[],
public txDefaults: Partial<TxData>,
) {} ) {}
} }

View File

@ -17,7 +17,7 @@
"sourceMap": true "sourceMap": true
}, },
// These are not working right now // These are not working right now
"exclude": ["./contracts/extensions/**/*", "./contracts/coordinator/**/*"], "exclude": ["./contracts/extensions/**/*"],
// The root of the project is just a list of references and does not contain // The root of the project is just a list of references and does not contain
// any top-level TypeScript code. // any top-level TypeScript code.
"include": [], "include": [],
@ -26,6 +26,7 @@
{ "path": "./contracts/erc20" }, { "path": "./contracts/erc20" },
{ "path": "./contracts/erc721" }, { "path": "./contracts/erc721" },
{ "path": "./contracts/exchange" }, { "path": "./contracts/exchange" },
{ "path": "./contracts/coordinator" },
{ "path": "./contracts/exchange-forwarder" }, { "path": "./contracts/exchange-forwarder" },
{ "path": "./contracts/exchange-libs" }, { "path": "./contracts/exchange-libs" },
// { "path": "./contracts/extensions" }, // { "path": "./contracts/extensions" },
@ -33,6 +34,7 @@
{ "path": "./contracts/test-utils" }, { "path": "./contracts/test-utils" },
{ "path": "./contracts/utils" }, { "path": "./contracts/utils" },
{ "path": "./contracts/dev-utils" }, { "path": "./contracts/dev-utils" },
{ "path": "./contracts/integrations" },
{ "path": "./packages/0x.js" }, { "path": "./packages/0x.js" },
{ "path": "./packages/abi-gen-wrappers" }, { "path": "./packages/abi-gen-wrappers" },
{ "path": "./packages/abi-gen" }, { "path": "./packages/abi-gen" },