Split protocol package into exchange, asset-proxy, and multisig
This commit is contained in:
222
contracts/exchange/test/utils/asset_wrapper.ts
Normal file
222
contracts/exchange/test/utils/asset_wrapper.ts
Normal file
@@ -0,0 +1,222 @@
|
||||
import { AbstractAssetWrapper, constants } from '@0x/contracts-test-utils';
|
||||
import { assetDataUtils } from '@0x/order-utils';
|
||||
import { AssetProxyId } from '@0x/types';
|
||||
import { BigNumber, errorUtils } from '@0x/utils';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { ERC20Wrapper } from './erc20_wrapper';
|
||||
import { ERC721Wrapper } from './erc721_wrapper';
|
||||
|
||||
interface ProxyIdToAssetWrappers {
|
||||
[proxyId: string]: AbstractAssetWrapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* This class abstracts away the differences between ERC20 and ERC721 tokens so that
|
||||
* the logic that uses it does not need to care what standard a token belongs to.
|
||||
*/
|
||||
export class AssetWrapper {
|
||||
private readonly _proxyIdToAssetWrappers: ProxyIdToAssetWrappers;
|
||||
constructor(assetWrappers: AbstractAssetWrapper[]) {
|
||||
this._proxyIdToAssetWrappers = {};
|
||||
_.each(assetWrappers, assetWrapper => {
|
||||
const proxyId = assetWrapper.getProxyId();
|
||||
this._proxyIdToAssetWrappers[proxyId] = assetWrapper;
|
||||
});
|
||||
}
|
||||
public async getBalanceAsync(userAddress: string, assetData: string): Promise<BigNumber> {
|
||||
const proxyId = assetDataUtils.decodeAssetProxyId(assetData);
|
||||
switch (proxyId) {
|
||||
case AssetProxyId.ERC20: {
|
||||
const erc20Wrapper = this._proxyIdToAssetWrappers[proxyId] as ERC20Wrapper;
|
||||
const balance = await erc20Wrapper.getBalanceAsync(userAddress, assetData);
|
||||
return balance;
|
||||
}
|
||||
case AssetProxyId.ERC721: {
|
||||
const assetWrapper = this._proxyIdToAssetWrappers[proxyId] as ERC721Wrapper;
|
||||
const assetProxyData = assetDataUtils.decodeERC721AssetData(assetData);
|
||||
const isOwner = await assetWrapper.isOwnerAsync(
|
||||
userAddress,
|
||||
assetProxyData.tokenAddress,
|
||||
assetProxyData.tokenId,
|
||||
);
|
||||
const balance = isOwner ? new BigNumber(1) : new BigNumber(0);
|
||||
return balance;
|
||||
}
|
||||
default:
|
||||
throw errorUtils.spawnSwitchErr('proxyId', proxyId);
|
||||
}
|
||||
}
|
||||
public async setBalanceAsync(userAddress: string, assetData: string, desiredBalance: BigNumber): Promise<void> {
|
||||
const proxyId = assetDataUtils.decodeAssetProxyId(assetData);
|
||||
switch (proxyId) {
|
||||
case AssetProxyId.ERC20: {
|
||||
const erc20Wrapper = this._proxyIdToAssetWrappers[proxyId] as ERC20Wrapper;
|
||||
await erc20Wrapper.setBalanceAsync(userAddress, assetData, desiredBalance);
|
||||
return;
|
||||
}
|
||||
case AssetProxyId.ERC721: {
|
||||
if (!desiredBalance.eq(0) && !desiredBalance.eq(1)) {
|
||||
throw new Error(`Balance for ERC721 token can only be set to 0 or 1. Got: ${desiredBalance}`);
|
||||
}
|
||||
const erc721Wrapper = this._proxyIdToAssetWrappers[proxyId] as ERC721Wrapper;
|
||||
const assetProxyData = assetDataUtils.decodeERC721AssetData(assetData);
|
||||
const doesTokenExist = erc721Wrapper.doesTokenExistAsync(
|
||||
assetProxyData.tokenAddress,
|
||||
assetProxyData.tokenId,
|
||||
);
|
||||
if (!doesTokenExist && desiredBalance.eq(1)) {
|
||||
await erc721Wrapper.mintAsync(assetProxyData.tokenAddress, assetProxyData.tokenId, userAddress);
|
||||
return;
|
||||
} else if (!doesTokenExist && desiredBalance.eq(0)) {
|
||||
return; // noop
|
||||
}
|
||||
const tokenOwner = await erc721Wrapper.ownerOfAsync(
|
||||
assetProxyData.tokenAddress,
|
||||
assetProxyData.tokenId,
|
||||
);
|
||||
if (userAddress !== tokenOwner && desiredBalance.eq(1)) {
|
||||
await erc721Wrapper.transferFromAsync(
|
||||
assetProxyData.tokenAddress,
|
||||
assetProxyData.tokenId,
|
||||
tokenOwner,
|
||||
userAddress,
|
||||
);
|
||||
} else if (tokenOwner === userAddress && desiredBalance.eq(0)) {
|
||||
// Transfer token to someone else
|
||||
const userAddresses = await (erc721Wrapper as any)._web3Wrapper.getAvailableAddressesAsync();
|
||||
const nonOwner = _.find(userAddresses, a => a !== userAddress);
|
||||
await erc721Wrapper.transferFromAsync(
|
||||
assetProxyData.tokenAddress,
|
||||
assetProxyData.tokenId,
|
||||
tokenOwner,
|
||||
nonOwner,
|
||||
);
|
||||
return;
|
||||
} else if (
|
||||
(userAddress !== tokenOwner && desiredBalance.eq(0)) ||
|
||||
(tokenOwner === userAddress && desiredBalance.eq(1))
|
||||
) {
|
||||
return; // noop
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw errorUtils.spawnSwitchErr('proxyId', proxyId);
|
||||
}
|
||||
}
|
||||
public async getProxyAllowanceAsync(userAddress: string, assetData: string): Promise<BigNumber> {
|
||||
const proxyId = assetDataUtils.decodeAssetProxyId(assetData);
|
||||
switch (proxyId) {
|
||||
case AssetProxyId.ERC20: {
|
||||
const erc20Wrapper = this._proxyIdToAssetWrappers[proxyId] as ERC20Wrapper;
|
||||
const allowance = await erc20Wrapper.getProxyAllowanceAsync(userAddress, assetData);
|
||||
return allowance;
|
||||
}
|
||||
case AssetProxyId.ERC721: {
|
||||
const assetWrapper = this._proxyIdToAssetWrappers[proxyId] as ERC721Wrapper;
|
||||
const erc721ProxyData = assetDataUtils.decodeERC721AssetData(assetData);
|
||||
const isProxyApprovedForAll = await assetWrapper.isProxyApprovedForAllAsync(
|
||||
userAddress,
|
||||
erc721ProxyData.tokenAddress,
|
||||
);
|
||||
if (isProxyApprovedForAll) {
|
||||
return constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS;
|
||||
}
|
||||
|
||||
const isProxyApproved = await assetWrapper.isProxyApprovedAsync(
|
||||
erc721ProxyData.tokenAddress,
|
||||
erc721ProxyData.tokenId,
|
||||
);
|
||||
const allowance = isProxyApproved ? new BigNumber(1) : new BigNumber(0);
|
||||
return allowance;
|
||||
}
|
||||
default:
|
||||
throw errorUtils.spawnSwitchErr('proxyId', proxyId);
|
||||
}
|
||||
}
|
||||
public async setProxyAllowanceAsync(
|
||||
userAddress: string,
|
||||
assetData: string,
|
||||
desiredAllowance: BigNumber,
|
||||
): Promise<void> {
|
||||
const proxyId = assetDataUtils.decodeAssetProxyId(assetData);
|
||||
switch (proxyId) {
|
||||
case AssetProxyId.ERC20: {
|
||||
const erc20Wrapper = this._proxyIdToAssetWrappers[proxyId] as ERC20Wrapper;
|
||||
await erc20Wrapper.setAllowanceAsync(userAddress, assetData, desiredAllowance);
|
||||
return;
|
||||
}
|
||||
case AssetProxyId.ERC721: {
|
||||
if (
|
||||
!desiredAllowance.eq(0) &&
|
||||
!desiredAllowance.eq(1) &&
|
||||
!desiredAllowance.eq(constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS)
|
||||
) {
|
||||
throw new Error(
|
||||
`Allowance for ERC721 token can only be set to 0, 1 or 2^256-1. Got: ${desiredAllowance}`,
|
||||
);
|
||||
}
|
||||
const erc721Wrapper = this._proxyIdToAssetWrappers[proxyId] as ERC721Wrapper;
|
||||
const assetProxyData = assetDataUtils.decodeERC721AssetData(assetData);
|
||||
|
||||
const doesTokenExist = await erc721Wrapper.doesTokenExistAsync(
|
||||
assetProxyData.tokenAddress,
|
||||
assetProxyData.tokenId,
|
||||
);
|
||||
if (!doesTokenExist) {
|
||||
throw new Error(
|
||||
`Cannot setProxyAllowance on non-existent token: ${assetProxyData.tokenAddress} ${
|
||||
assetProxyData.tokenId
|
||||
}`,
|
||||
);
|
||||
}
|
||||
const isProxyApprovedForAll = await erc721Wrapper.isProxyApprovedForAllAsync(
|
||||
userAddress,
|
||||
assetProxyData.tokenAddress,
|
||||
);
|
||||
if (!isProxyApprovedForAll && desiredAllowance.eq(constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS)) {
|
||||
const isApproved = true;
|
||||
await erc721Wrapper.approveProxyForAllAsync(
|
||||
assetProxyData.tokenAddress,
|
||||
assetProxyData.tokenId,
|
||||
isApproved,
|
||||
);
|
||||
} else if (isProxyApprovedForAll && desiredAllowance.eq(0)) {
|
||||
const isApproved = false;
|
||||
await erc721Wrapper.approveProxyForAllAsync(
|
||||
assetProxyData.tokenAddress,
|
||||
assetProxyData.tokenId,
|
||||
isApproved,
|
||||
);
|
||||
} else if (isProxyApprovedForAll && desiredAllowance.eq(constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS)) {
|
||||
return; // Noop
|
||||
}
|
||||
|
||||
const isProxyApproved = await erc721Wrapper.isProxyApprovedAsync(
|
||||
assetProxyData.tokenAddress,
|
||||
assetProxyData.tokenId,
|
||||
);
|
||||
if (!isProxyApproved && desiredAllowance.eq(1)) {
|
||||
await erc721Wrapper.approveProxyAsync(assetProxyData.tokenAddress, assetProxyData.tokenId);
|
||||
} else if (isProxyApproved && desiredAllowance.eq(0)) {
|
||||
// Remove approval
|
||||
await erc721Wrapper.approveAsync(
|
||||
constants.NULL_ADDRESS,
|
||||
assetProxyData.tokenAddress,
|
||||
assetProxyData.tokenId,
|
||||
);
|
||||
} else if (
|
||||
(!isProxyApproved && desiredAllowance.eq(0)) ||
|
||||
(isProxyApproved && desiredAllowance.eq(1))
|
||||
) {
|
||||
return; // noop
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw errorUtils.spawnSwitchErr('proxyId', proxyId);
|
||||
}
|
||||
}
|
||||
}
|
177
contracts/exchange/test/utils/erc20_wrapper.ts
Normal file
177
contracts/exchange/test/utils/erc20_wrapper.ts
Normal file
@@ -0,0 +1,177 @@
|
||||
import { artifacts as proxyArtifacts, ERC20ProxyContract } from '@0x/contracts-asset-proxy';
|
||||
import { constants, ERC20BalancesByOwner, txDefaults } from '@0x/contracts-test-utils';
|
||||
import { artifacts as tokensArtifacts, DummyERC20TokenContract } from '@0x/contracts-tokens';
|
||||
import { assetDataUtils } from '@0x/order-utils';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import { Web3Wrapper } from '@0x/web3-wrapper';
|
||||
import { Provider } from 'ethereum-types';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
export class ERC20Wrapper {
|
||||
private readonly _tokenOwnerAddresses: string[];
|
||||
private readonly _contractOwnerAddress: string;
|
||||
private readonly _web3Wrapper: Web3Wrapper;
|
||||
private readonly _provider: Provider;
|
||||
private readonly _dummyTokenContracts: DummyERC20TokenContract[];
|
||||
private _proxyContract?: ERC20ProxyContract;
|
||||
private _proxyIdIfExists?: string;
|
||||
/**
|
||||
* Instanitates an ERC20Wrapper
|
||||
* @param provider Web3 provider to use for all JSON RPC requests
|
||||
* @param tokenOwnerAddresses Addresses that we want to endow as owners for dummy ERC20 tokens
|
||||
* @param contractOwnerAddress Desired owner of the contract
|
||||
* Instance of ERC20Wrapper
|
||||
*/
|
||||
constructor(provider: Provider, tokenOwnerAddresses: string[], contractOwnerAddress: string) {
|
||||
this._dummyTokenContracts = [];
|
||||
this._web3Wrapper = new Web3Wrapper(provider);
|
||||
this._provider = provider;
|
||||
this._tokenOwnerAddresses = tokenOwnerAddresses;
|
||||
this._contractOwnerAddress = contractOwnerAddress;
|
||||
}
|
||||
public async deployDummyTokensAsync(
|
||||
numberToDeploy: number,
|
||||
decimals: BigNumber,
|
||||
): Promise<DummyERC20TokenContract[]> {
|
||||
for (let i = 0; i < numberToDeploy; i++) {
|
||||
this._dummyTokenContracts.push(
|
||||
await DummyERC20TokenContract.deployFrom0xArtifactAsync(
|
||||
tokensArtifacts.DummyERC20Token,
|
||||
this._provider,
|
||||
txDefaults,
|
||||
constants.DUMMY_TOKEN_NAME,
|
||||
constants.DUMMY_TOKEN_SYMBOL,
|
||||
decimals,
|
||||
constants.DUMMY_TOKEN_TOTAL_SUPPLY,
|
||||
),
|
||||
);
|
||||
}
|
||||
return this._dummyTokenContracts;
|
||||
}
|
||||
public async deployProxyAsync(): Promise<ERC20ProxyContract> {
|
||||
this._proxyContract = await ERC20ProxyContract.deployFrom0xArtifactAsync(
|
||||
proxyArtifacts.ERC20Proxy,
|
||||
this._provider,
|
||||
txDefaults,
|
||||
);
|
||||
this._proxyIdIfExists = await this._proxyContract.getProxyId.callAsync();
|
||||
return this._proxyContract;
|
||||
}
|
||||
public getProxyId(): string {
|
||||
this._validateProxyContractExistsOrThrow();
|
||||
return this._proxyIdIfExists as string;
|
||||
}
|
||||
public async setBalancesAndAllowancesAsync(): Promise<void> {
|
||||
this._validateDummyTokenContractsExistOrThrow();
|
||||
this._validateProxyContractExistsOrThrow();
|
||||
for (const dummyTokenContract of this._dummyTokenContracts) {
|
||||
for (const tokenOwnerAddress of this._tokenOwnerAddresses) {
|
||||
await this._web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await dummyTokenContract.setBalance.sendTransactionAsync(
|
||||
tokenOwnerAddress,
|
||||
constants.INITIAL_ERC20_BALANCE,
|
||||
{ from: this._contractOwnerAddress },
|
||||
),
|
||||
constants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
await this._web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await dummyTokenContract.approve.sendTransactionAsync(
|
||||
(this._proxyContract as ERC20ProxyContract).address,
|
||||
constants.INITIAL_ERC20_ALLOWANCE,
|
||||
{ from: tokenOwnerAddress },
|
||||
),
|
||||
constants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
public async getBalanceAsync(userAddress: string, assetData: string): Promise<BigNumber> {
|
||||
const tokenContract = this._getTokenContractFromAssetData(assetData);
|
||||
const balance = new BigNumber(await tokenContract.balanceOf.callAsync(userAddress));
|
||||
return balance;
|
||||
}
|
||||
public async setBalanceAsync(userAddress: string, assetData: string, amount: BigNumber): Promise<void> {
|
||||
const tokenContract = this._getTokenContractFromAssetData(assetData);
|
||||
await this._web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await tokenContract.setBalance.sendTransactionAsync(userAddress, amount, {
|
||||
from: this._contractOwnerAddress,
|
||||
}),
|
||||
constants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
}
|
||||
public async getProxyAllowanceAsync(userAddress: string, assetData: string): Promise<BigNumber> {
|
||||
const tokenContract = this._getTokenContractFromAssetData(assetData);
|
||||
const proxyAddress = (this._proxyContract as ERC20ProxyContract).address;
|
||||
const allowance = new BigNumber(await tokenContract.allowance.callAsync(userAddress, proxyAddress));
|
||||
return allowance;
|
||||
}
|
||||
public async setAllowanceAsync(userAddress: string, assetData: string, amount: BigNumber): Promise<void> {
|
||||
const tokenContract = this._getTokenContractFromAssetData(assetData);
|
||||
const proxyAddress = (this._proxyContract as ERC20ProxyContract).address;
|
||||
await this._web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await tokenContract.approve.sendTransactionAsync(proxyAddress, amount, {
|
||||
from: userAddress,
|
||||
}),
|
||||
constants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
}
|
||||
public async getBalancesAsync(): Promise<ERC20BalancesByOwner> {
|
||||
this._validateDummyTokenContractsExistOrThrow();
|
||||
const balancesByOwner: ERC20BalancesByOwner = {};
|
||||
const balances: BigNumber[] = [];
|
||||
const balanceInfo: Array<{ tokenOwnerAddress: string; tokenAddress: string }> = [];
|
||||
for (const dummyTokenContract of this._dummyTokenContracts) {
|
||||
for (const tokenOwnerAddress of this._tokenOwnerAddresses) {
|
||||
balances.push(await dummyTokenContract.balanceOf.callAsync(tokenOwnerAddress));
|
||||
balanceInfo.push({
|
||||
tokenOwnerAddress,
|
||||
tokenAddress: dummyTokenContract.address,
|
||||
});
|
||||
}
|
||||
}
|
||||
_.forEach(balances, (balance, balanceIndex) => {
|
||||
const tokenAddress = balanceInfo[balanceIndex].tokenAddress;
|
||||
const tokenOwnerAddress = balanceInfo[balanceIndex].tokenOwnerAddress;
|
||||
if (_.isUndefined(balancesByOwner[tokenOwnerAddress])) {
|
||||
balancesByOwner[tokenOwnerAddress] = {};
|
||||
}
|
||||
const wrappedBalance = new BigNumber(balance);
|
||||
balancesByOwner[tokenOwnerAddress][tokenAddress] = wrappedBalance;
|
||||
});
|
||||
return balancesByOwner;
|
||||
}
|
||||
public addDummyTokenContract(dummy: DummyERC20TokenContract): void {
|
||||
if (!_.isUndefined(this._dummyTokenContracts)) {
|
||||
this._dummyTokenContracts.push(dummy);
|
||||
}
|
||||
}
|
||||
public addTokenOwnerAddress(address: string): void {
|
||||
this._tokenOwnerAddresses.push(address);
|
||||
}
|
||||
public getTokenOwnerAddresses(): string[] {
|
||||
return this._tokenOwnerAddresses;
|
||||
}
|
||||
public getTokenAddresses(): string[] {
|
||||
const tokenAddresses = _.map(this._dummyTokenContracts, dummyTokenContract => dummyTokenContract.address);
|
||||
return tokenAddresses;
|
||||
}
|
||||
private _getTokenContractFromAssetData(assetData: string): DummyERC20TokenContract {
|
||||
const erc20ProxyData = assetDataUtils.decodeERC20AssetData(assetData);
|
||||
const tokenAddress = erc20ProxyData.tokenAddress;
|
||||
const tokenContractIfExists = _.find(this._dummyTokenContracts, c => c.address === tokenAddress);
|
||||
if (_.isUndefined(tokenContractIfExists)) {
|
||||
throw new Error(`Token: ${tokenAddress} was not deployed through ERC20Wrapper`);
|
||||
}
|
||||
return tokenContractIfExists;
|
||||
}
|
||||
private _validateDummyTokenContractsExistOrThrow(): void {
|
||||
if (_.isUndefined(this._dummyTokenContracts)) {
|
||||
throw new Error('Dummy ERC20 tokens not yet deployed, please call "deployDummyTokensAsync"');
|
||||
}
|
||||
}
|
||||
private _validateProxyContractExistsOrThrow(): void {
|
||||
if (_.isUndefined(this._proxyContract)) {
|
||||
throw new Error('ERC20 proxy contract not yet deployed, please call "deployProxyAsync"');
|
||||
}
|
||||
}
|
||||
}
|
234
contracts/exchange/test/utils/erc721_wrapper.ts
Normal file
234
contracts/exchange/test/utils/erc721_wrapper.ts
Normal file
@@ -0,0 +1,234 @@
|
||||
import { artifacts as proxyArtifacts, ERC721ProxyContract } from '@0x/contracts-asset-proxy';
|
||||
import { constants, ERC721TokenIdsByOwner, txDefaults } from '@0x/contracts-test-utils';
|
||||
import { artifacts as tokensArtifacts, DummyERC721TokenContract } from '@0x/contracts-tokens';
|
||||
import { generatePseudoRandomSalt } from '@0x/order-utils';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import { Web3Wrapper } from '@0x/web3-wrapper';
|
||||
import { Provider } from 'ethereum-types';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
export class ERC721Wrapper {
|
||||
private readonly _tokenOwnerAddresses: string[];
|
||||
private readonly _contractOwnerAddress: string;
|
||||
private readonly _web3Wrapper: Web3Wrapper;
|
||||
private readonly _provider: Provider;
|
||||
private readonly _dummyTokenContracts: DummyERC721TokenContract[];
|
||||
private _proxyContract?: ERC721ProxyContract;
|
||||
private _proxyIdIfExists?: string;
|
||||
private _initialTokenIdsByOwner: ERC721TokenIdsByOwner = {};
|
||||
constructor(provider: Provider, tokenOwnerAddresses: string[], contractOwnerAddress: string) {
|
||||
this._web3Wrapper = new Web3Wrapper(provider);
|
||||
this._provider = provider;
|
||||
this._dummyTokenContracts = [];
|
||||
this._tokenOwnerAddresses = tokenOwnerAddresses;
|
||||
this._contractOwnerAddress = contractOwnerAddress;
|
||||
}
|
||||
public async deployDummyTokensAsync(): Promise<DummyERC721TokenContract[]> {
|
||||
// tslint:disable-next-line:no-unused-variable
|
||||
for (const i of _.times(constants.NUM_DUMMY_ERC721_TO_DEPLOY)) {
|
||||
this._dummyTokenContracts.push(
|
||||
await DummyERC721TokenContract.deployFrom0xArtifactAsync(
|
||||
tokensArtifacts.DummyERC721Token,
|
||||
this._provider,
|
||||
txDefaults,
|
||||
constants.DUMMY_TOKEN_NAME,
|
||||
constants.DUMMY_TOKEN_SYMBOL,
|
||||
),
|
||||
);
|
||||
}
|
||||
return this._dummyTokenContracts;
|
||||
}
|
||||
public async deployProxyAsync(): Promise<ERC721ProxyContract> {
|
||||
this._proxyContract = await ERC721ProxyContract.deployFrom0xArtifactAsync(
|
||||
proxyArtifacts.ERC721Proxy,
|
||||
this._provider,
|
||||
txDefaults,
|
||||
);
|
||||
this._proxyIdIfExists = await this._proxyContract.getProxyId.callAsync();
|
||||
return this._proxyContract;
|
||||
}
|
||||
public getProxyId(): string {
|
||||
this._validateProxyContractExistsOrThrow();
|
||||
return this._proxyIdIfExists as string;
|
||||
}
|
||||
public async setBalancesAndAllowancesAsync(): Promise<void> {
|
||||
this._validateDummyTokenContractsExistOrThrow();
|
||||
this._validateProxyContractExistsOrThrow();
|
||||
this._initialTokenIdsByOwner = {};
|
||||
for (const dummyTokenContract of this._dummyTokenContracts) {
|
||||
for (const tokenOwnerAddress of this._tokenOwnerAddresses) {
|
||||
// tslint:disable-next-line:no-unused-variable
|
||||
for (const i of _.times(constants.NUM_ERC721_TOKENS_TO_MINT)) {
|
||||
const tokenId = generatePseudoRandomSalt();
|
||||
await this.mintAsync(dummyTokenContract.address, tokenId, tokenOwnerAddress);
|
||||
if (_.isUndefined(this._initialTokenIdsByOwner[tokenOwnerAddress])) {
|
||||
this._initialTokenIdsByOwner[tokenOwnerAddress] = {
|
||||
[dummyTokenContract.address]: [],
|
||||
};
|
||||
}
|
||||
if (_.isUndefined(this._initialTokenIdsByOwner[tokenOwnerAddress][dummyTokenContract.address])) {
|
||||
this._initialTokenIdsByOwner[tokenOwnerAddress][dummyTokenContract.address] = [];
|
||||
}
|
||||
this._initialTokenIdsByOwner[tokenOwnerAddress][dummyTokenContract.address].push(tokenId);
|
||||
|
||||
await this.approveProxyAsync(dummyTokenContract.address, tokenId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
public async doesTokenExistAsync(tokenAddress: string, tokenId: BigNumber): Promise<boolean> {
|
||||
const tokenContract = this._getTokenContractFromAssetData(tokenAddress);
|
||||
const owner = await tokenContract.ownerOf.callAsync(tokenId);
|
||||
const doesExist = owner !== constants.NULL_ADDRESS;
|
||||
return doesExist;
|
||||
}
|
||||
public async approveProxyAsync(tokenAddress: string, tokenId: BigNumber): Promise<void> {
|
||||
const proxyAddress = (this._proxyContract as ERC721ProxyContract).address;
|
||||
await this.approveAsync(proxyAddress, tokenAddress, tokenId);
|
||||
}
|
||||
public async approveProxyForAllAsync(tokenAddress: string, tokenId: BigNumber, isApproved: boolean): Promise<void> {
|
||||
const tokenContract = this._getTokenContractFromAssetData(tokenAddress);
|
||||
const tokenOwner = await this.ownerOfAsync(tokenAddress, tokenId);
|
||||
const proxyAddress = (this._proxyContract as ERC721ProxyContract).address;
|
||||
await this._web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await tokenContract.setApprovalForAll.sendTransactionAsync(proxyAddress, isApproved, {
|
||||
from: tokenOwner,
|
||||
}),
|
||||
constants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
}
|
||||
public async approveAsync(to: string, tokenAddress: string, tokenId: BigNumber): Promise<void> {
|
||||
const tokenContract = this._getTokenContractFromAssetData(tokenAddress);
|
||||
const tokenOwner = await this.ownerOfAsync(tokenAddress, tokenId);
|
||||
await this._web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await tokenContract.approve.sendTransactionAsync(to, tokenId, {
|
||||
from: tokenOwner,
|
||||
}),
|
||||
constants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
}
|
||||
public async transferFromAsync(
|
||||
tokenAddress: string,
|
||||
tokenId: BigNumber,
|
||||
currentOwner: string,
|
||||
userAddress: string,
|
||||
): Promise<void> {
|
||||
const tokenContract = this._getTokenContractFromAssetData(tokenAddress);
|
||||
await this._web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await tokenContract.transferFrom.sendTransactionAsync(currentOwner, userAddress, tokenId, {
|
||||
from: currentOwner,
|
||||
}),
|
||||
constants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
}
|
||||
public async mintAsync(tokenAddress: string, tokenId: BigNumber, userAddress: string): Promise<void> {
|
||||
const tokenContract = this._getTokenContractFromAssetData(tokenAddress);
|
||||
await this._web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await tokenContract.mint.sendTransactionAsync(userAddress, tokenId, {
|
||||
from: this._contractOwnerAddress,
|
||||
}),
|
||||
constants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
}
|
||||
public async burnAsync(tokenAddress: string, tokenId: BigNumber, owner: string): Promise<void> {
|
||||
const tokenContract = this._getTokenContractFromAssetData(tokenAddress);
|
||||
await this._web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await tokenContract.burn.sendTransactionAsync(owner, tokenId, {
|
||||
from: this._contractOwnerAddress,
|
||||
}),
|
||||
constants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
}
|
||||
public async ownerOfAsync(tokenAddress: string, tokenId: BigNumber): Promise<string> {
|
||||
const tokenContract = this._getTokenContractFromAssetData(tokenAddress);
|
||||
const owner = await tokenContract.ownerOf.callAsync(tokenId);
|
||||
return owner;
|
||||
}
|
||||
public async isOwnerAsync(userAddress: string, tokenAddress: string, tokenId: BigNumber): Promise<boolean> {
|
||||
const tokenContract = this._getTokenContractFromAssetData(tokenAddress);
|
||||
const tokenOwner = await tokenContract.ownerOf.callAsync(tokenId);
|
||||
const isOwner = tokenOwner === userAddress;
|
||||
return isOwner;
|
||||
}
|
||||
public async isProxyApprovedForAllAsync(userAddress: string, tokenAddress: string): Promise<boolean> {
|
||||
this._validateProxyContractExistsOrThrow();
|
||||
const tokenContract = this._getTokenContractFromAssetData(tokenAddress);
|
||||
const operator = (this._proxyContract as ERC721ProxyContract).address;
|
||||
const didApproveAll = await tokenContract.isApprovedForAll.callAsync(userAddress, operator);
|
||||
return didApproveAll;
|
||||
}
|
||||
public async isProxyApprovedAsync(tokenAddress: string, tokenId: BigNumber): Promise<boolean> {
|
||||
this._validateProxyContractExistsOrThrow();
|
||||
const tokenContract = this._getTokenContractFromAssetData(tokenAddress);
|
||||
const approvedAddress = await tokenContract.getApproved.callAsync(tokenId);
|
||||
const proxyAddress = (this._proxyContract as ERC721ProxyContract).address;
|
||||
const isProxyAnApprovedOperator = approvedAddress === proxyAddress;
|
||||
return isProxyAnApprovedOperator;
|
||||
}
|
||||
public async getBalancesAsync(): Promise<ERC721TokenIdsByOwner> {
|
||||
this._validateDummyTokenContractsExistOrThrow();
|
||||
this._validateBalancesAndAllowancesSetOrThrow();
|
||||
const tokenIdsByOwner: ERC721TokenIdsByOwner = {};
|
||||
const tokenOwnerAddresses: string[] = [];
|
||||
const tokenInfo: Array<{ tokenId: BigNumber; tokenAddress: string }> = [];
|
||||
for (const dummyTokenContract of this._dummyTokenContracts) {
|
||||
for (const tokenOwnerAddress of this._tokenOwnerAddresses) {
|
||||
const initialTokenOwnerIds = this._initialTokenIdsByOwner[tokenOwnerAddress][
|
||||
dummyTokenContract.address
|
||||
];
|
||||
for (const tokenId of initialTokenOwnerIds) {
|
||||
tokenOwnerAddresses.push(await dummyTokenContract.ownerOf.callAsync(tokenId));
|
||||
tokenInfo.push({
|
||||
tokenId,
|
||||
tokenAddress: dummyTokenContract.address,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
_.forEach(tokenOwnerAddresses, (tokenOwnerAddress, ownerIndex) => {
|
||||
const tokenAddress = tokenInfo[ownerIndex].tokenAddress;
|
||||
const tokenId = tokenInfo[ownerIndex].tokenId;
|
||||
if (_.isUndefined(tokenIdsByOwner[tokenOwnerAddress])) {
|
||||
tokenIdsByOwner[tokenOwnerAddress] = {
|
||||
[tokenAddress]: [],
|
||||
};
|
||||
}
|
||||
if (_.isUndefined(tokenIdsByOwner[tokenOwnerAddress][tokenAddress])) {
|
||||
tokenIdsByOwner[tokenOwnerAddress][tokenAddress] = [];
|
||||
}
|
||||
tokenIdsByOwner[tokenOwnerAddress][tokenAddress].push(tokenId);
|
||||
});
|
||||
return tokenIdsByOwner;
|
||||
}
|
||||
public getTokenOwnerAddresses(): string[] {
|
||||
return this._tokenOwnerAddresses;
|
||||
}
|
||||
public getTokenAddresses(): string[] {
|
||||
const tokenAddresses = _.map(this._dummyTokenContracts, dummyTokenContract => dummyTokenContract.address);
|
||||
return tokenAddresses;
|
||||
}
|
||||
private _getTokenContractFromAssetData(tokenAddress: string): DummyERC721TokenContract {
|
||||
const tokenContractIfExists = _.find(this._dummyTokenContracts, c => c.address === tokenAddress);
|
||||
if (_.isUndefined(tokenContractIfExists)) {
|
||||
throw new Error(`Token: ${tokenAddress} was not deployed through ERC20Wrapper`);
|
||||
}
|
||||
return tokenContractIfExists;
|
||||
}
|
||||
private _validateDummyTokenContractsExistOrThrow(): void {
|
||||
if (_.isUndefined(this._dummyTokenContracts)) {
|
||||
throw new Error('Dummy ERC721 tokens not yet deployed, please call "deployDummyTokensAsync"');
|
||||
}
|
||||
}
|
||||
private _validateProxyContractExistsOrThrow(): void {
|
||||
if (_.isUndefined(this._proxyContract)) {
|
||||
throw new Error('ERC721 proxy contract not yet deployed, please call "deployProxyAsync"');
|
||||
}
|
||||
}
|
||||
private _validateBalancesAndAllowancesSetOrThrow(): void {
|
||||
if (_.keys(this._initialTokenIdsByOwner).length === 0) {
|
||||
throw new Error(
|
||||
'Dummy ERC721 balances and allowances not yet set, please call "setBalancesAndAllowancesAsync"',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
292
contracts/exchange/test/utils/exchange_wrapper.ts
Normal file
292
contracts/exchange/test/utils/exchange_wrapper.ts
Normal file
@@ -0,0 +1,292 @@
|
||||
import {
|
||||
FillResults,
|
||||
formatters,
|
||||
LogDecoder,
|
||||
OrderInfo,
|
||||
orderUtils,
|
||||
SignedTransaction,
|
||||
} from '@0x/contracts-test-utils';
|
||||
import { artifacts as tokensArtifacts } from '@0x/contracts-tokens';
|
||||
import { SignedOrder } from '@0x/types';
|
||||
import { AbiEncoder, BigNumber } from '@0x/utils';
|
||||
import { Web3Wrapper } from '@0x/web3-wrapper';
|
||||
import { MethodAbi, Provider, TransactionReceiptWithDecodedLogs } from 'ethereum-types';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { ExchangeContract } from '../../generated-wrappers/exchange';
|
||||
import { artifacts } from '../../src/artifacts';
|
||||
|
||||
import { AbiDecodedFillOrderData } from './types';
|
||||
|
||||
export class ExchangeWrapper {
|
||||
private readonly _exchange: ExchangeContract;
|
||||
private readonly _web3Wrapper: Web3Wrapper;
|
||||
private readonly _logDecoder: LogDecoder;
|
||||
constructor(exchangeContract: ExchangeContract, provider: Provider) {
|
||||
this._exchange = exchangeContract;
|
||||
this._web3Wrapper = new Web3Wrapper(provider);
|
||||
this._logDecoder = new LogDecoder(this._web3Wrapper, { ...artifacts, ...tokensArtifacts });
|
||||
}
|
||||
public async fillOrderAsync(
|
||||
signedOrder: SignedOrder,
|
||||
from: string,
|
||||
opts: { takerAssetFillAmount?: BigNumber } = {},
|
||||
): Promise<TransactionReceiptWithDecodedLogs> {
|
||||
const params = orderUtils.createFill(signedOrder, opts.takerAssetFillAmount);
|
||||
const txHash = await this._exchange.fillOrder.sendTransactionAsync(
|
||||
params.order,
|
||||
params.takerAssetFillAmount,
|
||||
params.signature,
|
||||
{ from },
|
||||
);
|
||||
const txReceipt = await this._logDecoder.getTxWithDecodedLogsAsync(txHash);
|
||||
return txReceipt;
|
||||
}
|
||||
public async cancelOrderAsync(signedOrder: SignedOrder, from: string): Promise<TransactionReceiptWithDecodedLogs> {
|
||||
const params = orderUtils.createCancel(signedOrder);
|
||||
const txHash = await this._exchange.cancelOrder.sendTransactionAsync(params.order, { from });
|
||||
const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash);
|
||||
return tx;
|
||||
}
|
||||
public async fillOrKillOrderAsync(
|
||||
signedOrder: SignedOrder,
|
||||
from: string,
|
||||
opts: { takerAssetFillAmount?: BigNumber } = {},
|
||||
): Promise<TransactionReceiptWithDecodedLogs> {
|
||||
const params = orderUtils.createFill(signedOrder, opts.takerAssetFillAmount);
|
||||
const txHash = await this._exchange.fillOrKillOrder.sendTransactionAsync(
|
||||
params.order,
|
||||
params.takerAssetFillAmount,
|
||||
params.signature,
|
||||
{ from },
|
||||
);
|
||||
const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash);
|
||||
return tx;
|
||||
}
|
||||
public async fillOrderNoThrowAsync(
|
||||
signedOrder: SignedOrder,
|
||||
from: string,
|
||||
opts: { takerAssetFillAmount?: BigNumber; gas?: number } = {},
|
||||
): Promise<TransactionReceiptWithDecodedLogs> {
|
||||
const params = orderUtils.createFill(signedOrder, opts.takerAssetFillAmount);
|
||||
const txHash = await this._exchange.fillOrderNoThrow.sendTransactionAsync(
|
||||
params.order,
|
||||
params.takerAssetFillAmount,
|
||||
params.signature,
|
||||
{ from, gas: opts.gas },
|
||||
);
|
||||
const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash);
|
||||
return tx;
|
||||
}
|
||||
public async batchFillOrdersAsync(
|
||||
orders: SignedOrder[],
|
||||
from: string,
|
||||
opts: { takerAssetFillAmounts?: BigNumber[] } = {},
|
||||
): Promise<TransactionReceiptWithDecodedLogs> {
|
||||
const params = formatters.createBatchFill(orders, opts.takerAssetFillAmounts);
|
||||
const txHash = await this._exchange.batchFillOrders.sendTransactionAsync(
|
||||
params.orders,
|
||||
params.takerAssetFillAmounts,
|
||||
params.signatures,
|
||||
{ from },
|
||||
);
|
||||
const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash);
|
||||
return tx;
|
||||
}
|
||||
public async batchFillOrKillOrdersAsync(
|
||||
orders: SignedOrder[],
|
||||
from: string,
|
||||
opts: { takerAssetFillAmounts?: BigNumber[] } = {},
|
||||
): Promise<TransactionReceiptWithDecodedLogs> {
|
||||
const params = formatters.createBatchFill(orders, opts.takerAssetFillAmounts);
|
||||
const txHash = await this._exchange.batchFillOrKillOrders.sendTransactionAsync(
|
||||
params.orders,
|
||||
params.takerAssetFillAmounts,
|
||||
params.signatures,
|
||||
{ from },
|
||||
);
|
||||
const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash);
|
||||
return tx;
|
||||
}
|
||||
public async batchFillOrdersNoThrowAsync(
|
||||
orders: SignedOrder[],
|
||||
from: string,
|
||||
opts: { takerAssetFillAmounts?: BigNumber[]; gas?: number } = {},
|
||||
): Promise<TransactionReceiptWithDecodedLogs> {
|
||||
const params = formatters.createBatchFill(orders, opts.takerAssetFillAmounts);
|
||||
const txHash = await this._exchange.batchFillOrdersNoThrow.sendTransactionAsync(
|
||||
params.orders,
|
||||
params.takerAssetFillAmounts,
|
||||
params.signatures,
|
||||
{ from, gas: opts.gas },
|
||||
);
|
||||
const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash);
|
||||
return tx;
|
||||
}
|
||||
public async marketSellOrdersAsync(
|
||||
orders: SignedOrder[],
|
||||
from: string,
|
||||
opts: { takerAssetFillAmount: BigNumber },
|
||||
): Promise<TransactionReceiptWithDecodedLogs> {
|
||||
const params = formatters.createMarketSellOrders(orders, opts.takerAssetFillAmount);
|
||||
const txHash = await this._exchange.marketSellOrders.sendTransactionAsync(
|
||||
params.orders,
|
||||
params.takerAssetFillAmount,
|
||||
params.signatures,
|
||||
{ from },
|
||||
);
|
||||
const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash);
|
||||
return tx;
|
||||
}
|
||||
public async marketSellOrdersNoThrowAsync(
|
||||
orders: SignedOrder[],
|
||||
from: string,
|
||||
opts: { takerAssetFillAmount: BigNumber; gas?: number },
|
||||
): Promise<TransactionReceiptWithDecodedLogs> {
|
||||
const params = formatters.createMarketSellOrders(orders, opts.takerAssetFillAmount);
|
||||
const txHash = await this._exchange.marketSellOrdersNoThrow.sendTransactionAsync(
|
||||
params.orders,
|
||||
params.takerAssetFillAmount,
|
||||
params.signatures,
|
||||
{ from, gas: opts.gas },
|
||||
);
|
||||
const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash);
|
||||
return tx;
|
||||
}
|
||||
public async marketBuyOrdersAsync(
|
||||
orders: SignedOrder[],
|
||||
from: string,
|
||||
opts: { makerAssetFillAmount: BigNumber },
|
||||
): Promise<TransactionReceiptWithDecodedLogs> {
|
||||
const params = formatters.createMarketBuyOrders(orders, opts.makerAssetFillAmount);
|
||||
const txHash = await this._exchange.marketBuyOrders.sendTransactionAsync(
|
||||
params.orders,
|
||||
params.makerAssetFillAmount,
|
||||
params.signatures,
|
||||
{ from },
|
||||
);
|
||||
const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash);
|
||||
return tx;
|
||||
}
|
||||
public async marketBuyOrdersNoThrowAsync(
|
||||
orders: SignedOrder[],
|
||||
from: string,
|
||||
opts: { makerAssetFillAmount: BigNumber; gas?: number },
|
||||
): Promise<TransactionReceiptWithDecodedLogs> {
|
||||
const params = formatters.createMarketBuyOrders(orders, opts.makerAssetFillAmount);
|
||||
const txHash = await this._exchange.marketBuyOrdersNoThrow.sendTransactionAsync(
|
||||
params.orders,
|
||||
params.makerAssetFillAmount,
|
||||
params.signatures,
|
||||
{ from, gas: opts.gas },
|
||||
);
|
||||
const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash);
|
||||
return tx;
|
||||
}
|
||||
public async batchCancelOrdersAsync(
|
||||
orders: SignedOrder[],
|
||||
from: string,
|
||||
): Promise<TransactionReceiptWithDecodedLogs> {
|
||||
const params = formatters.createBatchCancel(orders);
|
||||
const txHash = await this._exchange.batchCancelOrders.sendTransactionAsync(params.orders, { from });
|
||||
const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash);
|
||||
return tx;
|
||||
}
|
||||
public async cancelOrdersUpToAsync(salt: BigNumber, from: string): Promise<TransactionReceiptWithDecodedLogs> {
|
||||
const txHash = await this._exchange.cancelOrdersUpTo.sendTransactionAsync(salt, { from });
|
||||
const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash);
|
||||
return tx;
|
||||
}
|
||||
public async registerAssetProxyAsync(
|
||||
assetProxyAddress: string,
|
||||
from: string,
|
||||
): Promise<TransactionReceiptWithDecodedLogs> {
|
||||
const txHash = await this._exchange.registerAssetProxy.sendTransactionAsync(assetProxyAddress, { from });
|
||||
const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash);
|
||||
return tx;
|
||||
}
|
||||
public async executeTransactionAsync(
|
||||
signedTx: SignedTransaction,
|
||||
from: string,
|
||||
): Promise<TransactionReceiptWithDecodedLogs> {
|
||||
const txHash = await this._exchange.executeTransaction.sendTransactionAsync(
|
||||
signedTx.salt,
|
||||
signedTx.signerAddress,
|
||||
signedTx.data,
|
||||
signedTx.signature,
|
||||
{ from },
|
||||
);
|
||||
const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash);
|
||||
return tx;
|
||||
}
|
||||
public async getTakerAssetFilledAmountAsync(orderHashHex: string): Promise<BigNumber> {
|
||||
const filledAmount = await this._exchange.filled.callAsync(orderHashHex);
|
||||
return filledAmount;
|
||||
}
|
||||
public async isCancelledAsync(orderHashHex: string): Promise<boolean> {
|
||||
const isCancelled = await this._exchange.cancelled.callAsync(orderHashHex);
|
||||
return isCancelled;
|
||||
}
|
||||
public async getOrderEpochAsync(makerAddress: string, senderAddress: string): Promise<BigNumber> {
|
||||
const orderEpoch = await this._exchange.orderEpoch.callAsync(makerAddress, senderAddress);
|
||||
return orderEpoch;
|
||||
}
|
||||
public async getOrderInfoAsync(signedOrder: SignedOrder): Promise<OrderInfo> {
|
||||
const orderInfo = await this._exchange.getOrderInfo.callAsync(signedOrder);
|
||||
return orderInfo;
|
||||
}
|
||||
public async getOrdersInfoAsync(signedOrders: SignedOrder[]): Promise<OrderInfo[]> {
|
||||
const ordersInfo = (await this._exchange.getOrdersInfo.callAsync(signedOrders)) as OrderInfo[];
|
||||
return ordersInfo;
|
||||
}
|
||||
public async matchOrdersAsync(
|
||||
signedOrderLeft: SignedOrder,
|
||||
signedOrderRight: SignedOrder,
|
||||
from: string,
|
||||
): Promise<TransactionReceiptWithDecodedLogs> {
|
||||
const params = orderUtils.createMatchOrders(signedOrderLeft, signedOrderRight);
|
||||
const txHash = await this._exchange.matchOrders.sendTransactionAsync(
|
||||
params.left,
|
||||
params.right,
|
||||
params.leftSignature,
|
||||
params.rightSignature,
|
||||
{ from },
|
||||
);
|
||||
const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash);
|
||||
return tx;
|
||||
}
|
||||
public async getFillOrderResultsAsync(
|
||||
signedOrder: SignedOrder,
|
||||
from: string,
|
||||
opts: { takerAssetFillAmount?: BigNumber } = {},
|
||||
): Promise<FillResults> {
|
||||
const params = orderUtils.createFill(signedOrder, opts.takerAssetFillAmount);
|
||||
const fillResults = await this._exchange.fillOrder.callAsync(
|
||||
params.order,
|
||||
params.takerAssetFillAmount,
|
||||
params.signature,
|
||||
{ from },
|
||||
);
|
||||
return fillResults;
|
||||
}
|
||||
public abiEncodeFillOrder(signedOrder: SignedOrder, opts: { takerAssetFillAmount?: BigNumber } = {}): string {
|
||||
const params = orderUtils.createFill(signedOrder, opts.takerAssetFillAmount);
|
||||
const data = this._exchange.fillOrder.getABIEncodedTransactionData(
|
||||
params.order,
|
||||
params.takerAssetFillAmount,
|
||||
params.signature,
|
||||
);
|
||||
return data;
|
||||
}
|
||||
public abiDecodeFillOrder(data: string): AbiDecodedFillOrderData {
|
||||
// Lookup fillOrder ABI in exchange abi
|
||||
const fillOrderAbi = _.find(this._exchange.abi, { name: 'fillOrder' }) as MethodAbi;
|
||||
// Decode input data
|
||||
const abiEncoder = new AbiEncoder.Method(fillOrderAbi);
|
||||
const decodedData = abiEncoder.decode(data) as AbiDecodedFillOrderData;
|
||||
return decodedData;
|
||||
}
|
||||
public getExchangeAddress(): string {
|
||||
return this._exchange.address;
|
||||
}
|
||||
}
|
928
contracts/exchange/test/utils/fill_order_combinatorial_utils.ts
Normal file
928
contracts/exchange/test/utils/fill_order_combinatorial_utils.ts
Normal file
@@ -0,0 +1,928 @@
|
||||
import { artifacts as libsArtifacts, TestLibsContract } from '@0x/contracts-libs';
|
||||
import {
|
||||
AllowanceAmountScenario,
|
||||
AssetDataScenario,
|
||||
BalanceAmountScenario,
|
||||
chaiSetup,
|
||||
constants,
|
||||
expectTransactionFailedAsync,
|
||||
ExpirationTimeSecondsScenario,
|
||||
FeeRecipientAddressScenario,
|
||||
FillScenario,
|
||||
OrderAssetAmountScenario,
|
||||
orderUtils,
|
||||
signingUtils,
|
||||
TakerAssetFillAmountScenario,
|
||||
TakerScenario,
|
||||
TraderStateScenario,
|
||||
} from '@0x/contracts-test-utils';
|
||||
import {
|
||||
assetDataUtils,
|
||||
BalanceAndProxyAllowanceLazyStore,
|
||||
ExchangeTransferSimulator,
|
||||
orderHashUtils,
|
||||
OrderStateUtils,
|
||||
OrderValidationUtils,
|
||||
} from '@0x/order-utils';
|
||||
import { AssetProxyId, RevertReason, SignatureType, SignedOrder } from '@0x/types';
|
||||
import { BigNumber, errorUtils, logUtils } from '@0x/utils';
|
||||
import { Web3Wrapper } from '@0x/web3-wrapper';
|
||||
import * as chai from 'chai';
|
||||
import { LogWithDecodedArgs, Provider, TxData } from 'ethereum-types';
|
||||
import * as _ from 'lodash';
|
||||
import 'make-promises-safe';
|
||||
|
||||
import { ExchangeContract, ExchangeFillEventArgs } from '../../generated-wrappers/exchange';
|
||||
import { artifacts } from '../../src/artifacts';
|
||||
|
||||
import { AssetWrapper } from './asset_wrapper';
|
||||
import { ERC20Wrapper } from './erc20_wrapper';
|
||||
import { ERC721Wrapper } from './erc721_wrapper';
|
||||
import { ExchangeWrapper } from './exchange_wrapper';
|
||||
import { OrderFactoryFromScenario } from './order_factory_from_scenario';
|
||||
import { SimpleAssetBalanceAndProxyAllowanceFetcher } from './simple_asset_balance_and_proxy_allowance_fetcher';
|
||||
import { SimpleOrderFilledCancelledFetcher } from './simple_order_filled_cancelled_fetcher';
|
||||
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
|
||||
/**
|
||||
* Instantiates a new instance of FillOrderCombinatorialUtils. Since this method has some
|
||||
* required async setup, a factory method is required.
|
||||
* @param web3Wrapper Web3Wrapper instance
|
||||
* @param txDefaults Default Ethereum tx options
|
||||
* @return FillOrderCombinatorialUtils instance
|
||||
*/
|
||||
export async function fillOrderCombinatorialUtilsFactoryAsync(
|
||||
web3Wrapper: Web3Wrapper,
|
||||
txDefaults: Partial<TxData>,
|
||||
): Promise<FillOrderCombinatorialUtils> {
|
||||
const accounts = await web3Wrapper.getAvailableAddressesAsync();
|
||||
const userAddresses = _.slice(accounts, 0, 5);
|
||||
const [ownerAddress, makerAddress, takerAddress] = userAddresses;
|
||||
const makerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[userAddresses.indexOf(makerAddress)];
|
||||
|
||||
const provider = web3Wrapper.getProvider();
|
||||
const erc20Wrapper = new ERC20Wrapper(provider, userAddresses, ownerAddress);
|
||||
const erc721Wrapper = new ERC721Wrapper(provider, userAddresses, ownerAddress);
|
||||
|
||||
const erc20EighteenDecimalTokenCount = 3;
|
||||
const eighteenDecimals = new BigNumber(18);
|
||||
const [
|
||||
erc20EighteenDecimalTokenA,
|
||||
erc20EighteenDecimalTokenB,
|
||||
zrxToken,
|
||||
] = await erc20Wrapper.deployDummyTokensAsync(erc20EighteenDecimalTokenCount, eighteenDecimals);
|
||||
const zrxAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address);
|
||||
|
||||
const erc20FiveDecimalTokenCount = 2;
|
||||
const fiveDecimals = new BigNumber(5);
|
||||
const [erc20FiveDecimalTokenA, erc20FiveDecimalTokenB] = await erc20Wrapper.deployDummyTokensAsync(
|
||||
erc20FiveDecimalTokenCount,
|
||||
fiveDecimals,
|
||||
);
|
||||
const zeroDecimals = new BigNumber(0);
|
||||
const erc20ZeroDecimalTokenCount = 2;
|
||||
const [erc20ZeroDecimalTokenA, erc20ZeroDecimalTokenB] = await erc20Wrapper.deployDummyTokensAsync(
|
||||
erc20ZeroDecimalTokenCount,
|
||||
zeroDecimals,
|
||||
);
|
||||
const erc20Proxy = await erc20Wrapper.deployProxyAsync();
|
||||
await erc20Wrapper.setBalancesAndAllowancesAsync();
|
||||
|
||||
const [erc721Token] = await erc721Wrapper.deployDummyTokensAsync();
|
||||
const erc721Proxy = await erc721Wrapper.deployProxyAsync();
|
||||
await erc721Wrapper.setBalancesAndAllowancesAsync();
|
||||
const erc721Balances = await erc721Wrapper.getBalancesAsync();
|
||||
|
||||
const assetWrapper = new AssetWrapper([erc20Wrapper, erc721Wrapper]);
|
||||
|
||||
const exchangeContract = await ExchangeContract.deployFrom0xArtifactAsync(
|
||||
artifacts.Exchange,
|
||||
provider,
|
||||
txDefaults,
|
||||
zrxAssetData,
|
||||
);
|
||||
const exchangeWrapper = new ExchangeWrapper(exchangeContract, provider);
|
||||
await exchangeWrapper.registerAssetProxyAsync(erc20Proxy.address, ownerAddress);
|
||||
await exchangeWrapper.registerAssetProxyAsync(erc721Proxy.address, ownerAddress);
|
||||
|
||||
await web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(exchangeContract.address, {
|
||||
from: ownerAddress,
|
||||
}),
|
||||
constants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
await web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await erc721Proxy.addAuthorizedAddress.sendTransactionAsync(exchangeContract.address, {
|
||||
from: ownerAddress,
|
||||
}),
|
||||
constants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
|
||||
const orderFactory = new OrderFactoryFromScenario(
|
||||
userAddresses,
|
||||
zrxToken.address,
|
||||
[erc20EighteenDecimalTokenA.address, erc20EighteenDecimalTokenB.address],
|
||||
[erc20FiveDecimalTokenA.address, erc20FiveDecimalTokenB.address],
|
||||
[erc20ZeroDecimalTokenA.address, erc20ZeroDecimalTokenB.address],
|
||||
erc721Token,
|
||||
erc721Balances,
|
||||
exchangeContract.address,
|
||||
);
|
||||
|
||||
const testLibsContract = await TestLibsContract.deployFrom0xArtifactAsync(
|
||||
libsArtifacts.TestLibs,
|
||||
provider,
|
||||
txDefaults,
|
||||
);
|
||||
|
||||
const fillOrderCombinatorialUtils = new FillOrderCombinatorialUtils(
|
||||
orderFactory,
|
||||
ownerAddress,
|
||||
makerAddress,
|
||||
makerPrivateKey,
|
||||
takerAddress,
|
||||
zrxAssetData,
|
||||
exchangeWrapper,
|
||||
assetWrapper,
|
||||
testLibsContract,
|
||||
);
|
||||
return fillOrderCombinatorialUtils;
|
||||
}
|
||||
|
||||
export class FillOrderCombinatorialUtils {
|
||||
public orderFactory: OrderFactoryFromScenario;
|
||||
public ownerAddress: string;
|
||||
public makerAddress: string;
|
||||
public makerPrivateKey: Buffer;
|
||||
public takerAddress: string;
|
||||
public zrxAssetData: string;
|
||||
public exchangeWrapper: ExchangeWrapper;
|
||||
public assetWrapper: AssetWrapper;
|
||||
public testLibsContract: TestLibsContract;
|
||||
public static generateFillOrderCombinations(): FillScenario[] {
|
||||
const takerScenarios = [
|
||||
TakerScenario.Unspecified,
|
||||
// TakerScenario.CorrectlySpecified,
|
||||
// TakerScenario.IncorrectlySpecified,
|
||||
];
|
||||
const feeRecipientScenarios = [
|
||||
FeeRecipientAddressScenario.EthUserAddress,
|
||||
// FeeRecipientAddressScenario.BurnAddress,
|
||||
];
|
||||
const makerAssetAmountScenario = [
|
||||
OrderAssetAmountScenario.Large,
|
||||
// OrderAssetAmountScenario.Zero,
|
||||
// OrderAssetAmountScenario.Small,
|
||||
];
|
||||
const takerAssetAmountScenario = [
|
||||
OrderAssetAmountScenario.Large,
|
||||
// OrderAssetAmountScenario.Zero,
|
||||
// OrderAssetAmountScenario.Small,
|
||||
];
|
||||
const makerFeeScenario = [
|
||||
OrderAssetAmountScenario.Large,
|
||||
// OrderAssetAmountScenario.Small,
|
||||
// OrderAssetAmountScenario.Zero,
|
||||
];
|
||||
const takerFeeScenario = [
|
||||
OrderAssetAmountScenario.Large,
|
||||
// OrderAssetAmountScenario.Small,
|
||||
// OrderAssetAmountScenario.Zero,
|
||||
];
|
||||
const expirationTimeSecondsScenario = [
|
||||
ExpirationTimeSecondsScenario.InFuture,
|
||||
ExpirationTimeSecondsScenario.InPast,
|
||||
];
|
||||
const makerAssetDataScenario = [
|
||||
AssetDataScenario.ERC20FiveDecimals,
|
||||
AssetDataScenario.ERC20NonZRXEighteenDecimals,
|
||||
AssetDataScenario.ERC721,
|
||||
AssetDataScenario.ZRXFeeToken,
|
||||
];
|
||||
const takerAssetDataScenario = [
|
||||
AssetDataScenario.ERC20FiveDecimals,
|
||||
AssetDataScenario.ERC20NonZRXEighteenDecimals,
|
||||
AssetDataScenario.ERC721,
|
||||
AssetDataScenario.ZRXFeeToken,
|
||||
];
|
||||
const takerAssetFillAmountScenario = [
|
||||
TakerAssetFillAmountScenario.ExactlyRemainingFillableTakerAssetAmount,
|
||||
// TakerAssetFillAmountScenario.GreaterThanRemainingFillableTakerAssetAmount,
|
||||
// TakerAssetFillAmountScenario.LessThanRemainingFillableTakerAssetAmount,
|
||||
];
|
||||
const makerAssetBalanceScenario = [
|
||||
BalanceAmountScenario.Higher,
|
||||
// BalanceAmountScenario.Exact,
|
||||
// BalanceAmountScenario.TooLow,
|
||||
];
|
||||
const makerAssetAllowanceScenario = [
|
||||
AllowanceAmountScenario.Higher,
|
||||
// AllowanceAmountScenario.Exact,
|
||||
// AllowanceAmountScenario.TooLow,
|
||||
// AllowanceAmountScenario.Unlimited,
|
||||
];
|
||||
const makerZRXBalanceScenario = [
|
||||
BalanceAmountScenario.Higher,
|
||||
// BalanceAmountScenario.Exact,
|
||||
// BalanceAmountScenario.TooLow,
|
||||
];
|
||||
const makerZRXAllowanceScenario = [
|
||||
AllowanceAmountScenario.Higher,
|
||||
// AllowanceAmountScenario.Exact,
|
||||
// AllowanceAmountScenario.TooLow,
|
||||
// AllowanceAmountScenario.Unlimited,
|
||||
];
|
||||
const takerAssetBalanceScenario = [
|
||||
BalanceAmountScenario.Higher,
|
||||
// BalanceAmountScenario.Exact,
|
||||
// BalanceAmountScenario.TooLow,
|
||||
];
|
||||
const takerAssetAllowanceScenario = [
|
||||
AllowanceAmountScenario.Higher,
|
||||
// AllowanceAmountScenario.Exact,
|
||||
// AllowanceAmountScenario.TooLow,
|
||||
// AllowanceAmountScenario.Unlimited,
|
||||
];
|
||||
const takerZRXBalanceScenario = [
|
||||
BalanceAmountScenario.Higher,
|
||||
// BalanceAmountScenario.Exact,
|
||||
// BalanceAmountScenario.TooLow,
|
||||
];
|
||||
const takerZRXAllowanceScenario = [
|
||||
AllowanceAmountScenario.Higher,
|
||||
// AllowanceAmountScenario.Exact,
|
||||
// AllowanceAmountScenario.TooLow,
|
||||
// AllowanceAmountScenario.Unlimited,
|
||||
];
|
||||
const fillScenarioArrays = FillOrderCombinatorialUtils._getAllCombinations([
|
||||
takerScenarios,
|
||||
feeRecipientScenarios,
|
||||
makerAssetAmountScenario,
|
||||
takerAssetAmountScenario,
|
||||
makerFeeScenario,
|
||||
takerFeeScenario,
|
||||
expirationTimeSecondsScenario,
|
||||
makerAssetDataScenario,
|
||||
takerAssetDataScenario,
|
||||
takerAssetFillAmountScenario,
|
||||
makerAssetBalanceScenario,
|
||||
makerAssetAllowanceScenario,
|
||||
makerZRXBalanceScenario,
|
||||
makerZRXAllowanceScenario,
|
||||
takerAssetBalanceScenario,
|
||||
takerAssetAllowanceScenario,
|
||||
takerZRXBalanceScenario,
|
||||
takerZRXAllowanceScenario,
|
||||
]);
|
||||
|
||||
const fillScenarios = _.map(fillScenarioArrays, fillScenarioArray => {
|
||||
// tslint:disable:custom-no-magic-numbers
|
||||
const fillScenario: FillScenario = {
|
||||
orderScenario: {
|
||||
takerScenario: fillScenarioArray[0] as TakerScenario,
|
||||
feeRecipientScenario: fillScenarioArray[1] as FeeRecipientAddressScenario,
|
||||
makerAssetAmountScenario: fillScenarioArray[2] as OrderAssetAmountScenario,
|
||||
takerAssetAmountScenario: fillScenarioArray[3] as OrderAssetAmountScenario,
|
||||
makerFeeScenario: fillScenarioArray[4] as OrderAssetAmountScenario,
|
||||
takerFeeScenario: fillScenarioArray[5] as OrderAssetAmountScenario,
|
||||
expirationTimeSecondsScenario: fillScenarioArray[6] as ExpirationTimeSecondsScenario,
|
||||
makerAssetDataScenario: fillScenarioArray[7] as AssetDataScenario,
|
||||
takerAssetDataScenario: fillScenarioArray[8] as AssetDataScenario,
|
||||
},
|
||||
takerAssetFillAmountScenario: fillScenarioArray[9] as TakerAssetFillAmountScenario,
|
||||
makerStateScenario: {
|
||||
traderAssetBalance: fillScenarioArray[10] as BalanceAmountScenario,
|
||||
traderAssetAllowance: fillScenarioArray[11] as AllowanceAmountScenario,
|
||||
zrxFeeBalance: fillScenarioArray[12] as BalanceAmountScenario,
|
||||
zrxFeeAllowance: fillScenarioArray[13] as AllowanceAmountScenario,
|
||||
},
|
||||
takerStateScenario: {
|
||||
traderAssetBalance: fillScenarioArray[14] as BalanceAmountScenario,
|
||||
traderAssetAllowance: fillScenarioArray[15] as AllowanceAmountScenario,
|
||||
zrxFeeBalance: fillScenarioArray[16] as BalanceAmountScenario,
|
||||
zrxFeeAllowance: fillScenarioArray[17] as AllowanceAmountScenario,
|
||||
},
|
||||
};
|
||||
// tslint:enable:custom-no-magic-numbers
|
||||
return fillScenario;
|
||||
});
|
||||
|
||||
return fillScenarios;
|
||||
}
|
||||
/**
|
||||
* Recursive implementation of generating all combinations of the supplied
|
||||
* string-containing arrays.
|
||||
*/
|
||||
private static _getAllCombinations(arrays: string[][]): string[][] {
|
||||
// Base case
|
||||
if (arrays.length === 1) {
|
||||
const remainingValues = _.map(arrays[0], val => {
|
||||
return [val];
|
||||
});
|
||||
return remainingValues;
|
||||
} else {
|
||||
const result = [];
|
||||
const restOfArrays = arrays.slice(1);
|
||||
const allCombinationsOfRemaining = FillOrderCombinatorialUtils._getAllCombinations(restOfArrays); // recur with the rest of array
|
||||
// tslint:disable:prefer-for-of
|
||||
for (let i = 0; i < allCombinationsOfRemaining.length; i++) {
|
||||
for (let j = 0; j < arrays[0].length; j++) {
|
||||
result.push([arrays[0][j], ...allCombinationsOfRemaining[i]]);
|
||||
}
|
||||
}
|
||||
// tslint:enable:prefer-for-of
|
||||
return result;
|
||||
}
|
||||
}
|
||||
constructor(
|
||||
orderFactory: OrderFactoryFromScenario,
|
||||
ownerAddress: string,
|
||||
makerAddress: string,
|
||||
makerPrivateKey: Buffer,
|
||||
takerAddress: string,
|
||||
zrxAssetData: string,
|
||||
exchangeWrapper: ExchangeWrapper,
|
||||
assetWrapper: AssetWrapper,
|
||||
testLibsContract: TestLibsContract,
|
||||
) {
|
||||
this.orderFactory = orderFactory;
|
||||
this.ownerAddress = ownerAddress;
|
||||
this.makerAddress = makerAddress;
|
||||
this.makerPrivateKey = makerPrivateKey;
|
||||
this.takerAddress = takerAddress;
|
||||
this.zrxAssetData = zrxAssetData;
|
||||
this.exchangeWrapper = exchangeWrapper;
|
||||
this.assetWrapper = assetWrapper;
|
||||
this.testLibsContract = testLibsContract;
|
||||
}
|
||||
public async testFillOrderScenarioAsync(
|
||||
provider: Provider,
|
||||
fillScenario: FillScenario,
|
||||
isVerbose: boolean = false,
|
||||
): Promise<void> {
|
||||
// 1. Generate order
|
||||
const order = this.orderFactory.generateOrder(fillScenario.orderScenario);
|
||||
|
||||
// 2. Sign order
|
||||
const orderHashBuff = orderHashUtils.getOrderHashBuffer(order);
|
||||
const signature = signingUtils.signMessage(orderHashBuff, this.makerPrivateKey, SignatureType.EthSign);
|
||||
const signedOrder = {
|
||||
...order,
|
||||
signature: `0x${signature.toString('hex')}`,
|
||||
};
|
||||
|
||||
const balanceAndProxyAllowanceFetcher = new SimpleAssetBalanceAndProxyAllowanceFetcher(this.assetWrapper);
|
||||
const orderFilledCancelledFetcher = new SimpleOrderFilledCancelledFetcher(
|
||||
this.exchangeWrapper,
|
||||
this.zrxAssetData,
|
||||
);
|
||||
|
||||
// 3. Figure out fill amount
|
||||
const takerAssetFillAmount = await this._getTakerAssetFillAmountAsync(
|
||||
signedOrder,
|
||||
fillScenario.takerAssetFillAmountScenario,
|
||||
balanceAndProxyAllowanceFetcher,
|
||||
orderFilledCancelledFetcher,
|
||||
);
|
||||
|
||||
// 4. Permutate the maker and taker balance/allowance scenarios
|
||||
await this._modifyTraderStateAsync(
|
||||
fillScenario.makerStateScenario,
|
||||
fillScenario.takerStateScenario,
|
||||
signedOrder,
|
||||
takerAssetFillAmount,
|
||||
);
|
||||
|
||||
// 5. If I fill it by X, what are the resulting balances/allowances/filled amounts expected?
|
||||
const orderValidationUtils = new OrderValidationUtils(orderFilledCancelledFetcher, provider);
|
||||
const lazyStore = new BalanceAndProxyAllowanceLazyStore(balanceAndProxyAllowanceFetcher);
|
||||
const exchangeTransferSimulator = new ExchangeTransferSimulator(lazyStore);
|
||||
|
||||
let fillRevertReasonIfExists;
|
||||
try {
|
||||
await orderValidationUtils.validateFillOrderThrowIfInvalidAsync(
|
||||
exchangeTransferSimulator,
|
||||
provider,
|
||||
signedOrder,
|
||||
takerAssetFillAmount,
|
||||
this.takerAddress,
|
||||
this.zrxAssetData,
|
||||
);
|
||||
if (isVerbose) {
|
||||
logUtils.log(`Expecting fillOrder to succeed.`);
|
||||
}
|
||||
} catch (err) {
|
||||
fillRevertReasonIfExists = err.message;
|
||||
if (isVerbose) {
|
||||
logUtils.log(`Expecting fillOrder to fail with:`);
|
||||
logUtils.log(err);
|
||||
}
|
||||
}
|
||||
|
||||
// 6. Fill the order
|
||||
await this._fillOrderAndAssertOutcomeAsync(
|
||||
signedOrder,
|
||||
takerAssetFillAmount,
|
||||
lazyStore,
|
||||
fillRevertReasonIfExists,
|
||||
);
|
||||
|
||||
await this._abiEncodeFillOrderAndAssertOutcomeAsync(signedOrder, takerAssetFillAmount);
|
||||
}
|
||||
private async _fillOrderAndAssertOutcomeAsync(
|
||||
signedOrder: SignedOrder,
|
||||
takerAssetFillAmount: BigNumber,
|
||||
lazyStore: BalanceAndProxyAllowanceLazyStore,
|
||||
fillRevertReasonIfExists: RevertReason | undefined,
|
||||
): Promise<void> {
|
||||
if (!_.isUndefined(fillRevertReasonIfExists)) {
|
||||
return expectTransactionFailedAsync(
|
||||
this.exchangeWrapper.fillOrderAsync(signedOrder, this.takerAddress, { takerAssetFillAmount }),
|
||||
fillRevertReasonIfExists,
|
||||
);
|
||||
}
|
||||
|
||||
const makerAddress = signedOrder.makerAddress;
|
||||
const makerAssetData = signedOrder.makerAssetData;
|
||||
const takerAssetData = signedOrder.takerAssetData;
|
||||
const feeRecipient = signedOrder.feeRecipientAddress;
|
||||
|
||||
const expMakerAssetBalanceOfMaker = await lazyStore.getBalanceAsync(makerAssetData, makerAddress);
|
||||
const expMakerAssetAllowanceOfMaker = await lazyStore.getProxyAllowanceAsync(makerAssetData, makerAddress);
|
||||
const expTakerAssetBalanceOfMaker = await lazyStore.getBalanceAsync(takerAssetData, makerAddress);
|
||||
const expZRXAssetBalanceOfMaker = await lazyStore.getBalanceAsync(this.zrxAssetData, makerAddress);
|
||||
const expZRXAssetAllowanceOfMaker = await lazyStore.getProxyAllowanceAsync(this.zrxAssetData, makerAddress);
|
||||
const expTakerAssetBalanceOfTaker = await lazyStore.getBalanceAsync(takerAssetData, this.takerAddress);
|
||||
const expTakerAssetAllowanceOfTaker = await lazyStore.getProxyAllowanceAsync(takerAssetData, this.takerAddress);
|
||||
const expMakerAssetBalanceOfTaker = await lazyStore.getBalanceAsync(makerAssetData, this.takerAddress);
|
||||
const expZRXAssetBalanceOfTaker = await lazyStore.getBalanceAsync(this.zrxAssetData, this.takerAddress);
|
||||
const expZRXAssetAllowanceOfTaker = await lazyStore.getProxyAllowanceAsync(
|
||||
this.zrxAssetData,
|
||||
this.takerAddress,
|
||||
);
|
||||
const expZRXAssetBalanceOfFeeRecipient = await lazyStore.getBalanceAsync(this.zrxAssetData, feeRecipient);
|
||||
|
||||
const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
|
||||
const alreadyFilledTakerAmount = await this.exchangeWrapper.getTakerAssetFilledAmountAsync(orderHash);
|
||||
const remainingTakerAmountToFill = signedOrder.takerAssetAmount.minus(alreadyFilledTakerAmount);
|
||||
const expFilledTakerAmount = takerAssetFillAmount.gt(remainingTakerAmountToFill)
|
||||
? remainingTakerAmountToFill
|
||||
: alreadyFilledTakerAmount.plus(takerAssetFillAmount);
|
||||
|
||||
const expFilledMakerAmount = orderUtils.getPartialAmountFloor(
|
||||
expFilledTakerAmount,
|
||||
signedOrder.takerAssetAmount,
|
||||
signedOrder.makerAssetAmount,
|
||||
);
|
||||
const expMakerFeePaid = orderUtils.getPartialAmountFloor(
|
||||
expFilledTakerAmount,
|
||||
signedOrder.takerAssetAmount,
|
||||
signedOrder.makerFee,
|
||||
);
|
||||
const expTakerFeePaid = orderUtils.getPartialAmountFloor(
|
||||
expFilledTakerAmount,
|
||||
signedOrder.takerAssetAmount,
|
||||
signedOrder.takerFee,
|
||||
);
|
||||
const fillResults = await this.exchangeWrapper.getFillOrderResultsAsync(signedOrder, this.takerAddress, {
|
||||
takerAssetFillAmount,
|
||||
});
|
||||
expect(fillResults.takerAssetFilledAmount).to.be.bignumber.equal(
|
||||
expFilledTakerAmount,
|
||||
'takerAssetFilledAmount',
|
||||
);
|
||||
expect(fillResults.makerAssetFilledAmount).to.be.bignumber.equal(
|
||||
expFilledMakerAmount,
|
||||
'makerAssetFilledAmount',
|
||||
);
|
||||
expect(fillResults.takerFeePaid).to.be.bignumber.equal(expTakerFeePaid, 'takerFeePaid');
|
||||
expect(fillResults.makerFeePaid).to.be.bignumber.equal(expMakerFeePaid, 'makerFeePaid');
|
||||
|
||||
// - Let's fill the order!
|
||||
const txReceipt = await this.exchangeWrapper.fillOrderAsync(signedOrder, this.takerAddress, {
|
||||
takerAssetFillAmount,
|
||||
});
|
||||
|
||||
const actFilledTakerAmount = await this.exchangeWrapper.getTakerAssetFilledAmountAsync(orderHash);
|
||||
expect(actFilledTakerAmount).to.be.bignumber.equal(expFilledTakerAmount, 'filledTakerAmount');
|
||||
|
||||
const exchangeLogs = _.filter(
|
||||
txReceipt.logs,
|
||||
txLog => txLog.address === this.exchangeWrapper.getExchangeAddress(),
|
||||
);
|
||||
expect(exchangeLogs.length).to.be.equal(1, 'logs length');
|
||||
// tslint:disable-next-line:no-unnecessary-type-assertion
|
||||
const log = txReceipt.logs[0] as LogWithDecodedArgs<ExchangeFillEventArgs>;
|
||||
expect(log.args.makerAddress).to.be.equal(makerAddress, 'log.args.makerAddress');
|
||||
expect(log.args.takerAddress).to.be.equal(this.takerAddress, 'log.args.this.takerAddress');
|
||||
expect(log.args.feeRecipientAddress).to.be.equal(feeRecipient, 'log.args.feeRecipientAddress');
|
||||
expect(log.args.makerAssetFilledAmount).to.be.bignumber.equal(
|
||||
expFilledMakerAmount,
|
||||
'log.args.makerAssetFilledAmount',
|
||||
);
|
||||
expect(log.args.takerAssetFilledAmount).to.be.bignumber.equal(
|
||||
expFilledTakerAmount,
|
||||
'log.args.takerAssetFilledAmount',
|
||||
);
|
||||
expect(log.args.makerFeePaid).to.be.bignumber.equal(expMakerFeePaid, 'log.args.makerFeePaid');
|
||||
expect(log.args.takerFeePaid).to.be.bignumber.equal(expTakerFeePaid, 'logs.args.takerFeePaid');
|
||||
expect(log.args.orderHash).to.be.equal(orderHash, 'log.args.orderHash');
|
||||
expect(log.args.makerAssetData).to.be.equal(makerAssetData, 'log.args.makerAssetData');
|
||||
expect(log.args.takerAssetData).to.be.equal(takerAssetData, 'log.args.takerAssetData');
|
||||
|
||||
const actMakerAssetBalanceOfMaker = await this.assetWrapper.getBalanceAsync(makerAddress, makerAssetData);
|
||||
expect(actMakerAssetBalanceOfMaker).to.be.bignumber.equal(
|
||||
expMakerAssetBalanceOfMaker,
|
||||
'makerAssetBalanceOfMaker',
|
||||
);
|
||||
|
||||
const actMakerAssetAllowanceOfMaker = await this.assetWrapper.getProxyAllowanceAsync(
|
||||
makerAddress,
|
||||
makerAssetData,
|
||||
);
|
||||
expect(actMakerAssetAllowanceOfMaker).to.be.bignumber.equal(
|
||||
expMakerAssetAllowanceOfMaker,
|
||||
'makerAssetAllowanceOfMaker',
|
||||
);
|
||||
|
||||
const actTakerAssetBalanceOfMaker = await this.assetWrapper.getBalanceAsync(makerAddress, takerAssetData);
|
||||
expect(actTakerAssetBalanceOfMaker).to.be.bignumber.equal(
|
||||
expTakerAssetBalanceOfMaker,
|
||||
'takerAssetBalanceOfMaker',
|
||||
);
|
||||
|
||||
const actZRXAssetBalanceOfMaker = await this.assetWrapper.getBalanceAsync(makerAddress, this.zrxAssetData);
|
||||
expect(actZRXAssetBalanceOfMaker).to.be.bignumber.equal(expZRXAssetBalanceOfMaker, 'ZRXAssetBalanceOfMaker');
|
||||
|
||||
const actZRXAssetAllowanceOfMaker = await this.assetWrapper.getProxyAllowanceAsync(
|
||||
makerAddress,
|
||||
this.zrxAssetData,
|
||||
);
|
||||
expect(actZRXAssetAllowanceOfMaker).to.be.bignumber.equal(
|
||||
expZRXAssetAllowanceOfMaker,
|
||||
'ZRXAssetAllowanceOfMaker',
|
||||
);
|
||||
|
||||
const actTakerAssetBalanceOfTaker = await this.assetWrapper.getBalanceAsync(this.takerAddress, takerAssetData);
|
||||
expect(actTakerAssetBalanceOfTaker).to.be.bignumber.equal(
|
||||
expTakerAssetBalanceOfTaker,
|
||||
'TakerAssetBalanceOfTaker',
|
||||
);
|
||||
|
||||
const actTakerAssetAllowanceOfTaker = await this.assetWrapper.getProxyAllowanceAsync(
|
||||
this.takerAddress,
|
||||
takerAssetData,
|
||||
);
|
||||
|
||||
expect(actTakerAssetAllowanceOfTaker).to.be.bignumber.equal(
|
||||
expTakerAssetAllowanceOfTaker,
|
||||
'TakerAssetAllowanceOfTaker',
|
||||
);
|
||||
|
||||
const actMakerAssetBalanceOfTaker = await this.assetWrapper.getBalanceAsync(this.takerAddress, makerAssetData);
|
||||
expect(actMakerAssetBalanceOfTaker).to.be.bignumber.equal(
|
||||
expMakerAssetBalanceOfTaker,
|
||||
'MakerAssetBalanceOfTaker',
|
||||
);
|
||||
|
||||
const actZRXAssetBalanceOfTaker = await this.assetWrapper.getBalanceAsync(this.takerAddress, this.zrxAssetData);
|
||||
expect(actZRXAssetBalanceOfTaker).to.be.bignumber.equal(expZRXAssetBalanceOfTaker, 'ZRXAssetBalanceOfTaker');
|
||||
|
||||
const actZRXAssetAllowanceOfTaker = await this.assetWrapper.getProxyAllowanceAsync(
|
||||
this.takerAddress,
|
||||
this.zrxAssetData,
|
||||
);
|
||||
expect(actZRXAssetAllowanceOfTaker).to.be.bignumber.equal(
|
||||
expZRXAssetAllowanceOfTaker,
|
||||
'ZRXAssetAllowanceOfTaker',
|
||||
);
|
||||
|
||||
const actZRXAssetBalanceOfFeeRecipient = await this.assetWrapper.getBalanceAsync(
|
||||
feeRecipient,
|
||||
this.zrxAssetData,
|
||||
);
|
||||
expect(actZRXAssetBalanceOfFeeRecipient).to.be.bignumber.equal(
|
||||
expZRXAssetBalanceOfFeeRecipient,
|
||||
'ZRXAssetBalanceOfFeeRecipient',
|
||||
);
|
||||
}
|
||||
private async _abiEncodeFillOrderAndAssertOutcomeAsync(
|
||||
signedOrder: SignedOrder,
|
||||
takerAssetFillAmount: BigNumber,
|
||||
): Promise<void> {
|
||||
const params = orderUtils.createFill(signedOrder, takerAssetFillAmount);
|
||||
const abiDataEncodedByContract = await this.testLibsContract.publicAbiEncodeFillOrder.callAsync(
|
||||
params.order,
|
||||
params.takerAssetFillAmount,
|
||||
params.signature,
|
||||
);
|
||||
const paramsDecodedByClient = this.exchangeWrapper.abiDecodeFillOrder(abiDataEncodedByContract);
|
||||
expect(paramsDecodedByClient).to.be.deep.equal(params, 'ABIEncodedFillOrderData');
|
||||
}
|
||||
private async _getTakerAssetFillAmountAsync(
|
||||
signedOrder: SignedOrder,
|
||||
takerAssetFillAmountScenario: TakerAssetFillAmountScenario,
|
||||
balanceAndProxyAllowanceFetcher: SimpleAssetBalanceAndProxyAllowanceFetcher,
|
||||
orderFilledCancelledFetcher: SimpleOrderFilledCancelledFetcher,
|
||||
): Promise<BigNumber> {
|
||||
const orderStateUtils = new OrderStateUtils(balanceAndProxyAllowanceFetcher, orderFilledCancelledFetcher);
|
||||
const fillableTakerAssetAmount = await orderStateUtils.getMaxFillableTakerAssetAmountAsync(
|
||||
signedOrder,
|
||||
this.takerAddress,
|
||||
);
|
||||
|
||||
let takerAssetFillAmount;
|
||||
switch (takerAssetFillAmountScenario) {
|
||||
case TakerAssetFillAmountScenario.Zero:
|
||||
takerAssetFillAmount = new BigNumber(0);
|
||||
break;
|
||||
|
||||
case TakerAssetFillAmountScenario.ExactlyRemainingFillableTakerAssetAmount:
|
||||
takerAssetFillAmount = fillableTakerAssetAmount;
|
||||
break;
|
||||
|
||||
case TakerAssetFillAmountScenario.GreaterThanRemainingFillableTakerAssetAmount:
|
||||
takerAssetFillAmount = fillableTakerAssetAmount.plus(1);
|
||||
break;
|
||||
|
||||
case TakerAssetFillAmountScenario.LessThanRemainingFillableTakerAssetAmount:
|
||||
const takerAssetProxyId = assetDataUtils.decodeAssetProxyId(signedOrder.takerAssetData);
|
||||
const makerAssetProxyId = assetDataUtils.decodeAssetProxyId(signedOrder.makerAssetData);
|
||||
const isEitherAssetERC721 =
|
||||
takerAssetProxyId === AssetProxyId.ERC721 || makerAssetProxyId === AssetProxyId.ERC721;
|
||||
if (isEitherAssetERC721) {
|
||||
throw new Error(
|
||||
'Cannot test `TakerAssetFillAmountScenario.LessThanRemainingFillableTakerAssetAmount` together with ERC721 assets since orders involving ERC721 must always be filled exactly.',
|
||||
);
|
||||
}
|
||||
takerAssetFillAmount = fillableTakerAssetAmount.div(2).integerValue(BigNumber.ROUND_FLOOR);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw errorUtils.spawnSwitchErr('TakerAssetFillAmountScenario', takerAssetFillAmountScenario);
|
||||
}
|
||||
|
||||
return takerAssetFillAmount;
|
||||
}
|
||||
private async _modifyTraderStateAsync(
|
||||
makerStateScenario: TraderStateScenario,
|
||||
takerStateScenario: TraderStateScenario,
|
||||
signedOrder: SignedOrder,
|
||||
takerAssetFillAmount: BigNumber,
|
||||
): Promise<void> {
|
||||
const makerAssetFillAmount = orderUtils.getPartialAmountFloor(
|
||||
takerAssetFillAmount,
|
||||
signedOrder.takerAssetAmount,
|
||||
signedOrder.makerAssetAmount,
|
||||
);
|
||||
switch (makerStateScenario.traderAssetBalance) {
|
||||
case BalanceAmountScenario.Higher:
|
||||
break; // Noop since this is already the default
|
||||
|
||||
case BalanceAmountScenario.TooLow:
|
||||
if (makerAssetFillAmount.eq(0)) {
|
||||
throw new Error(`Cannot set makerAssetBalanceOfMaker TooLow if makerAssetFillAmount is 0`);
|
||||
}
|
||||
const tooLowBalance = makerAssetFillAmount.minus(1);
|
||||
await this.assetWrapper.setBalanceAsync(
|
||||
signedOrder.makerAddress,
|
||||
signedOrder.makerAssetData,
|
||||
tooLowBalance,
|
||||
);
|
||||
break;
|
||||
|
||||
case BalanceAmountScenario.Exact:
|
||||
const exactBalance = makerAssetFillAmount;
|
||||
await this.assetWrapper.setBalanceAsync(
|
||||
signedOrder.makerAddress,
|
||||
signedOrder.makerAssetData,
|
||||
exactBalance,
|
||||
);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw errorUtils.spawnSwitchErr(
|
||||
'makerStateScenario.traderAssetBalance',
|
||||
makerStateScenario.traderAssetBalance,
|
||||
);
|
||||
}
|
||||
|
||||
const makerFee = orderUtils.getPartialAmountFloor(
|
||||
takerAssetFillAmount,
|
||||
signedOrder.takerAssetAmount,
|
||||
signedOrder.makerFee,
|
||||
);
|
||||
switch (makerStateScenario.zrxFeeBalance) {
|
||||
case BalanceAmountScenario.Higher:
|
||||
break; // Noop since this is already the default
|
||||
|
||||
case BalanceAmountScenario.TooLow:
|
||||
if (makerFee.eq(0)) {
|
||||
throw new Error(`Cannot set zrxAsserBalanceOfMaker TooLow if makerFee is 0`);
|
||||
}
|
||||
const tooLowBalance = makerFee.minus(1);
|
||||
await this.assetWrapper.setBalanceAsync(signedOrder.makerAddress, this.zrxAssetData, tooLowBalance);
|
||||
break;
|
||||
|
||||
case BalanceAmountScenario.Exact:
|
||||
const exactBalance = makerFee;
|
||||
await this.assetWrapper.setBalanceAsync(signedOrder.makerAddress, this.zrxAssetData, exactBalance);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw errorUtils.spawnSwitchErr('makerStateScenario.zrxFeeBalance', makerStateScenario.zrxFeeBalance);
|
||||
}
|
||||
|
||||
switch (makerStateScenario.traderAssetAllowance) {
|
||||
case AllowanceAmountScenario.Higher:
|
||||
break; // Noop since this is already the default
|
||||
|
||||
case AllowanceAmountScenario.TooLow:
|
||||
const tooLowAllowance = makerAssetFillAmount.minus(1);
|
||||
await this.assetWrapper.setProxyAllowanceAsync(
|
||||
signedOrder.makerAddress,
|
||||
signedOrder.makerAssetData,
|
||||
tooLowAllowance,
|
||||
);
|
||||
break;
|
||||
|
||||
case AllowanceAmountScenario.Exact:
|
||||
const exactAllowance = makerAssetFillAmount;
|
||||
await this.assetWrapper.setProxyAllowanceAsync(
|
||||
signedOrder.makerAddress,
|
||||
signedOrder.makerAssetData,
|
||||
exactAllowance,
|
||||
);
|
||||
break;
|
||||
|
||||
case AllowanceAmountScenario.Unlimited:
|
||||
await this.assetWrapper.setProxyAllowanceAsync(
|
||||
signedOrder.makerAddress,
|
||||
signedOrder.makerAssetData,
|
||||
constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS,
|
||||
);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw errorUtils.spawnSwitchErr(
|
||||
'makerStateScenario.traderAssetAllowance',
|
||||
makerStateScenario.traderAssetAllowance,
|
||||
);
|
||||
}
|
||||
|
||||
switch (makerStateScenario.zrxFeeAllowance) {
|
||||
case AllowanceAmountScenario.Higher:
|
||||
break; // Noop since this is already the default
|
||||
|
||||
case AllowanceAmountScenario.TooLow:
|
||||
const tooLowAllowance = makerFee.minus(1);
|
||||
await this.assetWrapper.setProxyAllowanceAsync(
|
||||
signedOrder.makerAddress,
|
||||
this.zrxAssetData,
|
||||
tooLowAllowance,
|
||||
);
|
||||
break;
|
||||
|
||||
case AllowanceAmountScenario.Exact:
|
||||
const exactAllowance = makerFee;
|
||||
await this.assetWrapper.setProxyAllowanceAsync(
|
||||
signedOrder.makerAddress,
|
||||
this.zrxAssetData,
|
||||
exactAllowance,
|
||||
);
|
||||
break;
|
||||
|
||||
case AllowanceAmountScenario.Unlimited:
|
||||
await this.assetWrapper.setProxyAllowanceAsync(
|
||||
signedOrder.makerAddress,
|
||||
this.zrxAssetData,
|
||||
constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS,
|
||||
);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw errorUtils.spawnSwitchErr(
|
||||
'makerStateScenario.zrxFeeAllowance',
|
||||
makerStateScenario.zrxFeeAllowance,
|
||||
);
|
||||
}
|
||||
|
||||
switch (takerStateScenario.traderAssetBalance) {
|
||||
case BalanceAmountScenario.Higher:
|
||||
break; // Noop since this is already the default
|
||||
|
||||
case BalanceAmountScenario.TooLow:
|
||||
if (takerAssetFillAmount.eq(0)) {
|
||||
throw new Error(`Cannot set takerAssetBalanceOfTaker TooLow if takerAssetFillAmount is 0`);
|
||||
}
|
||||
const tooLowBalance = takerAssetFillAmount.minus(1);
|
||||
await this.assetWrapper.setBalanceAsync(this.takerAddress, signedOrder.takerAssetData, tooLowBalance);
|
||||
break;
|
||||
|
||||
case BalanceAmountScenario.Exact:
|
||||
const exactBalance = takerAssetFillAmount;
|
||||
await this.assetWrapper.setBalanceAsync(this.takerAddress, signedOrder.takerAssetData, exactBalance);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw errorUtils.spawnSwitchErr(
|
||||
'takerStateScenario.traderAssetBalance',
|
||||
takerStateScenario.traderAssetBalance,
|
||||
);
|
||||
}
|
||||
|
||||
const takerFee = orderUtils.getPartialAmountFloor(
|
||||
takerAssetFillAmount,
|
||||
signedOrder.takerAssetAmount,
|
||||
signedOrder.takerFee,
|
||||
);
|
||||
switch (takerStateScenario.zrxFeeBalance) {
|
||||
case BalanceAmountScenario.Higher:
|
||||
break; // Noop since this is already the default
|
||||
|
||||
case BalanceAmountScenario.TooLow:
|
||||
if (takerFee.eq(0)) {
|
||||
throw new Error(`Cannot set zrxAssetBalanceOfTaker TooLow if takerFee is 0`);
|
||||
}
|
||||
const tooLowBalance = takerFee.minus(1);
|
||||
await this.assetWrapper.setBalanceAsync(this.takerAddress, this.zrxAssetData, tooLowBalance);
|
||||
break;
|
||||
|
||||
case BalanceAmountScenario.Exact:
|
||||
const exactBalance = takerFee;
|
||||
await this.assetWrapper.setBalanceAsync(this.takerAddress, this.zrxAssetData, exactBalance);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw errorUtils.spawnSwitchErr('takerStateScenario.zrxFeeBalance', takerStateScenario.zrxFeeBalance);
|
||||
}
|
||||
|
||||
switch (takerStateScenario.traderAssetAllowance) {
|
||||
case AllowanceAmountScenario.Higher:
|
||||
break; // Noop since this is already the default
|
||||
|
||||
case AllowanceAmountScenario.TooLow:
|
||||
const tooLowAllowance = takerAssetFillAmount.minus(1);
|
||||
await this.assetWrapper.setProxyAllowanceAsync(
|
||||
this.takerAddress,
|
||||
signedOrder.takerAssetData,
|
||||
tooLowAllowance,
|
||||
);
|
||||
break;
|
||||
|
||||
case AllowanceAmountScenario.Exact:
|
||||
const exactAllowance = takerAssetFillAmount;
|
||||
await this.assetWrapper.setProxyAllowanceAsync(
|
||||
this.takerAddress,
|
||||
signedOrder.takerAssetData,
|
||||
exactAllowance,
|
||||
);
|
||||
break;
|
||||
|
||||
case AllowanceAmountScenario.Unlimited:
|
||||
await this.assetWrapper.setProxyAllowanceAsync(
|
||||
this.takerAddress,
|
||||
signedOrder.takerAssetData,
|
||||
constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS,
|
||||
);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw errorUtils.spawnSwitchErr(
|
||||
'takerStateScenario.traderAssetAllowance',
|
||||
takerStateScenario.traderAssetAllowance,
|
||||
);
|
||||
}
|
||||
|
||||
switch (takerStateScenario.zrxFeeAllowance) {
|
||||
case AllowanceAmountScenario.Higher:
|
||||
break; // Noop since this is already the default
|
||||
|
||||
case AllowanceAmountScenario.TooLow:
|
||||
const tooLowAllowance = takerFee.minus(1);
|
||||
await this.assetWrapper.setProxyAllowanceAsync(this.takerAddress, this.zrxAssetData, tooLowAllowance);
|
||||
break;
|
||||
|
||||
case AllowanceAmountScenario.Exact:
|
||||
const exactAllowance = takerFee;
|
||||
await this.assetWrapper.setProxyAllowanceAsync(this.takerAddress, this.zrxAssetData, exactAllowance);
|
||||
break;
|
||||
|
||||
case AllowanceAmountScenario.Unlimited:
|
||||
await this.assetWrapper.setProxyAllowanceAsync(
|
||||
this.takerAddress,
|
||||
this.zrxAssetData,
|
||||
constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS,
|
||||
);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw errorUtils.spawnSwitchErr(
|
||||
'takerStateScenario.zrxFeeAllowance',
|
||||
takerStateScenario.zrxFeeAllowance,
|
||||
);
|
||||
}
|
||||
}
|
||||
} // tslint:disable:max-file-line-count
|
3
contracts/exchange/test/utils/index.ts
Normal file
3
contracts/exchange/test/utils/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from './exchange_wrapper';
|
||||
export * from './erc20_wrapper';
|
||||
export * from './erc721_wrapper';
|
564
contracts/exchange/test/utils/match_order_tester.ts
Normal file
564
contracts/exchange/test/utils/match_order_tester.ts
Normal file
@@ -0,0 +1,564 @@
|
||||
import {
|
||||
chaiSetup,
|
||||
ERC20BalancesByOwner,
|
||||
ERC721TokenIdsByOwner,
|
||||
OrderInfo,
|
||||
OrderStatus,
|
||||
TransferAmountsByMatchOrders as TransferAmounts,
|
||||
TransferAmountsLoggedByMatchOrders as LoggedTransferAmounts,
|
||||
} from '@0x/contracts-test-utils';
|
||||
import { assetDataUtils, orderHashUtils } from '@0x/order-utils';
|
||||
import { AssetProxyId, SignedOrder } from '@0x/types';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import * as chai from 'chai';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { TransactionReceiptWithDecodedLogs } from '../../../../node_modules/ethereum-types';
|
||||
|
||||
import { ERC20Wrapper } from './erc20_wrapper';
|
||||
import { ERC721Wrapper } from './erc721_wrapper';
|
||||
import { ExchangeWrapper } from './exchange_wrapper';
|
||||
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
|
||||
export class MatchOrderTester {
|
||||
private readonly _exchangeWrapper: ExchangeWrapper;
|
||||
private readonly _erc20Wrapper: ERC20Wrapper;
|
||||
private readonly _erc721Wrapper: ERC721Wrapper;
|
||||
private readonly _feeTokenAddress: string;
|
||||
/// @dev Checks values from the logs produced by Exchange.matchOrders against the expected transfer amounts.
|
||||
/// Values include the amounts transferred from the left/right makers and taker, along with
|
||||
/// the fees paid on each matched order. These are also the return values of MatchOrders.
|
||||
/// @param signedOrderLeft First matched order.
|
||||
/// @param signedOrderRight Second matched order.
|
||||
/// @param transactionReceipt Transaction receipt and logs produced by Exchange.matchOrders.
|
||||
/// @param takerAddress Address of taker (account that called Exchange.matchOrders)
|
||||
/// @param expectedTransferAmounts Expected amounts transferred as a result of order matching.
|
||||
private static async _assertLogsAsync(
|
||||
signedOrderLeft: SignedOrder,
|
||||
signedOrderRight: SignedOrder,
|
||||
transactionReceipt: TransactionReceiptWithDecodedLogs,
|
||||
takerAddress: string,
|
||||
expectedTransferAmounts: TransferAmounts,
|
||||
): Promise<void> {
|
||||
// Should have two fill event logs -- one for each order.
|
||||
const transactionFillLogs = _.filter(transactionReceipt.logs, ['event', 'Fill']);
|
||||
expect(transactionFillLogs.length, 'Checking number of logs').to.be.equal(2);
|
||||
// First log is for left fill
|
||||
const leftLog = (transactionFillLogs[0] as any).args as LoggedTransferAmounts;
|
||||
expect(leftLog.makerAddress, 'Checking logged maker address of left order').to.be.equal(
|
||||
signedOrderLeft.makerAddress,
|
||||
);
|
||||
expect(leftLog.takerAddress, 'Checking logged taker address of right order').to.be.equal(takerAddress);
|
||||
const amountBoughtByLeftMaker = new BigNumber(leftLog.takerAssetFilledAmount);
|
||||
const amountSoldByLeftMaker = new BigNumber(leftLog.makerAssetFilledAmount);
|
||||
const feePaidByLeftMaker = new BigNumber(leftLog.makerFeePaid);
|
||||
const feePaidByTakerLeft = new BigNumber(leftLog.takerFeePaid);
|
||||
// Second log is for right fill
|
||||
const rightLog = (transactionFillLogs[1] as any).args as LoggedTransferAmounts;
|
||||
expect(rightLog.makerAddress, 'Checking logged maker address of right order').to.be.equal(
|
||||
signedOrderRight.makerAddress,
|
||||
);
|
||||
expect(rightLog.takerAddress, 'Checking loggerd taker address of right order').to.be.equal(takerAddress);
|
||||
const amountBoughtByRightMaker = new BigNumber(rightLog.takerAssetFilledAmount);
|
||||
const amountSoldByRightMaker = new BigNumber(rightLog.makerAssetFilledAmount);
|
||||
const feePaidByRightMaker = new BigNumber(rightLog.makerFeePaid);
|
||||
const feePaidByTakerRight = new BigNumber(rightLog.takerFeePaid);
|
||||
// Derive amount received by taker
|
||||
const amountReceivedByTaker = amountSoldByLeftMaker.minus(amountBoughtByRightMaker);
|
||||
// Assert log values - left order
|
||||
expect(amountBoughtByLeftMaker, 'Checking logged amount bought by left maker').to.be.bignumber.equal(
|
||||
expectedTransferAmounts.amountBoughtByLeftMaker,
|
||||
);
|
||||
expect(amountSoldByLeftMaker, 'Checking logged amount sold by left maker').to.be.bignumber.equal(
|
||||
expectedTransferAmounts.amountSoldByLeftMaker,
|
||||
);
|
||||
expect(feePaidByLeftMaker, 'Checking logged fee paid by left maker').to.be.bignumber.equal(
|
||||
expectedTransferAmounts.feePaidByLeftMaker,
|
||||
);
|
||||
expect(feePaidByTakerLeft, 'Checking logged fee paid on left order by taker').to.be.bignumber.equal(
|
||||
expectedTransferAmounts.feePaidByTakerLeft,
|
||||
);
|
||||
// Assert log values - right order
|
||||
expect(amountBoughtByRightMaker, 'Checking logged amount bought by right maker').to.be.bignumber.equal(
|
||||
expectedTransferAmounts.amountBoughtByRightMaker,
|
||||
);
|
||||
expect(amountSoldByRightMaker, 'Checking logged amount sold by right maker').to.be.bignumber.equal(
|
||||
expectedTransferAmounts.amountSoldByRightMaker,
|
||||
);
|
||||
expect(feePaidByRightMaker, 'Checking logged fee paid by right maker').to.be.bignumber.equal(
|
||||
expectedTransferAmounts.feePaidByRightMaker,
|
||||
);
|
||||
expect(feePaidByTakerRight, 'Checking logged fee paid on right order by taker').to.be.bignumber.equal(
|
||||
expectedTransferAmounts.feePaidByTakerRight,
|
||||
);
|
||||
// Assert derived amount received by taker
|
||||
expect(amountReceivedByTaker, 'Checking logged amount received by taker').to.be.bignumber.equal(
|
||||
expectedTransferAmounts.amountReceivedByTaker,
|
||||
);
|
||||
}
|
||||
/// @dev Asserts all expected ERC20 and ERC721 account holdings match the real holdings.
|
||||
/// @param expectedERC20BalancesByOwner Expected ERC20 balances.
|
||||
/// @param realERC20BalancesByOwner Real ERC20 balances.
|
||||
/// @param expectedERC721TokenIdsByOwner Expected ERC721 token owners.
|
||||
/// @param realERC721TokenIdsByOwner Real ERC20 token owners.
|
||||
private static async _assertAllKnownBalancesAsync(
|
||||
expectedERC20BalancesByOwner: ERC20BalancesByOwner,
|
||||
realERC20BalancesByOwner: ERC20BalancesByOwner,
|
||||
expectedERC721TokenIdsByOwner: ERC721TokenIdsByOwner,
|
||||
realERC721TokenIdsByOwner: ERC721TokenIdsByOwner,
|
||||
): Promise<void> {
|
||||
// ERC20 Balances
|
||||
const areERC20BalancesEqual = _.isEqual(expectedERC20BalancesByOwner, realERC20BalancesByOwner);
|
||||
expect(areERC20BalancesEqual, 'Checking all known ERC20 account balances').to.be.true();
|
||||
// ERC721 Token Ids
|
||||
const sortedExpectedNewERC721TokenIdsByOwner = _.mapValues(expectedERC721TokenIdsByOwner, tokenIdsByOwner => {
|
||||
_.mapValues(tokenIdsByOwner, tokenIds => {
|
||||
_.sortBy(tokenIds);
|
||||
});
|
||||
});
|
||||
const sortedNewERC721TokenIdsByOwner = _.mapValues(realERC721TokenIdsByOwner, tokenIdsByOwner => {
|
||||
_.mapValues(tokenIdsByOwner, tokenIds => {
|
||||
_.sortBy(tokenIds);
|
||||
});
|
||||
});
|
||||
const areERC721TokenIdsEqual = _.isEqual(
|
||||
sortedExpectedNewERC721TokenIdsByOwner,
|
||||
sortedNewERC721TokenIdsByOwner,
|
||||
);
|
||||
expect(areERC721TokenIdsEqual, 'Checking all known ERC721 account balances').to.be.true();
|
||||
}
|
||||
/// @dev Constructs new MatchOrderTester.
|
||||
/// @param exchangeWrapper Used to call to the Exchange.
|
||||
/// @param erc20Wrapper Used to fetch ERC20 balances.
|
||||
/// @param erc721Wrapper Used to fetch ERC721 token owners.
|
||||
/// @param feeTokenAddress Address of ERC20 fee token.
|
||||
constructor(
|
||||
exchangeWrapper: ExchangeWrapper,
|
||||
erc20Wrapper: ERC20Wrapper,
|
||||
erc721Wrapper: ERC721Wrapper,
|
||||
feeTokenAddress: string,
|
||||
) {
|
||||
this._exchangeWrapper = exchangeWrapper;
|
||||
this._erc20Wrapper = erc20Wrapper;
|
||||
this._erc721Wrapper = erc721Wrapper;
|
||||
this._feeTokenAddress = feeTokenAddress;
|
||||
}
|
||||
/// @dev Matches two complementary orders and asserts results.
|
||||
/// @param signedOrderLeft First matched order.
|
||||
/// @param signedOrderRight Second matched order.
|
||||
/// @param takerAddress Address of taker (the address who matched the two orders)
|
||||
/// @param erc20BalancesByOwner Current ERC20 balances.
|
||||
/// @param erc721TokenIdsByOwner Current ERC721 token owners.
|
||||
/// @param expectedTransferAmounts Expected amounts transferred as a result of order matching.
|
||||
/// @param initialLeftOrderFilledAmount How much left order has been filled, prior to matching orders.
|
||||
/// @param initialRightOrderFilledAmount How much the right order has been filled, prior to matching orders.
|
||||
/// @return New ERC20 balances & ERC721 token owners.
|
||||
public async matchOrdersAndAssertEffectsAsync(
|
||||
signedOrderLeft: SignedOrder,
|
||||
signedOrderRight: SignedOrder,
|
||||
takerAddress: string,
|
||||
erc20BalancesByOwner: ERC20BalancesByOwner,
|
||||
erc721TokenIdsByOwner: ERC721TokenIdsByOwner,
|
||||
expectedTransferAmounts: TransferAmounts,
|
||||
initialLeftOrderFilledAmount: BigNumber = new BigNumber(0),
|
||||
initialRightOrderFilledAmount: BigNumber = new BigNumber(0),
|
||||
): Promise<[ERC20BalancesByOwner, ERC721TokenIdsByOwner]> {
|
||||
// Assert initial order states
|
||||
await this._assertInitialOrderStatesAsync(
|
||||
signedOrderLeft,
|
||||
signedOrderRight,
|
||||
initialLeftOrderFilledAmount,
|
||||
initialRightOrderFilledAmount,
|
||||
);
|
||||
// Match left & right orders
|
||||
const transactionReceipt = await this._exchangeWrapper.matchOrdersAsync(
|
||||
signedOrderLeft,
|
||||
signedOrderRight,
|
||||
takerAddress,
|
||||
);
|
||||
const newERC20BalancesByOwner = await this._erc20Wrapper.getBalancesAsync();
|
||||
const newERC721TokenIdsByOwner = await this._erc721Wrapper.getBalancesAsync();
|
||||
// Assert logs
|
||||
await MatchOrderTester._assertLogsAsync(
|
||||
signedOrderLeft,
|
||||
signedOrderRight,
|
||||
transactionReceipt,
|
||||
takerAddress,
|
||||
expectedTransferAmounts,
|
||||
);
|
||||
// Assert exchange state
|
||||
await this._assertExchangeStateAsync(
|
||||
signedOrderLeft,
|
||||
signedOrderRight,
|
||||
initialLeftOrderFilledAmount,
|
||||
initialRightOrderFilledAmount,
|
||||
expectedTransferAmounts,
|
||||
);
|
||||
// Assert balances of makers, taker, and fee recipients
|
||||
await this._assertBalancesAsync(
|
||||
signedOrderLeft,
|
||||
signedOrderRight,
|
||||
erc20BalancesByOwner,
|
||||
erc721TokenIdsByOwner,
|
||||
newERC20BalancesByOwner,
|
||||
newERC721TokenIdsByOwner,
|
||||
expectedTransferAmounts,
|
||||
takerAddress,
|
||||
);
|
||||
return [newERC20BalancesByOwner, newERC721TokenIdsByOwner];
|
||||
}
|
||||
/// @dev Asserts initial exchange state for the left and right orders.
|
||||
/// @param signedOrderLeft First matched order.
|
||||
/// @param signedOrderRight Second matched order.
|
||||
/// @param expectedOrderFilledAmountLeft How much left order has been filled, prior to matching orders.
|
||||
/// @param expectedOrderFilledAmountRight How much the right order has been filled, prior to matching orders.
|
||||
private async _assertInitialOrderStatesAsync(
|
||||
signedOrderLeft: SignedOrder,
|
||||
signedOrderRight: SignedOrder,
|
||||
expectedOrderFilledAmountLeft: BigNumber,
|
||||
expectedOrderFilledAmountRight: BigNumber,
|
||||
): Promise<void> {
|
||||
// Assert left order initial state
|
||||
const orderTakerAssetFilledAmountLeft = await this._exchangeWrapper.getTakerAssetFilledAmountAsync(
|
||||
orderHashUtils.getOrderHashHex(signedOrderLeft),
|
||||
);
|
||||
expect(orderTakerAssetFilledAmountLeft, 'Checking inital state of left order').to.be.bignumber.equal(
|
||||
expectedOrderFilledAmountLeft,
|
||||
);
|
||||
// Assert right order initial state
|
||||
const orderTakerAssetFilledAmountRight = await this._exchangeWrapper.getTakerAssetFilledAmountAsync(
|
||||
orderHashUtils.getOrderHashHex(signedOrderRight),
|
||||
);
|
||||
expect(orderTakerAssetFilledAmountRight, 'Checking inital state of right order').to.be.bignumber.equal(
|
||||
expectedOrderFilledAmountRight,
|
||||
);
|
||||
}
|
||||
/// @dev Asserts the exchange state against the expected amounts transferred by from matching orders.
|
||||
/// @param signedOrderLeft First matched order.
|
||||
/// @param signedOrderRight Second matched order.
|
||||
/// @param initialLeftOrderFilledAmount How much left order has been filled, prior to matching orders.
|
||||
/// @param initialRightOrderFilledAmount How much the right order has been filled, prior to matching orders.
|
||||
/// @return TransferAmounts A struct containing the expected transfer amounts.
|
||||
private async _assertExchangeStateAsync(
|
||||
signedOrderLeft: SignedOrder,
|
||||
signedOrderRight: SignedOrder,
|
||||
initialLeftOrderFilledAmount: BigNumber,
|
||||
initialRightOrderFilledAmount: BigNumber,
|
||||
expectedTransferAmounts: TransferAmounts,
|
||||
): Promise<void> {
|
||||
// Assert state for left order: amount bought by left maker
|
||||
let amountBoughtByLeftMaker = await this._exchangeWrapper.getTakerAssetFilledAmountAsync(
|
||||
orderHashUtils.getOrderHashHex(signedOrderLeft),
|
||||
);
|
||||
amountBoughtByLeftMaker = amountBoughtByLeftMaker.minus(initialLeftOrderFilledAmount);
|
||||
expect(amountBoughtByLeftMaker, 'Checking exchange state for left order').to.be.bignumber.equal(
|
||||
expectedTransferAmounts.amountBoughtByLeftMaker,
|
||||
);
|
||||
// Assert state for right order: amount bought by right maker
|
||||
let amountBoughtByRightMaker = await this._exchangeWrapper.getTakerAssetFilledAmountAsync(
|
||||
orderHashUtils.getOrderHashHex(signedOrderRight),
|
||||
);
|
||||
amountBoughtByRightMaker = amountBoughtByRightMaker.minus(initialRightOrderFilledAmount);
|
||||
expect(amountBoughtByRightMaker, 'Checking exchange state for right order').to.be.bignumber.equal(
|
||||
expectedTransferAmounts.amountBoughtByRightMaker,
|
||||
);
|
||||
// Assert left order status
|
||||
const maxAmountBoughtByLeftMaker = signedOrderLeft.takerAssetAmount.minus(initialLeftOrderFilledAmount);
|
||||
const leftOrderInfo: OrderInfo = await this._exchangeWrapper.getOrderInfoAsync(signedOrderLeft);
|
||||
const leftExpectedStatus = expectedTransferAmounts.amountBoughtByLeftMaker.isEqualTo(maxAmountBoughtByLeftMaker)
|
||||
? OrderStatus.FullyFilled
|
||||
: OrderStatus.Fillable;
|
||||
expect(leftOrderInfo.orderStatus, 'Checking exchange status for left order').to.be.equal(leftExpectedStatus);
|
||||
// Assert right order status
|
||||
const maxAmountBoughtByRightMaker = signedOrderRight.takerAssetAmount.minus(initialRightOrderFilledAmount);
|
||||
const rightOrderInfo: OrderInfo = await this._exchangeWrapper.getOrderInfoAsync(signedOrderRight);
|
||||
const rightExpectedStatus = expectedTransferAmounts.amountBoughtByRightMaker.isEqualTo(
|
||||
maxAmountBoughtByRightMaker,
|
||||
)
|
||||
? OrderStatus.FullyFilled
|
||||
: OrderStatus.Fillable;
|
||||
expect(rightOrderInfo.orderStatus, 'Checking exchange status for right order').to.be.equal(rightExpectedStatus);
|
||||
}
|
||||
/// @dev Asserts account balances after matching orders.
|
||||
/// @param signedOrderLeft First matched order.
|
||||
/// @param signedOrderRight Second matched order.
|
||||
/// @param initialERC20BalancesByOwner ERC20 balances prior to order matching.
|
||||
/// @param initialERC721TokenIdsByOwner ERC721 token owners prior to order matching.
|
||||
/// @param finalERC20BalancesByOwner ERC20 balances after order matching.
|
||||
/// @param finalERC721TokenIdsByOwner ERC721 token owners after order matching.
|
||||
/// @param expectedTransferAmounts Expected amounts transferred as a result of order matching.
|
||||
/// @param takerAddress Address of taker (account that called Exchange.matchOrders).
|
||||
private async _assertBalancesAsync(
|
||||
signedOrderLeft: SignedOrder,
|
||||
signedOrderRight: SignedOrder,
|
||||
initialERC20BalancesByOwner: ERC20BalancesByOwner,
|
||||
initialERC721TokenIdsByOwner: ERC721TokenIdsByOwner,
|
||||
finalERC20BalancesByOwner: ERC20BalancesByOwner,
|
||||
finalERC721TokenIdsByOwner: ERC721TokenIdsByOwner,
|
||||
expectedTransferAmounts: TransferAmounts,
|
||||
takerAddress: string,
|
||||
): Promise<void> {
|
||||
let expectedERC20BalancesByOwner: ERC20BalancesByOwner;
|
||||
let expectedERC721TokenIdsByOwner: ERC721TokenIdsByOwner;
|
||||
[expectedERC20BalancesByOwner, expectedERC721TokenIdsByOwner] = this._calculateExpectedBalances(
|
||||
signedOrderLeft,
|
||||
signedOrderRight,
|
||||
takerAddress,
|
||||
initialERC20BalancesByOwner,
|
||||
initialERC721TokenIdsByOwner,
|
||||
expectedTransferAmounts,
|
||||
);
|
||||
// Assert balances of makers, taker, and fee recipients
|
||||
await this._assertMakerTakerAndFeeRecipientBalancesAsync(
|
||||
signedOrderLeft,
|
||||
signedOrderRight,
|
||||
expectedERC20BalancesByOwner,
|
||||
finalERC20BalancesByOwner,
|
||||
expectedERC721TokenIdsByOwner,
|
||||
finalERC721TokenIdsByOwner,
|
||||
takerAddress,
|
||||
);
|
||||
// Assert balances for all known accounts
|
||||
await MatchOrderTester._assertAllKnownBalancesAsync(
|
||||
expectedERC20BalancesByOwner,
|
||||
finalERC20BalancesByOwner,
|
||||
expectedERC721TokenIdsByOwner,
|
||||
finalERC721TokenIdsByOwner,
|
||||
);
|
||||
}
|
||||
/// @dev Calculates the expected balances of order makers, fee recipients, and the taker,
|
||||
/// as a result of matching two orders.
|
||||
/// @param signedOrderRight First matched order.
|
||||
/// @param signedOrderRight Second matched order.
|
||||
/// @param takerAddress Address of taker (the address who matched the two orders)
|
||||
/// @param erc20BalancesByOwner Current ERC20 balances.
|
||||
/// @param erc721TokenIdsByOwner Current ERC721 token owners.
|
||||
/// @param expectedTransferAmounts Expected amounts transferred as a result of order matching.
|
||||
/// @return Expected ERC20 balances & ERC721 token owners after orders have been matched.
|
||||
private _calculateExpectedBalances(
|
||||
signedOrderLeft: SignedOrder,
|
||||
signedOrderRight: SignedOrder,
|
||||
takerAddress: string,
|
||||
erc20BalancesByOwner: ERC20BalancesByOwner,
|
||||
erc721TokenIdsByOwner: ERC721TokenIdsByOwner,
|
||||
expectedTransferAmounts: TransferAmounts,
|
||||
): [ERC20BalancesByOwner, ERC721TokenIdsByOwner] {
|
||||
const makerAddressLeft = signedOrderLeft.makerAddress;
|
||||
const makerAddressRight = signedOrderRight.makerAddress;
|
||||
const feeRecipientAddressLeft = signedOrderLeft.feeRecipientAddress;
|
||||
const feeRecipientAddressRight = signedOrderRight.feeRecipientAddress;
|
||||
// Operations are performed on copies of the balances
|
||||
const expectedNewERC20BalancesByOwner = _.cloneDeep(erc20BalancesByOwner);
|
||||
const expectedNewERC721TokenIdsByOwner = _.cloneDeep(erc721TokenIdsByOwner);
|
||||
// Left Maker Asset (Right Taker Asset)
|
||||
const makerAssetProxyIdLeft = assetDataUtils.decodeAssetProxyId(signedOrderLeft.makerAssetData);
|
||||
if (makerAssetProxyIdLeft === AssetProxyId.ERC20) {
|
||||
// Decode asset data
|
||||
const erc20AssetData = assetDataUtils.decodeERC20AssetData(signedOrderLeft.makerAssetData);
|
||||
const makerAssetAddressLeft = erc20AssetData.tokenAddress;
|
||||
const takerAssetAddressRight = makerAssetAddressLeft;
|
||||
// Left Maker
|
||||
expectedNewERC20BalancesByOwner[makerAddressLeft][makerAssetAddressLeft] = expectedNewERC20BalancesByOwner[
|
||||
makerAddressLeft
|
||||
][makerAssetAddressLeft].minus(expectedTransferAmounts.amountSoldByLeftMaker);
|
||||
// Right Maker
|
||||
expectedNewERC20BalancesByOwner[makerAddressRight][
|
||||
takerAssetAddressRight
|
||||
] = expectedNewERC20BalancesByOwner[makerAddressRight][takerAssetAddressRight].plus(
|
||||
expectedTransferAmounts.amountBoughtByRightMaker,
|
||||
);
|
||||
// Taker
|
||||
expectedNewERC20BalancesByOwner[takerAddress][makerAssetAddressLeft] = expectedNewERC20BalancesByOwner[
|
||||
takerAddress
|
||||
][makerAssetAddressLeft].plus(expectedTransferAmounts.amountReceivedByTaker);
|
||||
} else if (makerAssetProxyIdLeft === AssetProxyId.ERC721) {
|
||||
// Decode asset data
|
||||
const erc721AssetData = assetDataUtils.decodeERC721AssetData(signedOrderLeft.makerAssetData);
|
||||
const makerAssetAddressLeft = erc721AssetData.tokenAddress;
|
||||
const makerAssetIdLeft = erc721AssetData.tokenId;
|
||||
const takerAssetAddressRight = makerAssetAddressLeft;
|
||||
const takerAssetIdRight = makerAssetIdLeft;
|
||||
// Left Maker
|
||||
_.remove(expectedNewERC721TokenIdsByOwner[makerAddressLeft][makerAssetAddressLeft], makerAssetIdLeft);
|
||||
// Right Maker
|
||||
expectedNewERC721TokenIdsByOwner[makerAddressRight][takerAssetAddressRight].push(takerAssetIdRight);
|
||||
// Taker: Since there is only 1 asset transferred, the taker does not receive any of the left maker asset.
|
||||
}
|
||||
// Left Taker Asset (Right Maker Asset)
|
||||
// Note: This exchange is only between the order makers: the Taker does not receive any of the left taker asset.
|
||||
const takerAssetProxyIdLeft = assetDataUtils.decodeAssetProxyId(signedOrderLeft.takerAssetData);
|
||||
if (takerAssetProxyIdLeft === AssetProxyId.ERC20) {
|
||||
// Decode asset data
|
||||
const erc20AssetData = assetDataUtils.decodeERC20AssetData(signedOrderLeft.takerAssetData);
|
||||
const takerAssetAddressLeft = erc20AssetData.tokenAddress;
|
||||
const makerAssetAddressRight = takerAssetAddressLeft;
|
||||
// Left Maker
|
||||
expectedNewERC20BalancesByOwner[makerAddressLeft][takerAssetAddressLeft] = expectedNewERC20BalancesByOwner[
|
||||
makerAddressLeft
|
||||
][takerAssetAddressLeft].plus(expectedTransferAmounts.amountBoughtByLeftMaker);
|
||||
// Right Maker
|
||||
expectedNewERC20BalancesByOwner[makerAddressRight][
|
||||
makerAssetAddressRight
|
||||
] = expectedNewERC20BalancesByOwner[makerAddressRight][makerAssetAddressRight].minus(
|
||||
expectedTransferAmounts.amountSoldByRightMaker,
|
||||
);
|
||||
} else if (takerAssetProxyIdLeft === AssetProxyId.ERC721) {
|
||||
// Decode asset data
|
||||
const erc721AssetData = assetDataUtils.decodeERC721AssetData(signedOrderRight.makerAssetData);
|
||||
const makerAssetAddressRight = erc721AssetData.tokenAddress;
|
||||
const makerAssetIdRight = erc721AssetData.tokenId;
|
||||
const takerAssetAddressLeft = makerAssetAddressRight;
|
||||
const takerAssetIdLeft = makerAssetIdRight;
|
||||
// Right Maker
|
||||
_.remove(expectedNewERC721TokenIdsByOwner[makerAddressRight][makerAssetAddressRight], makerAssetIdRight);
|
||||
// Left Maker
|
||||
expectedNewERC721TokenIdsByOwner[makerAddressLeft][takerAssetAddressLeft].push(takerAssetIdLeft);
|
||||
}
|
||||
// Left Maker Fees
|
||||
expectedNewERC20BalancesByOwner[makerAddressLeft][this._feeTokenAddress] = expectedNewERC20BalancesByOwner[
|
||||
makerAddressLeft
|
||||
][this._feeTokenAddress].minus(expectedTransferAmounts.feePaidByLeftMaker);
|
||||
// Right Maker Fees
|
||||
expectedNewERC20BalancesByOwner[makerAddressRight][this._feeTokenAddress] = expectedNewERC20BalancesByOwner[
|
||||
makerAddressRight
|
||||
][this._feeTokenAddress].minus(expectedTransferAmounts.feePaidByRightMaker);
|
||||
// Taker Fees
|
||||
expectedNewERC20BalancesByOwner[takerAddress][this._feeTokenAddress] = expectedNewERC20BalancesByOwner[
|
||||
takerAddress
|
||||
][this._feeTokenAddress].minus(
|
||||
expectedTransferAmounts.feePaidByTakerLeft.plus(expectedTransferAmounts.feePaidByTakerRight),
|
||||
);
|
||||
// Left Fee Recipient Fees
|
||||
expectedNewERC20BalancesByOwner[feeRecipientAddressLeft][
|
||||
this._feeTokenAddress
|
||||
] = expectedNewERC20BalancesByOwner[feeRecipientAddressLeft][this._feeTokenAddress].plus(
|
||||
expectedTransferAmounts.feePaidByLeftMaker.plus(expectedTransferAmounts.feePaidByTakerLeft),
|
||||
);
|
||||
// Right Fee Recipient Fees
|
||||
expectedNewERC20BalancesByOwner[feeRecipientAddressRight][
|
||||
this._feeTokenAddress
|
||||
] = expectedNewERC20BalancesByOwner[feeRecipientAddressRight][this._feeTokenAddress].plus(
|
||||
expectedTransferAmounts.feePaidByRightMaker.plus(expectedTransferAmounts.feePaidByTakerRight),
|
||||
);
|
||||
|
||||
return [expectedNewERC20BalancesByOwner, expectedNewERC721TokenIdsByOwner];
|
||||
}
|
||||
/// @dev Asserts ERC20 account balances and ERC721 token holdings that result from order matching.
|
||||
/// Specifically checks balances of makers, taker and fee recipients.
|
||||
/// @param signedOrderLeft First matched order.
|
||||
/// @param signedOrderRight Second matched order.
|
||||
/// @param expectedERC20BalancesByOwner Expected ERC20 balances.
|
||||
/// @param realERC20BalancesByOwner Real ERC20 balances.
|
||||
/// @param expectedERC721TokenIdsByOwner Expected ERC721 token owners.
|
||||
/// @param realERC721TokenIdsByOwner Real ERC20 token owners.
|
||||
/// @param takerAddress Address of taker (account that called Exchange.matchOrders).
|
||||
private async _assertMakerTakerAndFeeRecipientBalancesAsync(
|
||||
signedOrderLeft: SignedOrder,
|
||||
signedOrderRight: SignedOrder,
|
||||
expectedERC20BalancesByOwner: ERC20BalancesByOwner,
|
||||
realERC20BalancesByOwner: ERC20BalancesByOwner,
|
||||
expectedERC721TokenIdsByOwner: ERC721TokenIdsByOwner,
|
||||
realERC721TokenIdsByOwner: ERC721TokenIdsByOwner,
|
||||
takerAddress: string,
|
||||
): Promise<void> {
|
||||
// Individual balance comparisons
|
||||
const makerAssetProxyIdLeft = assetDataUtils.decodeAssetProxyId(signedOrderLeft.makerAssetData);
|
||||
const makerERC20AssetDataLeft =
|
||||
makerAssetProxyIdLeft === AssetProxyId.ERC20
|
||||
? assetDataUtils.decodeERC20AssetData(signedOrderLeft.makerAssetData)
|
||||
: assetDataUtils.decodeERC721AssetData(signedOrderLeft.makerAssetData);
|
||||
const makerAssetAddressLeft = makerERC20AssetDataLeft.tokenAddress;
|
||||
const makerAssetProxyIdRight = assetDataUtils.decodeAssetProxyId(signedOrderRight.makerAssetData);
|
||||
const makerERC20AssetDataRight =
|
||||
makerAssetProxyIdRight === AssetProxyId.ERC20
|
||||
? assetDataUtils.decodeERC20AssetData(signedOrderRight.makerAssetData)
|
||||
: assetDataUtils.decodeERC721AssetData(signedOrderRight.makerAssetData);
|
||||
const makerAssetAddressRight = makerERC20AssetDataRight.tokenAddress;
|
||||
if (makerAssetProxyIdLeft === AssetProxyId.ERC20) {
|
||||
expect(
|
||||
realERC20BalancesByOwner[signedOrderLeft.makerAddress][makerAssetAddressLeft],
|
||||
'Checking left maker egress ERC20 account balance',
|
||||
).to.be.bignumber.equal(expectedERC20BalancesByOwner[signedOrderLeft.makerAddress][makerAssetAddressLeft]);
|
||||
expect(
|
||||
realERC20BalancesByOwner[signedOrderRight.makerAddress][makerAssetAddressLeft],
|
||||
'Checking right maker ingress ERC20 account balance',
|
||||
).to.be.bignumber.equal(expectedERC20BalancesByOwner[signedOrderRight.makerAddress][makerAssetAddressLeft]);
|
||||
expect(
|
||||
realERC20BalancesByOwner[takerAddress][makerAssetAddressLeft],
|
||||
'Checking taker ingress ERC20 account balance',
|
||||
).to.be.bignumber.equal(expectedERC20BalancesByOwner[takerAddress][makerAssetAddressLeft]);
|
||||
} else if (makerAssetProxyIdLeft === AssetProxyId.ERC721) {
|
||||
expect(
|
||||
realERC721TokenIdsByOwner[signedOrderLeft.makerAddress][makerAssetAddressLeft].sort(),
|
||||
'Checking left maker egress ERC721 account holdings',
|
||||
).to.be.deep.equal(
|
||||
expectedERC721TokenIdsByOwner[signedOrderLeft.makerAddress][makerAssetAddressLeft].sort(),
|
||||
);
|
||||
expect(
|
||||
realERC721TokenIdsByOwner[signedOrderRight.makerAddress][makerAssetAddressLeft].sort(),
|
||||
'Checking right maker ERC721 account holdings',
|
||||
).to.be.deep.equal(
|
||||
expectedERC721TokenIdsByOwner[signedOrderRight.makerAddress][makerAssetAddressLeft].sort(),
|
||||
);
|
||||
expect(
|
||||
realERC721TokenIdsByOwner[takerAddress][makerAssetAddressLeft].sort(),
|
||||
'Checking taker ingress ERC721 account holdings',
|
||||
).to.be.deep.equal(expectedERC721TokenIdsByOwner[takerAddress][makerAssetAddressLeft].sort());
|
||||
} else {
|
||||
throw new Error(`Unhandled Asset Proxy ID: ${makerAssetProxyIdLeft}`);
|
||||
}
|
||||
if (makerAssetProxyIdRight === AssetProxyId.ERC20) {
|
||||
expect(
|
||||
realERC20BalancesByOwner[signedOrderLeft.makerAddress][makerAssetAddressRight],
|
||||
'Checking left maker ingress ERC20 account balance',
|
||||
).to.be.bignumber.equal(expectedERC20BalancesByOwner[signedOrderLeft.makerAddress][makerAssetAddressRight]);
|
||||
expect(
|
||||
realERC20BalancesByOwner[signedOrderRight.makerAddress][makerAssetAddressRight],
|
||||
'Checking right maker egress ERC20 account balance',
|
||||
).to.be.bignumber.equal(
|
||||
expectedERC20BalancesByOwner[signedOrderRight.makerAddress][makerAssetAddressRight],
|
||||
);
|
||||
} else if (makerAssetProxyIdRight === AssetProxyId.ERC721) {
|
||||
expect(
|
||||
realERC721TokenIdsByOwner[signedOrderLeft.makerAddress][makerAssetAddressRight].sort(),
|
||||
'Checking left maker ingress ERC721 account holdings',
|
||||
).to.be.deep.equal(
|
||||
expectedERC721TokenIdsByOwner[signedOrderLeft.makerAddress][makerAssetAddressRight].sort(),
|
||||
);
|
||||
expect(
|
||||
realERC721TokenIdsByOwner[signedOrderRight.makerAddress][makerAssetAddressRight],
|
||||
'Checking right maker agress ERC721 account holdings',
|
||||
).to.be.deep.equal(expectedERC721TokenIdsByOwner[signedOrderRight.makerAddress][makerAssetAddressRight]);
|
||||
} else {
|
||||
throw new Error(`Unhandled Asset Proxy ID: ${makerAssetProxyIdRight}`);
|
||||
}
|
||||
// Paid fees
|
||||
expect(
|
||||
realERC20BalancesByOwner[signedOrderLeft.makerAddress][this._feeTokenAddress],
|
||||
'Checking left maker egress ERC20 account fees',
|
||||
).to.be.bignumber.equal(expectedERC20BalancesByOwner[signedOrderLeft.makerAddress][this._feeTokenAddress]);
|
||||
expect(
|
||||
realERC20BalancesByOwner[signedOrderRight.makerAddress][this._feeTokenAddress],
|
||||
'Checking right maker egress ERC20 account fees',
|
||||
).to.be.bignumber.equal(expectedERC20BalancesByOwner[signedOrderRight.makerAddress][this._feeTokenAddress]);
|
||||
expect(
|
||||
realERC20BalancesByOwner[takerAddress][this._feeTokenAddress],
|
||||
'Checking taker egress ERC20 account fees',
|
||||
).to.be.bignumber.equal(expectedERC20BalancesByOwner[takerAddress][this._feeTokenAddress]);
|
||||
// Received fees
|
||||
expect(
|
||||
realERC20BalancesByOwner[signedOrderLeft.feeRecipientAddress][this._feeTokenAddress],
|
||||
'Checking left fee recipient ingress ERC20 account fees',
|
||||
).to.be.bignumber.equal(
|
||||
expectedERC20BalancesByOwner[signedOrderLeft.feeRecipientAddress][this._feeTokenAddress],
|
||||
);
|
||||
expect(
|
||||
realERC20BalancesByOwner[signedOrderRight.feeRecipientAddress][this._feeTokenAddress],
|
||||
'Checking right fee receipient ingress ERC20 account fees',
|
||||
).to.be.bignumber.equal(
|
||||
expectedERC20BalancesByOwner[signedOrderRight.feeRecipientAddress][this._feeTokenAddress],
|
||||
);
|
||||
}
|
||||
} // tslint:disable-line:max-file-line-count
|
294
contracts/exchange/test/utils/order_factory_from_scenario.ts
Normal file
294
contracts/exchange/test/utils/order_factory_from_scenario.ts
Normal file
@@ -0,0 +1,294 @@
|
||||
import {
|
||||
AssetDataScenario,
|
||||
constants,
|
||||
ERC721TokenIdsByOwner,
|
||||
ExpirationTimeSecondsScenario,
|
||||
FeeRecipientAddressScenario,
|
||||
OrderAssetAmountScenario,
|
||||
OrderScenario,
|
||||
TakerScenario,
|
||||
} from '@0x/contracts-test-utils';
|
||||
import { DummyERC721TokenContract } from '@0x/contracts-tokens';
|
||||
import { assetDataUtils, generatePseudoRandomSalt } from '@0x/order-utils';
|
||||
import { Order } from '@0x/types';
|
||||
import { BigNumber, errorUtils } from '@0x/utils';
|
||||
|
||||
const TEN_UNITS_EIGHTEEN_DECIMALS = new BigNumber(10_000_000_000_000_000_000);
|
||||
const FIVE_UNITS_EIGHTEEN_DECIMALS = new BigNumber(5_000_000_000_000_000_000);
|
||||
const POINT_ONE_UNITS_EIGHTEEN_DECIMALS = new BigNumber(100_000_000_000_000_000);
|
||||
const POINT_ZERO_FIVE_UNITS_EIGHTEEN_DECIMALS = new BigNumber(50_000_000_000_000_000);
|
||||
const TEN_UNITS_FIVE_DECIMALS = new BigNumber(1_000_000);
|
||||
const FIVE_UNITS_FIVE_DECIMALS = new BigNumber(500_000);
|
||||
const TEN_UNITS_ZERO_DECIMALS = new BigNumber(10);
|
||||
const ONE_THOUSAND_UNITS_ZERO_DECIMALS = new BigNumber(1000);
|
||||
const ONE_NFT_UNIT = new BigNumber(1);
|
||||
|
||||
export class OrderFactoryFromScenario {
|
||||
private readonly _userAddresses: string[];
|
||||
private readonly _zrxAddress: string;
|
||||
private readonly _nonZrxERC20EighteenDecimalTokenAddresses: string[];
|
||||
private readonly _erc20FiveDecimalTokenAddresses: string[];
|
||||
private readonly _erc20ZeroDecimalTokenAddresses: string[];
|
||||
private readonly _erc721Token: DummyERC721TokenContract;
|
||||
private readonly _erc721Balances: ERC721TokenIdsByOwner;
|
||||
private readonly _exchangeAddress: string;
|
||||
constructor(
|
||||
userAddresses: string[],
|
||||
zrxAddress: string,
|
||||
nonZrxERC20EighteenDecimalTokenAddresses: string[],
|
||||
erc20FiveDecimalTokenAddresses: string[],
|
||||
erc20ZeroDecimalTokenAddresses: string[],
|
||||
erc721Token: DummyERC721TokenContract,
|
||||
erc721Balances: ERC721TokenIdsByOwner,
|
||||
exchangeAddress: string,
|
||||
) {
|
||||
this._userAddresses = userAddresses;
|
||||
this._zrxAddress = zrxAddress;
|
||||
this._nonZrxERC20EighteenDecimalTokenAddresses = nonZrxERC20EighteenDecimalTokenAddresses;
|
||||
this._erc20FiveDecimalTokenAddresses = erc20FiveDecimalTokenAddresses;
|
||||
this._erc20ZeroDecimalTokenAddresses = erc20ZeroDecimalTokenAddresses;
|
||||
this._erc721Token = erc721Token;
|
||||
this._erc721Balances = erc721Balances;
|
||||
this._exchangeAddress = exchangeAddress;
|
||||
}
|
||||
public generateOrder(orderScenario: OrderScenario): Order {
|
||||
const makerAddress = this._userAddresses[1];
|
||||
let takerAddress = this._userAddresses[2];
|
||||
const erc721MakerAssetIds = this._erc721Balances[makerAddress][this._erc721Token.address];
|
||||
const erc721TakerAssetIds = this._erc721Balances[takerAddress][this._erc721Token.address];
|
||||
let feeRecipientAddress;
|
||||
let makerAssetAmount;
|
||||
let takerAssetAmount;
|
||||
let makerFee;
|
||||
let takerFee;
|
||||
let expirationTimeSeconds;
|
||||
let makerAssetData;
|
||||
let takerAssetData;
|
||||
|
||||
switch (orderScenario.feeRecipientScenario) {
|
||||
case FeeRecipientAddressScenario.BurnAddress:
|
||||
feeRecipientAddress = constants.NULL_ADDRESS;
|
||||
break;
|
||||
case FeeRecipientAddressScenario.EthUserAddress:
|
||||
feeRecipientAddress = this._userAddresses[4];
|
||||
break;
|
||||
default:
|
||||
throw errorUtils.spawnSwitchErr('FeeRecipientAddressScenario', orderScenario.feeRecipientScenario);
|
||||
}
|
||||
|
||||
switch (orderScenario.makerAssetDataScenario) {
|
||||
case AssetDataScenario.ZRXFeeToken:
|
||||
makerAssetData = assetDataUtils.encodeERC20AssetData(this._zrxAddress);
|
||||
break;
|
||||
case AssetDataScenario.ERC20NonZRXEighteenDecimals:
|
||||
makerAssetData = assetDataUtils.encodeERC20AssetData(this._nonZrxERC20EighteenDecimalTokenAddresses[0]);
|
||||
break;
|
||||
case AssetDataScenario.ERC20FiveDecimals:
|
||||
makerAssetData = assetDataUtils.encodeERC20AssetData(this._erc20FiveDecimalTokenAddresses[0]);
|
||||
break;
|
||||
case AssetDataScenario.ERC721:
|
||||
makerAssetData = assetDataUtils.encodeERC721AssetData(
|
||||
this._erc721Token.address,
|
||||
erc721MakerAssetIds[0],
|
||||
);
|
||||
break;
|
||||
case AssetDataScenario.ERC20ZeroDecimals:
|
||||
makerAssetData = assetDataUtils.encodeERC20AssetData(this._erc20ZeroDecimalTokenAddresses[0]);
|
||||
break;
|
||||
default:
|
||||
throw errorUtils.spawnSwitchErr('AssetDataScenario', orderScenario.makerAssetDataScenario);
|
||||
}
|
||||
|
||||
switch (orderScenario.takerAssetDataScenario) {
|
||||
case AssetDataScenario.ZRXFeeToken:
|
||||
takerAssetData = assetDataUtils.encodeERC20AssetData(this._zrxAddress);
|
||||
break;
|
||||
case AssetDataScenario.ERC20NonZRXEighteenDecimals:
|
||||
takerAssetData = assetDataUtils.encodeERC20AssetData(this._nonZrxERC20EighteenDecimalTokenAddresses[1]);
|
||||
break;
|
||||
case AssetDataScenario.ERC20FiveDecimals:
|
||||
takerAssetData = assetDataUtils.encodeERC20AssetData(this._erc20FiveDecimalTokenAddresses[1]);
|
||||
break;
|
||||
case AssetDataScenario.ERC721:
|
||||
takerAssetData = assetDataUtils.encodeERC721AssetData(
|
||||
this._erc721Token.address,
|
||||
erc721TakerAssetIds[0],
|
||||
);
|
||||
break;
|
||||
case AssetDataScenario.ERC20ZeroDecimals:
|
||||
takerAssetData = assetDataUtils.encodeERC20AssetData(this._erc20ZeroDecimalTokenAddresses[1]);
|
||||
break;
|
||||
default:
|
||||
throw errorUtils.spawnSwitchErr('AssetDataScenario', orderScenario.takerAssetDataScenario);
|
||||
}
|
||||
|
||||
switch (orderScenario.makerAssetAmountScenario) {
|
||||
case OrderAssetAmountScenario.Large:
|
||||
switch (orderScenario.makerAssetDataScenario) {
|
||||
case AssetDataScenario.ZRXFeeToken:
|
||||
case AssetDataScenario.ERC20NonZRXEighteenDecimals:
|
||||
makerAssetAmount = TEN_UNITS_EIGHTEEN_DECIMALS;
|
||||
break;
|
||||
case AssetDataScenario.ERC20FiveDecimals:
|
||||
makerAssetAmount = TEN_UNITS_FIVE_DECIMALS;
|
||||
break;
|
||||
case AssetDataScenario.ERC721:
|
||||
makerAssetAmount = ONE_NFT_UNIT;
|
||||
break;
|
||||
case AssetDataScenario.ERC20ZeroDecimals:
|
||||
makerAssetAmount = ONE_THOUSAND_UNITS_ZERO_DECIMALS;
|
||||
break;
|
||||
default:
|
||||
throw errorUtils.spawnSwitchErr('AssetDataScenario', orderScenario.makerAssetDataScenario);
|
||||
}
|
||||
break;
|
||||
case OrderAssetAmountScenario.Small:
|
||||
switch (orderScenario.makerAssetDataScenario) {
|
||||
case AssetDataScenario.ZRXFeeToken:
|
||||
case AssetDataScenario.ERC20NonZRXEighteenDecimals:
|
||||
makerAssetAmount = FIVE_UNITS_EIGHTEEN_DECIMALS;
|
||||
break;
|
||||
case AssetDataScenario.ERC20FiveDecimals:
|
||||
makerAssetAmount = FIVE_UNITS_FIVE_DECIMALS;
|
||||
break;
|
||||
case AssetDataScenario.ERC721:
|
||||
makerAssetAmount = ONE_NFT_UNIT;
|
||||
break;
|
||||
case AssetDataScenario.ERC20ZeroDecimals:
|
||||
makerAssetAmount = TEN_UNITS_ZERO_DECIMALS;
|
||||
break;
|
||||
default:
|
||||
throw errorUtils.spawnSwitchErr('AssetDataScenario', orderScenario.makerAssetDataScenario);
|
||||
}
|
||||
break;
|
||||
case OrderAssetAmountScenario.Zero:
|
||||
makerAssetAmount = new BigNumber(0);
|
||||
break;
|
||||
default:
|
||||
throw errorUtils.spawnSwitchErr('OrderAssetAmountScenario', orderScenario.makerAssetAmountScenario);
|
||||
}
|
||||
|
||||
switch (orderScenario.takerAssetAmountScenario) {
|
||||
case OrderAssetAmountScenario.Large:
|
||||
switch (orderScenario.takerAssetDataScenario) {
|
||||
case AssetDataScenario.ERC20NonZRXEighteenDecimals:
|
||||
case AssetDataScenario.ZRXFeeToken:
|
||||
takerAssetAmount = TEN_UNITS_EIGHTEEN_DECIMALS;
|
||||
break;
|
||||
case AssetDataScenario.ERC20FiveDecimals:
|
||||
takerAssetAmount = TEN_UNITS_FIVE_DECIMALS;
|
||||
break;
|
||||
case AssetDataScenario.ERC721:
|
||||
takerAssetAmount = ONE_NFT_UNIT;
|
||||
break;
|
||||
case AssetDataScenario.ERC20ZeroDecimals:
|
||||
takerAssetAmount = ONE_THOUSAND_UNITS_ZERO_DECIMALS;
|
||||
break;
|
||||
default:
|
||||
throw errorUtils.spawnSwitchErr('AssetDataScenario', orderScenario.takerAssetDataScenario);
|
||||
}
|
||||
break;
|
||||
case OrderAssetAmountScenario.Small:
|
||||
switch (orderScenario.takerAssetDataScenario) {
|
||||
case AssetDataScenario.ERC20NonZRXEighteenDecimals:
|
||||
case AssetDataScenario.ZRXFeeToken:
|
||||
takerAssetAmount = FIVE_UNITS_EIGHTEEN_DECIMALS;
|
||||
break;
|
||||
case AssetDataScenario.ERC20FiveDecimals:
|
||||
takerAssetAmount = FIVE_UNITS_FIVE_DECIMALS;
|
||||
break;
|
||||
case AssetDataScenario.ERC721:
|
||||
takerAssetAmount = ONE_NFT_UNIT;
|
||||
break;
|
||||
case AssetDataScenario.ERC20ZeroDecimals:
|
||||
takerAssetAmount = TEN_UNITS_ZERO_DECIMALS;
|
||||
break;
|
||||
default:
|
||||
throw errorUtils.spawnSwitchErr('AssetDataScenario', orderScenario.takerAssetDataScenario);
|
||||
}
|
||||
break;
|
||||
case OrderAssetAmountScenario.Zero:
|
||||
takerAssetAmount = new BigNumber(0);
|
||||
break;
|
||||
default:
|
||||
throw errorUtils.spawnSwitchErr('OrderAssetAmountScenario', orderScenario.takerAssetAmountScenario);
|
||||
}
|
||||
|
||||
switch (orderScenario.makerFeeScenario) {
|
||||
case OrderAssetAmountScenario.Large:
|
||||
makerFee = POINT_ONE_UNITS_EIGHTEEN_DECIMALS;
|
||||
break;
|
||||
case OrderAssetAmountScenario.Small:
|
||||
makerFee = POINT_ZERO_FIVE_UNITS_EIGHTEEN_DECIMALS;
|
||||
break;
|
||||
case OrderAssetAmountScenario.Zero:
|
||||
makerFee = new BigNumber(0);
|
||||
break;
|
||||
default:
|
||||
throw errorUtils.spawnSwitchErr('OrderAssetAmountScenario', orderScenario.makerFeeScenario);
|
||||
}
|
||||
|
||||
switch (orderScenario.takerFeeScenario) {
|
||||
case OrderAssetAmountScenario.Large:
|
||||
takerFee = POINT_ONE_UNITS_EIGHTEEN_DECIMALS;
|
||||
break;
|
||||
case OrderAssetAmountScenario.Small:
|
||||
takerFee = POINT_ZERO_FIVE_UNITS_EIGHTEEN_DECIMALS;
|
||||
break;
|
||||
case OrderAssetAmountScenario.Zero:
|
||||
takerFee = new BigNumber(0);
|
||||
break;
|
||||
default:
|
||||
throw errorUtils.spawnSwitchErr('OrderAssetAmountScenario', orderScenario.takerFeeScenario);
|
||||
}
|
||||
|
||||
switch (orderScenario.expirationTimeSecondsScenario) {
|
||||
case ExpirationTimeSecondsScenario.InFuture:
|
||||
expirationTimeSeconds = new BigNumber(2524604400); // Close to infinite
|
||||
break;
|
||||
case ExpirationTimeSecondsScenario.InPast:
|
||||
expirationTimeSeconds = new BigNumber(0); // Jan 1, 1970
|
||||
break;
|
||||
default:
|
||||
throw errorUtils.spawnSwitchErr(
|
||||
'ExpirationTimeSecondsScenario',
|
||||
orderScenario.expirationTimeSecondsScenario,
|
||||
);
|
||||
}
|
||||
|
||||
switch (orderScenario.takerScenario) {
|
||||
case TakerScenario.CorrectlySpecified:
|
||||
break; // noop since takerAddress is already specified
|
||||
|
||||
case TakerScenario.IncorrectlySpecified:
|
||||
const notTaker = this._userAddresses[3];
|
||||
takerAddress = notTaker;
|
||||
break;
|
||||
|
||||
case TakerScenario.Unspecified:
|
||||
takerAddress = constants.NULL_ADDRESS;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw errorUtils.spawnSwitchErr('TakerScenario', orderScenario.takerScenario);
|
||||
}
|
||||
|
||||
const order = {
|
||||
senderAddress: constants.NULL_ADDRESS,
|
||||
makerAddress,
|
||||
takerAddress,
|
||||
makerFee,
|
||||
takerFee,
|
||||
makerAssetAmount,
|
||||
takerAssetAmount,
|
||||
makerAssetData,
|
||||
takerAssetData,
|
||||
salt: generatePseudoRandomSalt(),
|
||||
exchangeAddress: this._exchangeAddress,
|
||||
feeRecipientAddress,
|
||||
expirationTimeSeconds,
|
||||
};
|
||||
|
||||
return order;
|
||||
}
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
import { AbstractBalanceAndProxyAllowanceFetcher } from '@0x/order-utils';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
|
||||
import { AssetWrapper } from './asset_wrapper';
|
||||
|
||||
export class SimpleAssetBalanceAndProxyAllowanceFetcher implements AbstractBalanceAndProxyAllowanceFetcher {
|
||||
private readonly _assetWrapper: AssetWrapper;
|
||||
constructor(assetWrapper: AssetWrapper) {
|
||||
this._assetWrapper = assetWrapper;
|
||||
}
|
||||
public async getBalanceAsync(assetData: string, userAddress: string): Promise<BigNumber> {
|
||||
const balance = await this._assetWrapper.getBalanceAsync(userAddress, assetData);
|
||||
return balance;
|
||||
}
|
||||
public async getProxyAllowanceAsync(assetData: string, userAddress: string): Promise<BigNumber> {
|
||||
const proxyAllowance = await this._assetWrapper.getProxyAllowanceAsync(userAddress, assetData);
|
||||
return proxyAllowance;
|
||||
}
|
||||
}
|
@@ -0,0 +1,31 @@
|
||||
import { AbstractOrderFilledCancelledFetcher, orderHashUtils } from '@0x/order-utils';
|
||||
import { SignedOrder } from '@0x/types';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
|
||||
import { ExchangeWrapper } from './exchange_wrapper';
|
||||
|
||||
export class SimpleOrderFilledCancelledFetcher implements AbstractOrderFilledCancelledFetcher {
|
||||
private readonly _exchangeWrapper: ExchangeWrapper;
|
||||
private readonly _zrxAssetData: string;
|
||||
constructor(exchange: ExchangeWrapper, zrxAssetData: string) {
|
||||
this._exchangeWrapper = exchange;
|
||||
this._zrxAssetData = zrxAssetData;
|
||||
}
|
||||
public async getFilledTakerAmountAsync(orderHash: string): Promise<BigNumber> {
|
||||
const filledTakerAmount = new BigNumber(await this._exchangeWrapper.getTakerAssetFilledAmountAsync(orderHash));
|
||||
return filledTakerAmount;
|
||||
}
|
||||
public async isOrderCancelledAsync(signedOrder: SignedOrder): Promise<boolean> {
|
||||
const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
|
||||
const isCancelled = await this._exchangeWrapper.isCancelledAsync(orderHash);
|
||||
const orderEpoch = await this._exchangeWrapper.getOrderEpochAsync(
|
||||
signedOrder.makerAddress,
|
||||
signedOrder.senderAddress,
|
||||
);
|
||||
const isCancelledByOrderEpoch = orderEpoch > signedOrder.salt;
|
||||
return isCancelled || isCancelledByOrderEpoch;
|
||||
}
|
||||
public getZRXAssetData(): string {
|
||||
return this._zrxAssetData;
|
||||
}
|
||||
}
|
8
contracts/exchange/test/utils/types.ts
Normal file
8
contracts/exchange/test/utils/types.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { SignedOrder } from '@0x/types';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
|
||||
export interface AbiDecodedFillOrderData {
|
||||
order: SignedOrder;
|
||||
takerAssetFillAmount: BigNumber;
|
||||
signature: string;
|
||||
}
|
Reference in New Issue
Block a user