Merge branch 'v2-prototype' of https://github.com/0xProject/0x-monorepo into feature/website/portal-onboarding-polish
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
export abstract class AbstractAssetWrapper {
|
||||
public abstract getProxyId(): string;
|
||||
}
|
||||
@@ -56,4 +56,20 @@ contract DummyERC721Token is
|
||||
);
|
||||
_mint(to, tokenId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Function to burn a token
|
||||
* @dev Reverts if the given token ID doesn't exist
|
||||
* @param tokenId uint256 ID of the token to be minted by the msg.sender
|
||||
*/
|
||||
function burn(address owner, uint256 tokenId)
|
||||
public
|
||||
onlyOwner
|
||||
{
|
||||
require(
|
||||
exists(tokenId),
|
||||
"Token with tokenId does not exist."
|
||||
);
|
||||
_burn(owner, tokenId);
|
||||
}
|
||||
}
|
||||
|
||||
217
packages/contracts/src/utils/asset_wrapper.ts
Normal file
217
packages/contracts/src/utils/asset_wrapper.ts
Normal file
@@ -0,0 +1,217 @@
|
||||
import { assetProxyUtils } from '@0xproject/order-utils';
|
||||
import { AssetProxyId } from '@0xproject/types';
|
||||
import { BigNumber, errorUtils } from '@0xproject/utils';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { AbstractAssetWrapper } from '../abstract/abstract_asset_wrapper';
|
||||
|
||||
import { constants } from './constants';
|
||||
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 _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 = assetProxyUtils.decodeAssetDataId(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 = assetProxyUtils.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 = assetProxyUtils.decodeAssetDataId(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 = assetProxyUtils.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)) {
|
||||
// Burn token
|
||||
await erc721Wrapper.burnAsync(assetProxyData.tokenAddress, assetProxyData.tokenId, userAddress);
|
||||
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 = assetProxyUtils.decodeAssetDataId(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 = assetProxyUtils.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 = assetProxyUtils.decodeAssetDataId(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 = assetProxyUtils.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
801
packages/contracts/src/utils/core_combinatorial_utils.ts
Normal file
801
packages/contracts/src/utils/core_combinatorial_utils.ts
Normal file
@@ -0,0 +1,801 @@
|
||||
import {
|
||||
assetProxyUtils,
|
||||
BalanceAndProxyAllowanceLazyStore,
|
||||
ExchangeTransferSimulator,
|
||||
orderHashUtils,
|
||||
OrderStateUtils,
|
||||
OrderValidationUtils,
|
||||
} from '@0xproject/order-utils';
|
||||
import { AssetProxyId, RevertReason, SignatureType, SignedOrder } from '@0xproject/types';
|
||||
import { BigNumber, errorUtils, logUtils } from '@0xproject/utils';
|
||||
import { Web3Wrapper } from '@0xproject/web3-wrapper';
|
||||
import * as chai from 'chai';
|
||||
import { LogWithDecodedArgs, Provider, TxData } from 'ethereum-types';
|
||||
import * as _ from 'lodash';
|
||||
import 'make-promises-safe';
|
||||
|
||||
import { ExchangeContract, FillContractEventArgs } from '../generated_contract_wrappers/exchange';
|
||||
import { artifacts } from '../utils/artifacts';
|
||||
import { expectRevertReasonOrAlwaysFailingTransactionAsync } from '../utils/assertions';
|
||||
import { AssetWrapper } from '../utils/asset_wrapper';
|
||||
import { chaiSetup } from '../utils/chai_setup';
|
||||
import { constants } from '../utils/constants';
|
||||
import { ERC20Wrapper } from '../utils/erc20_wrapper';
|
||||
import { ERC721Wrapper } from '../utils/erc721_wrapper';
|
||||
import { ExchangeWrapper } from '../utils/exchange_wrapper';
|
||||
import { OrderFactoryFromScenario } from '../utils/order_factory_from_scenario';
|
||||
import { orderUtils } from '../utils/order_utils';
|
||||
import { signingUtils } from '../utils/signing_utils';
|
||||
import { SimpleAssetBalanceAndProxyAllowanceFetcher } from '../utils/simple_asset_balance_and_proxy_allowance_fetcher';
|
||||
import { SimpleOrderFilledCancelledFetcher } from '../utils/simple_order_filled_cancelled_fetcher';
|
||||
import {
|
||||
AllowanceAmountScenario,
|
||||
AssetDataScenario,
|
||||
BalanceAmountScenario,
|
||||
ExpirationTimeSecondsScenario,
|
||||
FeeRecipientAddressScenario,
|
||||
FillScenario,
|
||||
OrderAssetAmountScenario,
|
||||
TakerAssetFillAmountScenario,
|
||||
TakerScenario,
|
||||
TraderStateScenario,
|
||||
} from '../utils/types';
|
||||
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
|
||||
/**
|
||||
* Instantiates a new instance of CoreCombinatorialUtils. Since this method has some
|
||||
* required async setup, a factory method is required.
|
||||
* @param web3Wrapper Web3Wrapper instance
|
||||
* @param txDefaults Default Ethereum tx options
|
||||
* @return CoreCombinatorialUtils instance
|
||||
*/
|
||||
export async function coreCombinatorialUtilsFactoryAsync(
|
||||
web3Wrapper: Web3Wrapper,
|
||||
txDefaults: Partial<TxData>,
|
||||
): Promise<CoreCombinatorialUtils> {
|
||||
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 = assetProxyUtils.encodeERC20AssetData(zrxToken.address);
|
||||
|
||||
const erc20FiveDecimalTokenCount = 2;
|
||||
const fiveDecimals = new BigNumber(18);
|
||||
const [erc20FiveDecimalTokenA, erc20FiveDecimalTokenB] = await erc20Wrapper.deployDummyTokensAsync(
|
||||
erc20FiveDecimalTokenCount,
|
||||
fiveDecimals,
|
||||
);
|
||||
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(AssetProxyId.ERC20, erc20Proxy.address, ownerAddress);
|
||||
await exchangeWrapper.registerAssetProxyAsync(AssetProxyId.ERC721, 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],
|
||||
erc721Token,
|
||||
erc721Balances,
|
||||
exchangeContract.address,
|
||||
);
|
||||
|
||||
const coreCombinatorialUtils = new CoreCombinatorialUtils(
|
||||
orderFactory,
|
||||
ownerAddress,
|
||||
makerAddress,
|
||||
makerPrivateKey,
|
||||
takerAddress,
|
||||
zrxAssetData,
|
||||
exchangeWrapper,
|
||||
assetWrapper,
|
||||
);
|
||||
return coreCombinatorialUtils;
|
||||
}
|
||||
|
||||
export class CoreCombinatorialUtils {
|
||||
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 static generateFillOrderCombinations(): FillScenario[] {
|
||||
const takerScenarios = [TakerScenario.Unspecified];
|
||||
const feeRecipientScenarios = [FeeRecipientAddressScenario.EthUserAddress];
|
||||
const makerAssetAmountScenario = [OrderAssetAmountScenario.Large];
|
||||
const takerAssetAmountScenario = [OrderAssetAmountScenario.Large];
|
||||
const makerFeeScenario = [OrderAssetAmountScenario.Large];
|
||||
const takerFeeScenario = [OrderAssetAmountScenario.Large];
|
||||
const expirationTimeSecondsScenario = [ExpirationTimeSecondsScenario.InFuture];
|
||||
const makerAssetDataScenario = [
|
||||
AssetDataScenario.ERC20FiveDecimals,
|
||||
AssetDataScenario.ERC20NonZRXEighteenDecimals,
|
||||
AssetDataScenario.ERC721,
|
||||
AssetDataScenario.ZRXFeeToken,
|
||||
];
|
||||
const takerAssetDataScenario = [
|
||||
AssetDataScenario.ERC20FiveDecimals,
|
||||
AssetDataScenario.ERC20NonZRXEighteenDecimals,
|
||||
AssetDataScenario.ERC721,
|
||||
AssetDataScenario.ZRXFeeToken,
|
||||
];
|
||||
const takerAssetFillAmountScenario = [TakerAssetFillAmountScenario.ExactlyRemainingFillableTakerAssetAmount];
|
||||
const fillScenarioArrays = CoreCombinatorialUtils._getAllCombinations([
|
||||
takerScenarios,
|
||||
feeRecipientScenarios,
|
||||
makerAssetAmountScenario,
|
||||
takerAssetAmountScenario,
|
||||
makerFeeScenario,
|
||||
takerFeeScenario,
|
||||
expirationTimeSecondsScenario,
|
||||
makerAssetDataScenario,
|
||||
takerAssetDataScenario,
|
||||
takerAssetFillAmountScenario,
|
||||
]);
|
||||
|
||||
const fillScenarios = _.map(fillScenarioArrays, fillScenarioArray => {
|
||||
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: BalanceAmountScenario.Higher,
|
||||
traderAssetAllowance: AllowanceAmountScenario.Higher,
|
||||
zrxFeeBalance: BalanceAmountScenario.Higher,
|
||||
zrxFeeAllowance: AllowanceAmountScenario.Higher,
|
||||
},
|
||||
takerStateScenario: {
|
||||
traderAssetBalance: BalanceAmountScenario.Higher,
|
||||
traderAssetAllowance: AllowanceAmountScenario.Higher,
|
||||
zrxFeeBalance: BalanceAmountScenario.Higher,
|
||||
zrxFeeAllowance: AllowanceAmountScenario.Higher,
|
||||
},
|
||||
};
|
||||
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 = CoreCombinatorialUtils._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,
|
||||
) {
|
||||
this.orderFactory = orderFactory;
|
||||
this.ownerAddress = ownerAddress;
|
||||
this.makerAddress = makerAddress;
|
||||
this.makerPrivateKey = makerPrivateKey;
|
||||
this.takerAddress = takerAddress;
|
||||
this.zrxAssetData = zrxAssetData;
|
||||
this.exchangeWrapper = exchangeWrapper;
|
||||
this.assetWrapper = assetWrapper;
|
||||
}
|
||||
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);
|
||||
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,
|
||||
);
|
||||
}
|
||||
private async _fillOrderAndAssertOutcomeAsync(
|
||||
signedOrder: SignedOrder,
|
||||
takerAssetFillAmount: BigNumber,
|
||||
lazyStore: BalanceAndProxyAllowanceLazyStore,
|
||||
fillRevertReasonIfExists: RevertReason | undefined,
|
||||
): Promise<void> {
|
||||
if (!_.isUndefined(fillRevertReasonIfExists)) {
|
||||
return expectRevertReasonOrAlwaysFailingTransactionAsync(
|
||||
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.add(takerAssetFillAmount);
|
||||
|
||||
const expFilledMakerAmount = orderUtils.getPartialAmount(
|
||||
expFilledTakerAmount,
|
||||
signedOrder.takerAssetAmount,
|
||||
signedOrder.makerAssetAmount,
|
||||
);
|
||||
|
||||
// - 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');
|
||||
|
||||
expect(txReceipt.logs.length).to.be.equal(1, 'logs length');
|
||||
// tslint:disable-next-line:no-unnecessary-type-assertion
|
||||
const log = txReceipt.logs[0] as LogWithDecodedArgs<FillContractEventArgs>;
|
||||
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',
|
||||
);
|
||||
const expMakerFeePaid = orderUtils.getPartialAmount(
|
||||
expFilledTakerAmount,
|
||||
signedOrder.takerAssetAmount,
|
||||
signedOrder.makerFee,
|
||||
);
|
||||
expect(log.args.makerFeePaid).to.be.bignumber.equal(expMakerFeePaid, 'log.args.makerFeePaid');
|
||||
const expTakerFeePaid = orderUtils.getPartialAmount(
|
||||
expFilledTakerAmount,
|
||||
signedOrder.takerAssetAmount,
|
||||
signedOrder.takerFee,
|
||||
);
|
||||
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 _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.add(1);
|
||||
break;
|
||||
|
||||
case TakerAssetFillAmountScenario.LessThanRemainingFillableTakerAssetAmount:
|
||||
const takerAssetProxyId = assetProxyUtils.decodeAssetDataId(signedOrder.takerAssetData);
|
||||
const makerAssetProxyId = assetProxyUtils.decodeAssetDataId(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).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.getPartialAmount(
|
||||
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.getPartialAmount(
|
||||
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.getPartialAmount(
|
||||
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(
|
||||
signedOrder.takerAddress,
|
||||
this.zrxAssetData,
|
||||
tooLowAllowance,
|
||||
);
|
||||
break;
|
||||
|
||||
case AllowanceAmountScenario.Exact:
|
||||
const exactAllowance = takerFee;
|
||||
await this.assetWrapper.setProxyAllowanceAsync(
|
||||
signedOrder.takerAddress,
|
||||
this.zrxAssetData,
|
||||
exactAllowance,
|
||||
);
|
||||
break;
|
||||
|
||||
case AllowanceAmountScenario.Unlimited:
|
||||
await this.assetWrapper.setProxyAllowanceAsync(
|
||||
signedOrder.takerAddress,
|
||||
this.zrxAssetData,
|
||||
constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS,
|
||||
);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw errorUtils.spawnSwitchErr(
|
||||
'takerStateScenario.zrxFeeAllowance',
|
||||
takerStateScenario.zrxFeeAllowance,
|
||||
);
|
||||
}
|
||||
}
|
||||
} // tslint:disable:max-file-line-count
|
||||
@@ -1,3 +1,4 @@
|
||||
import { assetProxyUtils } from '@0xproject/order-utils';
|
||||
import { BigNumber } from '@0xproject/utils';
|
||||
import { Web3Wrapper } from '@0xproject/web3-wrapper';
|
||||
import { Provider } from 'ethereum-types';
|
||||
@@ -18,6 +19,7 @@ export class ERC20Wrapper {
|
||||
private _provider: Provider;
|
||||
private _dummyTokenContracts: DummyERC20TokenContract[];
|
||||
private _proxyContract?: ERC20ProxyContract;
|
||||
private _proxyIdIfExists?: string;
|
||||
constructor(provider: Provider, tokenOwnerAddresses: string[], contractOwnerAddress: string) {
|
||||
this._dummyTokenContracts = [];
|
||||
this._web3Wrapper = new Web3Wrapper(provider);
|
||||
@@ -25,8 +27,11 @@ export class ERC20Wrapper {
|
||||
this._tokenOwnerAddresses = tokenOwnerAddresses;
|
||||
this._contractOwnerAddress = contractOwnerAddress;
|
||||
}
|
||||
public async deployDummyTokensAsync(): Promise<DummyERC20TokenContract[]> {
|
||||
for (let i = 0; i < constants.NUM_DUMMY_ERC20_TO_DEPLOY; i++) {
|
||||
public async deployDummyTokensAsync(
|
||||
numberToDeploy: number,
|
||||
decimals: BigNumber,
|
||||
): Promise<DummyERC20TokenContract[]> {
|
||||
for (let i = 0; i < numberToDeploy; i++) {
|
||||
this._dummyTokenContracts.push(
|
||||
await DummyERC20TokenContract.deployFrom0xArtifactAsync(
|
||||
artifacts.DummyERC20Token,
|
||||
@@ -34,7 +39,7 @@ export class ERC20Wrapper {
|
||||
txDefaults,
|
||||
constants.DUMMY_TOKEN_NAME,
|
||||
constants.DUMMY_TOKEN_SYMBOL,
|
||||
constants.DUMMY_TOKEN_DECIMALS,
|
||||
decimals,
|
||||
constants.DUMMY_TOKEN_TOTAL_SUPPLY,
|
||||
),
|
||||
);
|
||||
@@ -47,8 +52,13 @@ export class ERC20Wrapper {
|
||||
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();
|
||||
@@ -73,6 +83,36 @@ export class ERC20Wrapper {
|
||||
}
|
||||
}
|
||||
}
|
||||
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 = {};
|
||||
@@ -105,6 +145,15 @@ export class ERC20Wrapper {
|
||||
const tokenAddresses = _.map(this._dummyTokenContracts, dummyTokenContract => dummyTokenContract.address);
|
||||
return tokenAddresses;
|
||||
}
|
||||
private _getTokenContractFromAssetData(assetData: string): DummyERC20TokenContract {
|
||||
const erc20ProxyData = assetProxyUtils.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"');
|
||||
|
||||
@@ -19,6 +19,7 @@ export class ERC721Wrapper {
|
||||
private _provider: Provider;
|
||||
private _dummyTokenContracts: DummyERC721TokenContract[];
|
||||
private _proxyContract?: ERC721ProxyContract;
|
||||
private _proxyIdIfExists?: string;
|
||||
private _initialTokenIdsByOwner: ERC721TokenIdsByOwner = {};
|
||||
constructor(provider: Provider, tokenOwnerAddresses: string[], contractOwnerAddress: string) {
|
||||
this._web3Wrapper = new Web3Wrapper(provider);
|
||||
@@ -47,8 +48,13 @@ export class ERC721Wrapper {
|
||||
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();
|
||||
@@ -57,12 +63,7 @@ export class ERC721Wrapper {
|
||||
for (const tokenOwnerAddress of this._tokenOwnerAddresses) {
|
||||
for (let i = 0; i < constants.NUM_ERC721_TOKENS_TO_MINT; i++) {
|
||||
const tokenId = generatePseudoRandomSalt();
|
||||
await this._web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await dummyTokenContract.mint.sendTransactionAsync(tokenOwnerAddress, tokenId, {
|
||||
from: this._contractOwnerAddress,
|
||||
}),
|
||||
constants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
await this.mintAsync(dummyTokenContract.address, tokenId, tokenOwnerAddress);
|
||||
if (_.isUndefined(this._initialTokenIdsByOwner[tokenOwnerAddress])) {
|
||||
this._initialTokenIdsByOwner[tokenOwnerAddress] = {
|
||||
[dummyTokenContract.address]: [],
|
||||
@@ -72,19 +73,100 @@ export class ERC721Wrapper {
|
||||
this._initialTokenIdsByOwner[tokenOwnerAddress][dummyTokenContract.address] = [];
|
||||
}
|
||||
this._initialTokenIdsByOwner[tokenOwnerAddress][dummyTokenContract.address].push(tokenId);
|
||||
|
||||
await this.approveProxyAsync(dummyTokenContract.address, tokenId);
|
||||
}
|
||||
const shouldApprove = true;
|
||||
await this._web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await dummyTokenContract.setApprovalForAll.sendTransactionAsync(
|
||||
(this._proxyContract as ERC721ProxyContract).address,
|
||||
shouldApprove,
|
||||
{ from: tokenOwnerAddress },
|
||||
),
|
||||
constants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
public async doesTokenExistAsync(tokenAddress: string, tokenId: BigNumber): Promise<boolean> {
|
||||
const tokenContract = this._getTokenContractFromAssetData(tokenAddress);
|
||||
const doesExist = await tokenContract.exists.callAsync(tokenId);
|
||||
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();
|
||||
@@ -127,6 +209,13 @@ export class ERC721Wrapper {
|
||||
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"');
|
||||
|
||||
@@ -33,8 +33,8 @@ export class ExchangeWrapper {
|
||||
params.signature,
|
||||
{ from },
|
||||
);
|
||||
const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash);
|
||||
return tx;
|
||||
const txReceipt = await this._logDecoder.getTxWithDecodedLogsAsync(txHash);
|
||||
return txReceipt;
|
||||
}
|
||||
public async cancelOrderAsync(signedOrder: SignedOrder, from: string): Promise<TransactionReceiptWithDecodedLogs> {
|
||||
const params = orderUtils.createCancel(signedOrder);
|
||||
@@ -227,6 +227,10 @@ export class ExchangeWrapper {
|
||||
const filledAmount = new BigNumber(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 getOrderInfoAsync(signedOrder: SignedOrder): Promise<OrderInfo> {
|
||||
const orderInfo = (await this._exchange.getOrderInfo.callAsync(signedOrder)) as OrderInfo;
|
||||
return orderInfo;
|
||||
|
||||
277
packages/contracts/src/utils/order_factory_from_scenario.ts
Normal file
277
packages/contracts/src/utils/order_factory_from_scenario.ts
Normal file
@@ -0,0 +1,277 @@
|
||||
import { assetProxyUtils, generatePseudoRandomSalt } from '@0xproject/order-utils';
|
||||
import { Order } from '@0xproject/types';
|
||||
import { BigNumber, errorUtils } from '@0xproject/utils';
|
||||
|
||||
import { DummyERC721TokenContract } from '../generated_contract_wrappers/dummy_e_r_c721_token';
|
||||
|
||||
import { constants } from './constants';
|
||||
import {
|
||||
AssetDataScenario,
|
||||
ERC721TokenIdsByOwner,
|
||||
ExpirationTimeSecondsScenario,
|
||||
FeeRecipientAddressScenario,
|
||||
OrderAssetAmountScenario,
|
||||
OrderScenario,
|
||||
TakerScenario,
|
||||
} from './types';
|
||||
|
||||
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 ONE_NFT_UNIT = new BigNumber(1);
|
||||
|
||||
export class OrderFactoryFromScenario {
|
||||
private _userAddresses: string[];
|
||||
private _zrxAddress: string;
|
||||
private _nonZrxERC20EighteenDecimalTokenAddresses: string[];
|
||||
private _erc20FiveDecimalTokenAddresses: string[];
|
||||
private _erc721Token: DummyERC721TokenContract;
|
||||
private _erc721Balances: ERC721TokenIdsByOwner;
|
||||
private _exchangeAddress: string;
|
||||
constructor(
|
||||
userAddresses: string[],
|
||||
zrxAddress: string,
|
||||
nonZrxERC20EighteenDecimalTokenAddresses: string[],
|
||||
erc20FiveDecimalTokenAddresses: string[],
|
||||
erc721Token: DummyERC721TokenContract,
|
||||
erc721Balances: ERC721TokenIdsByOwner,
|
||||
exchangeAddress: string,
|
||||
) {
|
||||
this._userAddresses = userAddresses;
|
||||
this._zrxAddress = zrxAddress;
|
||||
this._nonZrxERC20EighteenDecimalTokenAddresses = nonZrxERC20EighteenDecimalTokenAddresses;
|
||||
this._erc20FiveDecimalTokenAddresses = erc20FiveDecimalTokenAddresses;
|
||||
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 = assetProxyUtils.encodeERC20AssetData(this._zrxAddress);
|
||||
break;
|
||||
case AssetDataScenario.ERC20NonZRXEighteenDecimals:
|
||||
makerAssetData = assetProxyUtils.encodeERC20AssetData(
|
||||
this._nonZrxERC20EighteenDecimalTokenAddresses[0],
|
||||
);
|
||||
break;
|
||||
case AssetDataScenario.ERC20FiveDecimals:
|
||||
makerAssetData = assetProxyUtils.encodeERC20AssetData(this._erc20FiveDecimalTokenAddresses[0]);
|
||||
break;
|
||||
case AssetDataScenario.ERC721:
|
||||
makerAssetData = assetProxyUtils.encodeERC721AssetData(
|
||||
this._erc721Token.address,
|
||||
erc721MakerAssetIds[0],
|
||||
);
|
||||
break;
|
||||
default:
|
||||
throw errorUtils.spawnSwitchErr('AssetDataScenario', orderScenario.makerAssetDataScenario);
|
||||
}
|
||||
|
||||
switch (orderScenario.takerAssetDataScenario) {
|
||||
case AssetDataScenario.ZRXFeeToken:
|
||||
takerAssetData = assetProxyUtils.encodeERC20AssetData(this._zrxAddress);
|
||||
break;
|
||||
case AssetDataScenario.ERC20NonZRXEighteenDecimals:
|
||||
takerAssetData = assetProxyUtils.encodeERC20AssetData(
|
||||
this._nonZrxERC20EighteenDecimalTokenAddresses[1],
|
||||
);
|
||||
break;
|
||||
case AssetDataScenario.ERC20FiveDecimals:
|
||||
takerAssetData = assetProxyUtils.encodeERC20AssetData(this._erc20FiveDecimalTokenAddresses[1]);
|
||||
break;
|
||||
case AssetDataScenario.ERC721:
|
||||
takerAssetData = assetProxyUtils.encodeERC721AssetData(
|
||||
this._erc721Token.address,
|
||||
erc721TakerAssetIds[0],
|
||||
);
|
||||
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;
|
||||
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;
|
||||
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;
|
||||
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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,13 @@ import { constants } from './constants';
|
||||
import { CancelOrder, MatchOrder } from './types';
|
||||
|
||||
export const orderUtils = {
|
||||
getPartialAmount(numerator: BigNumber, denominator: BigNumber, target: BigNumber): BigNumber {
|
||||
const partialAmount = numerator
|
||||
.mul(target)
|
||||
.div(denominator)
|
||||
.floor();
|
||||
return partialAmount;
|
||||
},
|
||||
createFill: (signedOrder: SignedOrder, takerAssetFillAmount?: BigNumber) => {
|
||||
const fill = {
|
||||
order: orderUtils.getOrderWithoutExchangeAddress(signedOrder),
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { AbstractBalanceAndProxyAllowanceFetcher } from '@0xproject/order-utils';
|
||||
import { BigNumber } from '@0xproject/utils';
|
||||
|
||||
import { AssetWrapper } from './asset_wrapper';
|
||||
|
||||
export class SimpleAssetBalanceAndProxyAllowanceFetcher implements AbstractBalanceAndProxyAllowanceFetcher {
|
||||
private _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,24 @@
|
||||
import { AbstractOrderFilledCancelledFetcher } from '@0xproject/order-utils';
|
||||
import { BigNumber } from '@0xproject/utils';
|
||||
|
||||
import { ExchangeWrapper } from './exchange_wrapper';
|
||||
|
||||
export class SimpleOrderFilledCancelledFetcher implements AbstractOrderFilledCancelledFetcher {
|
||||
private _exchangeWrapper: ExchangeWrapper;
|
||||
private _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(orderHash: string): Promise<boolean> {
|
||||
const isCancelled = await this._exchangeWrapper.isCancelledAsync(orderHash);
|
||||
return isCancelled;
|
||||
}
|
||||
public getZRXAssetData(): string {
|
||||
return this._zrxAssetData;
|
||||
}
|
||||
}
|
||||
@@ -150,3 +150,80 @@ export interface MatchOrder {
|
||||
leftSignature: string;
|
||||
rightSignature: string;
|
||||
}
|
||||
|
||||
// Combinatorial testing types
|
||||
|
||||
export enum FeeRecipientAddressScenario {
|
||||
BurnAddress = 'BURN_ADDRESS',
|
||||
EthUserAddress = 'ETH_USER_ADDRESS',
|
||||
}
|
||||
|
||||
export enum OrderAssetAmountScenario {
|
||||
Zero = 'ZERO',
|
||||
Large = 'LARGE',
|
||||
Small = 'SMALL',
|
||||
}
|
||||
|
||||
export enum TakerScenario {
|
||||
CorrectlySpecified = 'CORRECTLY_SPECIFIED',
|
||||
IncorrectlySpecified = 'INCORRECTLY_SPECIFIED',
|
||||
Unspecified = 'UNSPECIFIED',
|
||||
}
|
||||
|
||||
export enum ExpirationTimeSecondsScenario {
|
||||
InPast = 'IN_PAST',
|
||||
InFuture = 'IN_FUTURE',
|
||||
}
|
||||
|
||||
export enum AssetDataScenario {
|
||||
ERC721 = 'ERC721',
|
||||
ZRXFeeToken = 'ZRX_FEE_TOKEN',
|
||||
ERC20FiveDecimals = 'ERC20_FIVE_DECIMALS',
|
||||
ERC20NonZRXEighteenDecimals = 'ERC20_NON_ZRX_EIGHTEEN_DECIMALS',
|
||||
}
|
||||
|
||||
export enum TakerAssetFillAmountScenario {
|
||||
Zero = 'ZERO',
|
||||
GreaterThanRemainingFillableTakerAssetAmount = 'GREATER_THAN_REMAINING_FILLABLE_TAKER_ASSET_AMOUNT',
|
||||
LessThanRemainingFillableTakerAssetAmount = 'LESS_THAN_REMAINING_FILLABLE_TAKER_ASSET_AMOUNT',
|
||||
ExactlyRemainingFillableTakerAssetAmount = 'EXACTLY_REMAINING_FILLABLE_TAKER_ASSET_AMOUNT',
|
||||
}
|
||||
|
||||
export interface OrderScenario {
|
||||
takerScenario: TakerScenario;
|
||||
feeRecipientScenario: FeeRecipientAddressScenario;
|
||||
makerAssetAmountScenario: OrderAssetAmountScenario;
|
||||
takerAssetAmountScenario: OrderAssetAmountScenario;
|
||||
makerFeeScenario: OrderAssetAmountScenario;
|
||||
takerFeeScenario: OrderAssetAmountScenario;
|
||||
expirationTimeSecondsScenario: ExpirationTimeSecondsScenario;
|
||||
makerAssetDataScenario: AssetDataScenario;
|
||||
takerAssetDataScenario: AssetDataScenario;
|
||||
}
|
||||
|
||||
export enum BalanceAmountScenario {
|
||||
Exact = 'EXACT',
|
||||
TooLow = 'TOO_LOW',
|
||||
Higher = 'HIGHER',
|
||||
}
|
||||
|
||||
export enum AllowanceAmountScenario {
|
||||
Exact = 'EXACT',
|
||||
TooLow = 'TOO_LOW',
|
||||
Higher = 'HIGHER',
|
||||
Unlimited = 'UNLIMITED',
|
||||
}
|
||||
|
||||
export interface TraderStateScenario {
|
||||
traderAssetBalance: BalanceAmountScenario;
|
||||
traderAssetAllowance: AllowanceAmountScenario;
|
||||
zrxFeeBalance: BalanceAmountScenario;
|
||||
zrxFeeAllowance: AllowanceAmountScenario;
|
||||
}
|
||||
|
||||
export interface FillScenario {
|
||||
orderScenario: OrderScenario;
|
||||
takerAssetFillAmountScenario: TakerAssetFillAmountScenario;
|
||||
makerStateScenario: TraderStateScenario;
|
||||
takerStateScenario: TraderStateScenario;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { BigNumber } from '@0xproject/utils';
|
||||
import * as chai from 'chai';
|
||||
import { LogWithDecodedArgs } from 'ethereum-types';
|
||||
import ethUtil = require('ethereumjs-util');
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { DummyERC20TokenContract } from '../../src/generated_contract_wrappers/dummy_e_r_c20_token';
|
||||
import {
|
||||
@@ -53,12 +54,17 @@ describe('Asset Transfer Proxies', () => {
|
||||
});
|
||||
before(async () => {
|
||||
const accounts = await web3Wrapper.getAvailableAddressesAsync();
|
||||
const usedAddresses = ([owner, notAuthorized, exchangeAddress, makerAddress, takerAddress] = accounts);
|
||||
const usedAddresses = ([owner, notAuthorized, exchangeAddress, makerAddress, takerAddress] = _.slice(
|
||||
accounts,
|
||||
0,
|
||||
5,
|
||||
));
|
||||
|
||||
erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner);
|
||||
erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner);
|
||||
|
||||
[zrxToken] = await erc20Wrapper.deployDummyTokensAsync();
|
||||
const numDummyErc20ToDeploy = 1;
|
||||
[zrxToken] = await erc20Wrapper.deployDummyTokensAsync(numDummyErc20ToDeploy, constants.DUMMY_TOKEN_DECIMALS);
|
||||
erc20Proxy = await erc20Wrapper.deployProxyAsync();
|
||||
await erc20Wrapper.setBalancesAndAllowancesAsync();
|
||||
await web3Wrapper.awaitTransactionSuccessAsync(
|
||||
|
||||
@@ -6,16 +6,13 @@ import { Web3Wrapper } from '@0xproject/web3-wrapper';
|
||||
import * as chai from 'chai';
|
||||
import { LogWithDecodedArgs } from 'ethereum-types';
|
||||
import ethUtil = require('ethereumjs-util');
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { DummyERC20TokenContract } from '../../src/generated_contract_wrappers/dummy_e_r_c20_token';
|
||||
import { DummyERC721TokenContract } from '../../src/generated_contract_wrappers/dummy_e_r_c721_token';
|
||||
import { ERC20ProxyContract } from '../../src/generated_contract_wrappers/e_r_c20_proxy';
|
||||
import { ERC721ProxyContract } from '../../src/generated_contract_wrappers/e_r_c721_proxy';
|
||||
import {
|
||||
CancelContractEventArgs,
|
||||
ExchangeContract,
|
||||
FillContractEventArgs,
|
||||
} from '../../src/generated_contract_wrappers/exchange';
|
||||
import { CancelContractEventArgs, ExchangeContract } from '../../src/generated_contract_wrappers/exchange';
|
||||
import { artifacts } from '../../src/utils/artifacts';
|
||||
import { expectRevertReasonOrAlwaysFailingTransactionAsync } from '../../src/utils/assertions';
|
||||
import { chaiSetup } from '../../src/utils/chai_setup';
|
||||
@@ -66,12 +63,16 @@ describe('Exchange core', () => {
|
||||
});
|
||||
before(async () => {
|
||||
const accounts = await web3Wrapper.getAvailableAddressesAsync();
|
||||
const usedAddresses = ([owner, makerAddress, takerAddress, feeRecipientAddress] = accounts);
|
||||
const usedAddresses = ([owner, makerAddress, takerAddress, feeRecipientAddress] = _.slice(accounts, 0, 4));
|
||||
|
||||
erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner);
|
||||
erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner);
|
||||
|
||||
[erc20TokenA, erc20TokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync();
|
||||
const numDummyErc20ToDeploy = 3;
|
||||
[erc20TokenA, erc20TokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync(
|
||||
numDummyErc20ToDeploy,
|
||||
constants.DUMMY_TOKEN_DECIMALS,
|
||||
);
|
||||
erc20Proxy = await erc20Wrapper.deployProxyAsync();
|
||||
await erc20Wrapper.setBalancesAndAllowancesAsync();
|
||||
|
||||
@@ -130,296 +131,6 @@ describe('Exchange core', () => {
|
||||
erc20Balances = await erc20Wrapper.getBalancesAsync();
|
||||
signedOrder = orderFactory.newSignedOrder();
|
||||
});
|
||||
it('should transfer the correct amounts when makerAssetAmount === takerAssetAmount', async () => {
|
||||
signedOrder = orderFactory.newSignedOrder({
|
||||
makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18),
|
||||
takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18),
|
||||
});
|
||||
|
||||
const takerAssetFilledAmountBefore = await exchangeWrapper.getTakerAssetFilledAmountAsync(
|
||||
orderHashUtils.getOrderHashHex(signedOrder),
|
||||
);
|
||||
expect(takerAssetFilledAmountBefore).to.be.bignumber.equal(0);
|
||||
|
||||
const takerAssetFillAmount = signedOrder.takerAssetAmount.div(2);
|
||||
await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount });
|
||||
|
||||
const makerAmountBoughtAfter = await exchangeWrapper.getTakerAssetFilledAmountAsync(
|
||||
orderHashUtils.getOrderHashHex(signedOrder),
|
||||
);
|
||||
expect(makerAmountBoughtAfter).to.be.bignumber.equal(takerAssetFillAmount);
|
||||
|
||||
const newBalances = await erc20Wrapper.getBalancesAsync();
|
||||
|
||||
const makerAssetFilledAmount = takerAssetFillAmount
|
||||
.times(signedOrder.makerAssetAmount)
|
||||
.dividedToIntegerBy(signedOrder.takerAssetAmount);
|
||||
const makerFeePaid = signedOrder.makerFee
|
||||
.times(makerAssetFilledAmount)
|
||||
.dividedToIntegerBy(signedOrder.makerAssetAmount);
|
||||
const takerFeePaid = signedOrder.takerFee
|
||||
.times(makerAssetFilledAmount)
|
||||
.dividedToIntegerBy(signedOrder.makerAssetAmount);
|
||||
expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
|
||||
erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFilledAmount),
|
||||
);
|
||||
expect(newBalances[makerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
|
||||
erc20Balances[makerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount),
|
||||
);
|
||||
expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal(
|
||||
erc20Balances[makerAddress][zrxToken.address].minus(makerFeePaid),
|
||||
);
|
||||
expect(newBalances[takerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
|
||||
erc20Balances[takerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount),
|
||||
);
|
||||
expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
|
||||
erc20Balances[takerAddress][defaultMakerAssetAddress].add(makerAssetFilledAmount),
|
||||
);
|
||||
expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal(
|
||||
erc20Balances[takerAddress][zrxToken.address].minus(takerFeePaid),
|
||||
);
|
||||
expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal(
|
||||
erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)),
|
||||
);
|
||||
});
|
||||
|
||||
it('should transfer the correct amounts when makerAssetAmount > takerAssetAmount', async () => {
|
||||
signedOrder = orderFactory.newSignedOrder({
|
||||
makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(200), 18),
|
||||
takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18),
|
||||
});
|
||||
|
||||
const takerAssetFilledAmountBefore = await exchangeWrapper.getTakerAssetFilledAmountAsync(
|
||||
orderHashUtils.getOrderHashHex(signedOrder),
|
||||
);
|
||||
expect(takerAssetFilledAmountBefore).to.be.bignumber.equal(0);
|
||||
|
||||
const takerAssetFillAmount = signedOrder.takerAssetAmount.div(2);
|
||||
await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount });
|
||||
|
||||
const makerAmountBoughtAfter = await exchangeWrapper.getTakerAssetFilledAmountAsync(
|
||||
orderHashUtils.getOrderHashHex(signedOrder),
|
||||
);
|
||||
expect(makerAmountBoughtAfter).to.be.bignumber.equal(takerAssetFillAmount);
|
||||
|
||||
const newBalances = await erc20Wrapper.getBalancesAsync();
|
||||
|
||||
const makerAssetFilledAmount = takerAssetFillAmount
|
||||
.times(signedOrder.makerAssetAmount)
|
||||
.dividedToIntegerBy(signedOrder.takerAssetAmount);
|
||||
const makerFeePaid = signedOrder.makerFee
|
||||
.times(makerAssetFilledAmount)
|
||||
.dividedToIntegerBy(signedOrder.makerAssetAmount);
|
||||
const takerFeePaid = signedOrder.takerFee
|
||||
.times(makerAssetFilledAmount)
|
||||
.dividedToIntegerBy(signedOrder.makerAssetAmount);
|
||||
expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
|
||||
erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFilledAmount),
|
||||
);
|
||||
expect(newBalances[makerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
|
||||
erc20Balances[makerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount),
|
||||
);
|
||||
expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal(
|
||||
erc20Balances[makerAddress][zrxToken.address].minus(makerFeePaid),
|
||||
);
|
||||
expect(newBalances[takerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
|
||||
erc20Balances[takerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount),
|
||||
);
|
||||
expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
|
||||
erc20Balances[takerAddress][defaultMakerAssetAddress].add(makerAssetFilledAmount),
|
||||
);
|
||||
expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal(
|
||||
erc20Balances[takerAddress][zrxToken.address].minus(takerFeePaid),
|
||||
);
|
||||
expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal(
|
||||
erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)),
|
||||
);
|
||||
});
|
||||
|
||||
it('should transfer the correct amounts when makerAssetAmount < takerAssetAmount', async () => {
|
||||
signedOrder = orderFactory.newSignedOrder({
|
||||
makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18),
|
||||
takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(200), 18),
|
||||
});
|
||||
|
||||
const takerAssetFilledAmountBefore = await exchangeWrapper.getTakerAssetFilledAmountAsync(
|
||||
orderHashUtils.getOrderHashHex(signedOrder),
|
||||
);
|
||||
expect(takerAssetFilledAmountBefore).to.be.bignumber.equal(0);
|
||||
|
||||
const takerAssetFillAmount = signedOrder.takerAssetAmount.div(2);
|
||||
await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount });
|
||||
|
||||
const makerAmountBoughtAfter = await exchangeWrapper.getTakerAssetFilledAmountAsync(
|
||||
orderHashUtils.getOrderHashHex(signedOrder),
|
||||
);
|
||||
expect(makerAmountBoughtAfter).to.be.bignumber.equal(takerAssetFillAmount);
|
||||
|
||||
const newBalances = await erc20Wrapper.getBalancesAsync();
|
||||
|
||||
const makerAssetFilledAmount = takerAssetFillAmount
|
||||
.times(signedOrder.makerAssetAmount)
|
||||
.dividedToIntegerBy(signedOrder.takerAssetAmount);
|
||||
const makerFeePaid = signedOrder.makerFee
|
||||
.times(makerAssetFilledAmount)
|
||||
.dividedToIntegerBy(signedOrder.makerAssetAmount);
|
||||
const takerFeePaid = signedOrder.takerFee
|
||||
.times(makerAssetFilledAmount)
|
||||
.dividedToIntegerBy(signedOrder.makerAssetAmount);
|
||||
expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
|
||||
erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFilledAmount),
|
||||
);
|
||||
expect(newBalances[makerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
|
||||
erc20Balances[makerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount),
|
||||
);
|
||||
expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal(
|
||||
erc20Balances[makerAddress][zrxToken.address].minus(makerFeePaid),
|
||||
);
|
||||
expect(newBalances[takerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
|
||||
erc20Balances[takerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount),
|
||||
);
|
||||
expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
|
||||
erc20Balances[takerAddress][defaultMakerAssetAddress].add(makerAssetFilledAmount),
|
||||
);
|
||||
expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal(
|
||||
erc20Balances[takerAddress][zrxToken.address].minus(takerFeePaid),
|
||||
);
|
||||
expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal(
|
||||
erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)),
|
||||
);
|
||||
});
|
||||
|
||||
it('should transfer the correct amounts when taker is specified and order is claimed by taker', async () => {
|
||||
signedOrder = orderFactory.newSignedOrder({
|
||||
takerAddress,
|
||||
makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18),
|
||||
takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(200), 18),
|
||||
});
|
||||
|
||||
const takerAssetFilledAmountBefore = await exchangeWrapper.getTakerAssetFilledAmountAsync(
|
||||
orderHashUtils.getOrderHashHex(signedOrder),
|
||||
);
|
||||
expect(takerAssetFilledAmountBefore).to.be.bignumber.equal(0);
|
||||
|
||||
const takerAssetFillAmount = signedOrder.takerAssetAmount.div(2);
|
||||
await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount });
|
||||
|
||||
const makerAmountBoughtAfter = await exchangeWrapper.getTakerAssetFilledAmountAsync(
|
||||
orderHashUtils.getOrderHashHex(signedOrder),
|
||||
);
|
||||
const expectedMakerAmountBoughtAfter = takerAssetFillAmount.add(takerAssetFilledAmountBefore);
|
||||
expect(makerAmountBoughtAfter).to.be.bignumber.equal(expectedMakerAmountBoughtAfter);
|
||||
|
||||
const newBalances = await erc20Wrapper.getBalancesAsync();
|
||||
|
||||
const makerAssetFilledAmount = takerAssetFillAmount
|
||||
.times(signedOrder.makerAssetAmount)
|
||||
.dividedToIntegerBy(signedOrder.takerAssetAmount);
|
||||
const makerFeePaid = signedOrder.makerFee
|
||||
.times(makerAssetFilledAmount)
|
||||
.dividedToIntegerBy(signedOrder.makerAssetAmount);
|
||||
const takerFeePaid = signedOrder.takerFee
|
||||
.times(makerAssetFilledAmount)
|
||||
.dividedToIntegerBy(signedOrder.makerAssetAmount);
|
||||
expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
|
||||
erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFilledAmount),
|
||||
);
|
||||
expect(newBalances[makerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
|
||||
erc20Balances[makerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount),
|
||||
);
|
||||
expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal(
|
||||
erc20Balances[makerAddress][zrxToken.address].minus(makerFeePaid),
|
||||
);
|
||||
expect(newBalances[takerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
|
||||
erc20Balances[takerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount),
|
||||
);
|
||||
expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
|
||||
erc20Balances[takerAddress][defaultMakerAssetAddress].add(makerAssetFilledAmount),
|
||||
);
|
||||
expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal(
|
||||
erc20Balances[takerAddress][zrxToken.address].minus(takerFeePaid),
|
||||
);
|
||||
expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal(
|
||||
erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)),
|
||||
);
|
||||
});
|
||||
|
||||
it('should fill remaining value if takerAssetFillAmount > remaining takerAssetAmount', async () => {
|
||||
const takerAssetFillAmount = signedOrder.takerAssetAmount.div(2);
|
||||
await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount });
|
||||
|
||||
const res = await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, {
|
||||
takerAssetFillAmount: signedOrder.takerAssetAmount,
|
||||
});
|
||||
const log = res.logs[0] as LogWithDecodedArgs<FillContractEventArgs>;
|
||||
expect(log.args.takerAssetFilledAmount).to.be.bignumber.equal(
|
||||
signedOrder.takerAssetAmount.minus(takerAssetFillAmount),
|
||||
);
|
||||
const newBalances = await erc20Wrapper.getBalancesAsync();
|
||||
|
||||
expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
|
||||
erc20Balances[makerAddress][defaultMakerAssetAddress].minus(signedOrder.makerAssetAmount),
|
||||
);
|
||||
expect(newBalances[makerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
|
||||
erc20Balances[makerAddress][defaultTakerAssetAddress].add(signedOrder.takerAssetAmount),
|
||||
);
|
||||
expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal(
|
||||
erc20Balances[makerAddress][zrxToken.address].minus(signedOrder.makerFee),
|
||||
);
|
||||
expect(newBalances[takerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
|
||||
erc20Balances[takerAddress][defaultTakerAssetAddress].minus(signedOrder.takerAssetAmount),
|
||||
);
|
||||
expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
|
||||
erc20Balances[takerAddress][defaultMakerAssetAddress].add(signedOrder.makerAssetAmount),
|
||||
);
|
||||
expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal(
|
||||
erc20Balances[takerAddress][zrxToken.address].minus(signedOrder.takerFee),
|
||||
);
|
||||
expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal(
|
||||
erc20Balances[feeRecipientAddress][zrxToken.address].add(
|
||||
signedOrder.makerFee.add(signedOrder.takerFee),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('should log 1 event with the correct arguments when order has a feeRecipient', async () => {
|
||||
const divisor = 2;
|
||||
const res = await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, {
|
||||
takerAssetFillAmount: signedOrder.takerAssetAmount.div(divisor),
|
||||
});
|
||||
expect(res.logs).to.have.length(1);
|
||||
|
||||
const log = res.logs[0] as LogWithDecodedArgs<FillContractEventArgs>;
|
||||
const logArgs = log.args;
|
||||
const expectedFilledMakerAssetAmount = signedOrder.makerAssetAmount.div(divisor);
|
||||
const expectedFilledTakerAssetAmount = signedOrder.takerAssetAmount.div(divisor);
|
||||
const expectedFeeMPaid = signedOrder.makerFee.div(divisor);
|
||||
const expectedFeeTPaid = signedOrder.takerFee.div(divisor);
|
||||
|
||||
expect(signedOrder.makerAddress).to.be.equal(logArgs.makerAddress);
|
||||
expect(takerAddress).to.be.equal(logArgs.takerAddress);
|
||||
expect(takerAddress).to.be.equal(logArgs.senderAddress);
|
||||
expect(signedOrder.feeRecipientAddress).to.be.equal(logArgs.feeRecipientAddress);
|
||||
expect(signedOrder.makerAssetData).to.be.equal(logArgs.makerAssetData);
|
||||
expect(signedOrder.takerAssetData).to.be.equal(logArgs.takerAssetData);
|
||||
expect(expectedFilledMakerAssetAmount).to.be.bignumber.equal(logArgs.makerAssetFilledAmount);
|
||||
expect(expectedFilledTakerAssetAmount).to.be.bignumber.equal(logArgs.takerAssetFilledAmount);
|
||||
expect(expectedFeeMPaid).to.be.bignumber.equal(logArgs.makerFeePaid);
|
||||
expect(expectedFeeTPaid).to.be.bignumber.equal(logArgs.takerFeePaid);
|
||||
expect(orderHashUtils.getOrderHashHex(signedOrder)).to.be.equal(logArgs.orderHash);
|
||||
});
|
||||
|
||||
it('should throw when taker is specified and order is claimed by other', async () => {
|
||||
signedOrder = orderFactory.newSignedOrder({
|
||||
takerAddress: feeRecipientAddress,
|
||||
makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18),
|
||||
takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(200), 18),
|
||||
});
|
||||
return expectRevertReasonOrAlwaysFailingTransactionAsync(
|
||||
exchangeWrapper.fillOrderAsync(signedOrder, takerAddress),
|
||||
RevertReason.InvalidTaker,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw if signature is invalid', async () => {
|
||||
signedOrder = orderFactory.newSignedOrder({
|
||||
@@ -439,96 +150,6 @@ describe('Exchange core', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw if makerAssetAmount is 0', async () => {
|
||||
signedOrder = orderFactory.newSignedOrder({
|
||||
makerAssetAmount: new BigNumber(0),
|
||||
});
|
||||
|
||||
return expectRevertReasonOrAlwaysFailingTransactionAsync(
|
||||
exchangeWrapper.fillOrderAsync(signedOrder, takerAddress),
|
||||
RevertReason.OrderUnfillable,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw if takerAssetAmount is 0', async () => {
|
||||
signedOrder = orderFactory.newSignedOrder({
|
||||
takerAssetAmount: new BigNumber(0),
|
||||
});
|
||||
|
||||
return expectRevertReasonOrAlwaysFailingTransactionAsync(
|
||||
exchangeWrapper.fillOrderAsync(signedOrder, takerAddress),
|
||||
RevertReason.OrderUnfillable,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw if takerAssetFillAmount is 0', async () => {
|
||||
signedOrder = orderFactory.newSignedOrder();
|
||||
|
||||
return expectRevertReasonOrAlwaysFailingTransactionAsync(
|
||||
exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, {
|
||||
takerAssetFillAmount: new BigNumber(0),
|
||||
}),
|
||||
RevertReason.InvalidTakerAmount,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw if maker erc20Balances are too low to fill order', async () => {
|
||||
signedOrder = orderFactory.newSignedOrder({
|
||||
makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100000), 18),
|
||||
});
|
||||
|
||||
return expectRevertReasonOrAlwaysFailingTransactionAsync(
|
||||
exchangeWrapper.fillOrderAsync(signedOrder, takerAddress),
|
||||
RevertReason.TransferFailed,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw if taker erc20Balances are too low to fill order', async () => {
|
||||
signedOrder = orderFactory.newSignedOrder({
|
||||
takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100000), 18),
|
||||
});
|
||||
return expectRevertReasonOrAlwaysFailingTransactionAsync(
|
||||
exchangeWrapper.fillOrderAsync(signedOrder, takerAddress),
|
||||
RevertReason.TransferFailed,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw if maker allowances are too low to fill order', async () => {
|
||||
await web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await erc20TokenA.approve.sendTransactionAsync(erc20Proxy.address, new BigNumber(0), {
|
||||
from: makerAddress,
|
||||
}),
|
||||
constants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
return expectRevertReasonOrAlwaysFailingTransactionAsync(
|
||||
exchangeWrapper.fillOrderAsync(signedOrder, takerAddress),
|
||||
RevertReason.TransferFailed,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw if taker allowances are too low to fill order', async () => {
|
||||
await web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await erc20TokenB.approve.sendTransactionAsync(erc20Proxy.address, new BigNumber(0), {
|
||||
from: takerAddress,
|
||||
}),
|
||||
constants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
return expectRevertReasonOrAlwaysFailingTransactionAsync(
|
||||
exchangeWrapper.fillOrderAsync(signedOrder, takerAddress),
|
||||
RevertReason.TransferFailed,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw if an order is expired', async () => {
|
||||
signedOrder = orderFactory.newSignedOrder({
|
||||
expirationTimeSeconds: new BigNumber(Math.floor((Date.now() - 10000) / 1000)),
|
||||
});
|
||||
return expectRevertReasonOrAlwaysFailingTransactionAsync(
|
||||
exchangeWrapper.fillOrderAsync(signedOrder, takerAddress),
|
||||
RevertReason.OrderUnfillable,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw if no value is filled', async () => {
|
||||
signedOrder = orderFactory.newSignedOrder();
|
||||
await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress);
|
||||
@@ -725,32 +346,6 @@ describe('Exchange core', () => {
|
||||
});
|
||||
|
||||
describe('Testing Exchange of ERC721 Tokens', () => {
|
||||
it('should successfully exchange a single token between the maker and taker (via fillOrder)', async () => {
|
||||
// Construct Exchange parameters
|
||||
const makerAssetId = erc721MakerAssetIds[0];
|
||||
const takerAssetId = erc721TakerAssetIds[1];
|
||||
signedOrder = orderFactory.newSignedOrder({
|
||||
makerAssetAmount: new BigNumber(1),
|
||||
takerAssetAmount: new BigNumber(1),
|
||||
makerAssetData: assetProxyUtils.encodeERC721AssetData(erc721Token.address, makerAssetId),
|
||||
takerAssetData: assetProxyUtils.encodeERC721AssetData(erc721Token.address, takerAssetId),
|
||||
});
|
||||
// Verify pre-conditions
|
||||
const initialOwnerMakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId);
|
||||
expect(initialOwnerMakerAsset).to.be.bignumber.equal(makerAddress);
|
||||
const initialOwnerTakerAsset = await erc721Token.ownerOf.callAsync(takerAssetId);
|
||||
expect(initialOwnerTakerAsset).to.be.bignumber.equal(takerAddress);
|
||||
// Call Exchange
|
||||
const takerAssetFillAmount = signedOrder.takerAssetAmount;
|
||||
// tslint:disable-next-line:no-unused-variable
|
||||
const res = await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount });
|
||||
// Verify post-conditions
|
||||
const newOwnerMakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId);
|
||||
expect(newOwnerMakerAsset).to.be.bignumber.equal(takerAddress);
|
||||
const newOwnerTakerAsset = await erc721Token.ownerOf.callAsync(takerAssetId);
|
||||
expect(newOwnerTakerAsset).to.be.bignumber.equal(makerAddress);
|
||||
});
|
||||
|
||||
it('should throw when maker does not own the token with id makerAssetId', async () => {
|
||||
// Construct Exchange parameters
|
||||
const makerAssetId = erc721TakerAssetIds[0];
|
||||
@@ -885,86 +480,6 @@ describe('Exchange core', () => {
|
||||
RevertReason.LengthGreaterThan131Required,
|
||||
);
|
||||
});
|
||||
|
||||
it('should successfully fill order when makerAsset is ERC721 and takerAsset is ERC20', async () => {
|
||||
// Construct Exchange parameters
|
||||
const makerAssetId = erc721MakerAssetIds[0];
|
||||
signedOrder = orderFactory.newSignedOrder({
|
||||
makerAssetAmount: new BigNumber(1),
|
||||
takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18),
|
||||
makerAssetData: assetProxyUtils.encodeERC721AssetData(erc721Token.address, makerAssetId),
|
||||
takerAssetData: assetProxyUtils.encodeERC20AssetData(defaultTakerAssetAddress),
|
||||
});
|
||||
// Verify pre-conditions
|
||||
const initialOwnerMakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId);
|
||||
expect(initialOwnerMakerAsset).to.be.bignumber.equal(makerAddress);
|
||||
// Call Exchange
|
||||
erc20Balances = await erc20Wrapper.getBalancesAsync();
|
||||
const takerAssetFillAmount = signedOrder.takerAssetAmount;
|
||||
await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount });
|
||||
// Verify ERC721 token was transferred from Maker to Taker
|
||||
const newOwnerMakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId);
|
||||
expect(newOwnerMakerAsset).to.be.bignumber.equal(takerAddress);
|
||||
// Verify ERC20 tokens were transferred from Taker to Maker & fees were paid correctly
|
||||
const newBalances = await erc20Wrapper.getBalancesAsync();
|
||||
expect(newBalances[makerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
|
||||
erc20Balances[makerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount),
|
||||
);
|
||||
expect(newBalances[takerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
|
||||
erc20Balances[takerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount),
|
||||
);
|
||||
expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal(
|
||||
erc20Balances[makerAddress][zrxToken.address].minus(signedOrder.makerFee),
|
||||
);
|
||||
expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal(
|
||||
erc20Balances[takerAddress][zrxToken.address].minus(signedOrder.takerFee),
|
||||
);
|
||||
expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal(
|
||||
erc20Balances[feeRecipientAddress][zrxToken.address].add(
|
||||
signedOrder.makerFee.add(signedOrder.takerFee),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('should successfully fill order when makerAsset is ERC20 and takerAsset is ERC721', async () => {
|
||||
// Construct Exchange parameters
|
||||
const takerAssetId = erc721TakerAssetIds[0];
|
||||
signedOrder = orderFactory.newSignedOrder({
|
||||
takerAssetAmount: new BigNumber(1),
|
||||
makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18),
|
||||
takerAssetData: assetProxyUtils.encodeERC721AssetData(erc721Token.address, takerAssetId),
|
||||
makerAssetData: assetProxyUtils.encodeERC20AssetData(defaultMakerAssetAddress),
|
||||
});
|
||||
// Verify pre-conditions
|
||||
const initialOwnerTakerAsset = await erc721Token.ownerOf.callAsync(takerAssetId);
|
||||
expect(initialOwnerTakerAsset).to.be.bignumber.equal(takerAddress);
|
||||
// Call Exchange
|
||||
erc20Balances = await erc20Wrapper.getBalancesAsync();
|
||||
const takerAssetFillAmount = signedOrder.takerAssetAmount;
|
||||
await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount });
|
||||
// Verify ERC721 token was transferred from Taker to Maker
|
||||
const newOwnerTakerAsset = await erc721Token.ownerOf.callAsync(takerAssetId);
|
||||
expect(newOwnerTakerAsset).to.be.bignumber.equal(makerAddress);
|
||||
// Verify ERC20 tokens were transferred from Maker to Taker & fees were paid correctly
|
||||
const newBalances = await erc20Wrapper.getBalancesAsync();
|
||||
expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
|
||||
erc20Balances[takerAddress][defaultMakerAssetAddress].add(signedOrder.makerAssetAmount),
|
||||
);
|
||||
expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
|
||||
erc20Balances[makerAddress][defaultMakerAssetAddress].minus(signedOrder.makerAssetAmount),
|
||||
);
|
||||
expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal(
|
||||
erc20Balances[makerAddress][zrxToken.address].minus(signedOrder.makerFee),
|
||||
);
|
||||
expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal(
|
||||
erc20Balances[takerAddress][zrxToken.address].minus(signedOrder.takerFee),
|
||||
);
|
||||
expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal(
|
||||
erc20Balances[feeRecipientAddress][zrxToken.address].add(
|
||||
signedOrder.makerFee.add(signedOrder.takerFee),
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
// tslint:disable:max-file-line-count
|
||||
|
||||
@@ -3,6 +3,7 @@ import { assetProxyUtils } from '@0xproject/order-utils';
|
||||
import { AssetProxyId, RevertReason } from '@0xproject/types';
|
||||
import { BigNumber } from '@0xproject/utils';
|
||||
import * as chai from 'chai';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { DummyERC20TokenContract } from '../../src/generated_contract_wrappers/dummy_e_r_c20_token';
|
||||
import { ERC20ProxyContract } from '../../src/generated_contract_wrappers/e_r_c20_proxy';
|
||||
@@ -43,12 +44,13 @@ describe('AssetProxyDispatcher', () => {
|
||||
before(async () => {
|
||||
// Setup accounts & addresses
|
||||
const accounts = await web3Wrapper.getAvailableAddressesAsync();
|
||||
const usedAddresses = ([owner, notOwner, makerAddress, takerAddress] = accounts);
|
||||
const usedAddresses = ([owner, notOwner, makerAddress, takerAddress] = _.slice(accounts, 0, 4));
|
||||
|
||||
erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner);
|
||||
erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner);
|
||||
|
||||
[zrxToken] = await erc20Wrapper.deployDummyTokensAsync();
|
||||
const numDummyErc20ToDeploy = 1;
|
||||
[zrxToken] = await erc20Wrapper.deployDummyTokensAsync(numDummyErc20ToDeploy, constants.DUMMY_TOKEN_DECIMALS);
|
||||
erc20Proxy = await erc20Wrapper.deployProxyAsync();
|
||||
await erc20Wrapper.setBalancesAndAllowancesAsync();
|
||||
|
||||
|
||||
306
packages/contracts/test/exchange/fill_order.ts
Normal file
306
packages/contracts/test/exchange/fill_order.ts
Normal file
@@ -0,0 +1,306 @@
|
||||
import { BlockchainLifecycle } from '@0xproject/dev-utils';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { chaiSetup } from '../../src/utils/chai_setup';
|
||||
import { CoreCombinatorialUtils, coreCombinatorialUtilsFactoryAsync } from '../../src/utils/core_combinatorial_utils';
|
||||
import { provider, txDefaults, web3Wrapper } from '../../src/utils/web3_wrapper';
|
||||
|
||||
import {
|
||||
AllowanceAmountScenario,
|
||||
AssetDataScenario,
|
||||
BalanceAmountScenario,
|
||||
ExpirationTimeSecondsScenario,
|
||||
FeeRecipientAddressScenario,
|
||||
FillScenario,
|
||||
OrderAssetAmountScenario,
|
||||
TakerAssetFillAmountScenario,
|
||||
TakerScenario,
|
||||
} from '../../src/utils/types';
|
||||
|
||||
chaiSetup.configure();
|
||||
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
|
||||
|
||||
const defaultFillScenario = {
|
||||
orderScenario: {
|
||||
takerScenario: TakerScenario.Unspecified,
|
||||
feeRecipientScenario: FeeRecipientAddressScenario.EthUserAddress,
|
||||
makerAssetAmountScenario: OrderAssetAmountScenario.Large,
|
||||
takerAssetAmountScenario: OrderAssetAmountScenario.Large,
|
||||
makerFeeScenario: OrderAssetAmountScenario.Large,
|
||||
takerFeeScenario: OrderAssetAmountScenario.Large,
|
||||
expirationTimeSecondsScenario: ExpirationTimeSecondsScenario.InFuture,
|
||||
makerAssetDataScenario: AssetDataScenario.ERC20NonZRXEighteenDecimals,
|
||||
takerAssetDataScenario: AssetDataScenario.ERC20NonZRXEighteenDecimals,
|
||||
},
|
||||
takerAssetFillAmountScenario: TakerAssetFillAmountScenario.LessThanRemainingFillableTakerAssetAmount,
|
||||
makerStateScenario: {
|
||||
traderAssetBalance: BalanceAmountScenario.Higher,
|
||||
traderAssetAllowance: AllowanceAmountScenario.Higher,
|
||||
zrxFeeBalance: BalanceAmountScenario.Higher,
|
||||
zrxFeeAllowance: AllowanceAmountScenario.Higher,
|
||||
},
|
||||
takerStateScenario: {
|
||||
traderAssetBalance: BalanceAmountScenario.Higher,
|
||||
traderAssetAllowance: AllowanceAmountScenario.Higher,
|
||||
zrxFeeBalance: BalanceAmountScenario.Higher,
|
||||
zrxFeeAllowance: AllowanceAmountScenario.Higher,
|
||||
},
|
||||
};
|
||||
|
||||
describe('FillOrder Tests', () => {
|
||||
let coreCombinatorialUtils: CoreCombinatorialUtils;
|
||||
|
||||
before(async () => {
|
||||
await blockchainLifecycle.startAsync();
|
||||
coreCombinatorialUtils = await coreCombinatorialUtilsFactoryAsync(web3Wrapper, txDefaults);
|
||||
});
|
||||
after(async () => {
|
||||
await blockchainLifecycle.revertAsync();
|
||||
});
|
||||
beforeEach(async () => {
|
||||
await blockchainLifecycle.startAsync();
|
||||
});
|
||||
afterEach(async () => {
|
||||
await blockchainLifecycle.revertAsync();
|
||||
});
|
||||
describe('fillOrder', () => {
|
||||
const test = (fillScenarios: FillScenario[]) => {
|
||||
_.forEach(fillScenarios, fillScenario => {
|
||||
const orderScenario = fillScenario.orderScenario;
|
||||
const description = `Combinatorial OrderFill: ${orderScenario.feeRecipientScenario} ${
|
||||
orderScenario.makerAssetAmountScenario
|
||||
} ${orderScenario.takerAssetAmountScenario} ${orderScenario.makerFeeScenario} ${
|
||||
orderScenario.takerFeeScenario
|
||||
} ${orderScenario.expirationTimeSecondsScenario} ${orderScenario.makerAssetDataScenario} ${
|
||||
orderScenario.takerAssetDataScenario
|
||||
}`;
|
||||
it(description, async () => {
|
||||
await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const allFillScenarios = CoreCombinatorialUtils.generateFillOrderCombinations();
|
||||
describe('Combinatorially generated fills orders', () => test(allFillScenarios));
|
||||
|
||||
it('should transfer the correct amounts when makerAssetAmount === takerAssetAmount', async () => {
|
||||
const fillScenario = {
|
||||
...defaultFillScenario,
|
||||
};
|
||||
await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario);
|
||||
});
|
||||
it('should transfer the correct amounts when makerAssetAmount > takerAssetAmount', async () => {
|
||||
const fillScenario = {
|
||||
...defaultFillScenario,
|
||||
orderScenario: {
|
||||
...defaultFillScenario.orderScenario,
|
||||
takerAssetAmountScenario: OrderAssetAmountScenario.Small,
|
||||
},
|
||||
};
|
||||
await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario);
|
||||
});
|
||||
it('should transfer the correct amounts when makerAssetAmount < takerAssetAmount', async () => {
|
||||
const fillScenario = {
|
||||
...defaultFillScenario,
|
||||
orderScenario: {
|
||||
...defaultFillScenario.orderScenario,
|
||||
makerAssetAmountScenario: OrderAssetAmountScenario.Small,
|
||||
},
|
||||
};
|
||||
await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario);
|
||||
});
|
||||
it('should transfer the correct amounts when taker is specified and order is claimed by taker', async () => {
|
||||
const fillScenario = {
|
||||
...defaultFillScenario,
|
||||
orderScenario: {
|
||||
...defaultFillScenario.orderScenario,
|
||||
takerScenario: TakerScenario.CorrectlySpecified,
|
||||
},
|
||||
};
|
||||
await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario);
|
||||
});
|
||||
it('should fill remaining value if takerAssetFillAmount > remaining takerAssetAmount', async () => {
|
||||
const fillScenario = {
|
||||
...defaultFillScenario,
|
||||
takerAssetFillAmountScenario: TakerAssetFillAmountScenario.GreaterThanRemainingFillableTakerAssetAmount,
|
||||
};
|
||||
await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario);
|
||||
});
|
||||
it('should throw when taker is specified and order is claimed by other', async () => {
|
||||
const fillScenario = {
|
||||
...defaultFillScenario,
|
||||
orderScenario: {
|
||||
...defaultFillScenario.orderScenario,
|
||||
takerScenario: TakerScenario.IncorrectlySpecified,
|
||||
},
|
||||
};
|
||||
await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario);
|
||||
});
|
||||
|
||||
it('should throw if makerAssetAmount is 0', async () => {
|
||||
const fillScenario = {
|
||||
...defaultFillScenario,
|
||||
orderScenario: {
|
||||
...defaultFillScenario.orderScenario,
|
||||
makerAssetAmountScenario: OrderAssetAmountScenario.Zero,
|
||||
},
|
||||
takerAssetFillAmountScenario: TakerAssetFillAmountScenario.GreaterThanRemainingFillableTakerAssetAmount,
|
||||
};
|
||||
await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario);
|
||||
});
|
||||
|
||||
it('should throw if takerAssetAmount is 0', async () => {
|
||||
const fillScenario = {
|
||||
...defaultFillScenario,
|
||||
orderScenario: {
|
||||
...defaultFillScenario.orderScenario,
|
||||
takerAssetAmountScenario: OrderAssetAmountScenario.Zero,
|
||||
},
|
||||
takerAssetFillAmountScenario: TakerAssetFillAmountScenario.GreaterThanRemainingFillableTakerAssetAmount,
|
||||
};
|
||||
await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario);
|
||||
});
|
||||
|
||||
it('should throw if takerAssetFillAmount is 0', async () => {
|
||||
const fillScenario = {
|
||||
...defaultFillScenario,
|
||||
takerAssetFillAmountScenario: TakerAssetFillAmountScenario.Zero,
|
||||
};
|
||||
await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario);
|
||||
});
|
||||
|
||||
it('should throw if an order is expired', async () => {
|
||||
const fillScenario = {
|
||||
...defaultFillScenario,
|
||||
orderScenario: {
|
||||
...defaultFillScenario.orderScenario,
|
||||
expirationTimeSecondsScenario: ExpirationTimeSecondsScenario.InPast,
|
||||
},
|
||||
};
|
||||
await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario);
|
||||
});
|
||||
|
||||
it('should throw if maker erc20Balances are too low to fill order', async () => {
|
||||
const fillScenario = {
|
||||
...defaultFillScenario,
|
||||
makerStateScenario: {
|
||||
...defaultFillScenario.makerStateScenario,
|
||||
traderAssetBalance: BalanceAmountScenario.TooLow,
|
||||
},
|
||||
};
|
||||
await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario);
|
||||
});
|
||||
|
||||
it('should throw if taker erc20Balances are too low to fill order', async () => {
|
||||
const fillScenario = {
|
||||
...defaultFillScenario,
|
||||
takerStateScenario: {
|
||||
...defaultFillScenario.makerStateScenario,
|
||||
traderAssetBalance: BalanceAmountScenario.TooLow,
|
||||
},
|
||||
};
|
||||
await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario);
|
||||
});
|
||||
|
||||
it('should throw if maker allowances are too low to fill order', async () => {
|
||||
const fillScenario = {
|
||||
...defaultFillScenario,
|
||||
makerStateScenario: {
|
||||
...defaultFillScenario.makerStateScenario,
|
||||
traderAssetAllowance: AllowanceAmountScenario.TooLow,
|
||||
},
|
||||
};
|
||||
await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario);
|
||||
});
|
||||
|
||||
it('should throw if taker allowances are too low to fill order', async () => {
|
||||
const fillScenario = {
|
||||
...defaultFillScenario,
|
||||
takerStateScenario: {
|
||||
...defaultFillScenario.makerStateScenario,
|
||||
traderAssetAllowance: AllowanceAmountScenario.TooLow,
|
||||
},
|
||||
};
|
||||
await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Testing Exchange of ERC721 Tokens', () => {
|
||||
it('should successfully exchange a single token between the maker and taker (via fillOrder)', async () => {
|
||||
const fillScenario = {
|
||||
...defaultFillScenario,
|
||||
orderScenario: {
|
||||
...defaultFillScenario.orderScenario,
|
||||
makerAssetDataScenario: AssetDataScenario.ERC721,
|
||||
takerAssetDataScenario: AssetDataScenario.ERC721,
|
||||
},
|
||||
takerAssetFillAmountScenario: TakerAssetFillAmountScenario.ExactlyRemainingFillableTakerAssetAmount,
|
||||
};
|
||||
await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario);
|
||||
});
|
||||
|
||||
it('should successfully fill order when makerAsset is ERC721 and takerAsset is ERC20', async () => {
|
||||
const fillScenario = {
|
||||
...defaultFillScenario,
|
||||
orderScenario: {
|
||||
...defaultFillScenario.orderScenario,
|
||||
makerAssetDataScenario: AssetDataScenario.ERC721,
|
||||
takerAssetDataScenario: AssetDataScenario.ERC20NonZRXEighteenDecimals,
|
||||
},
|
||||
takerAssetFillAmountScenario: TakerAssetFillAmountScenario.ExactlyRemainingFillableTakerAssetAmount,
|
||||
};
|
||||
await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario, true);
|
||||
});
|
||||
|
||||
it('should successfully fill order when makerAsset is ERC20 and takerAsset is ERC721', async () => {
|
||||
const fillScenario = {
|
||||
...defaultFillScenario,
|
||||
orderScenario: {
|
||||
...defaultFillScenario.orderScenario,
|
||||
makerAssetDataScenario: AssetDataScenario.ERC20NonZRXEighteenDecimals,
|
||||
takerAssetDataScenario: AssetDataScenario.ERC721,
|
||||
},
|
||||
takerAssetFillAmountScenario: TakerAssetFillAmountScenario.ExactlyRemainingFillableTakerAssetAmount,
|
||||
};
|
||||
await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario);
|
||||
});
|
||||
|
||||
it('should successfully fill order when makerAsset is ERC721 and approveAll is set for it', async () => {
|
||||
const fillScenario = {
|
||||
...defaultFillScenario,
|
||||
orderScenario: {
|
||||
...defaultFillScenario.orderScenario,
|
||||
makerAssetDataScenario: AssetDataScenario.ERC721,
|
||||
takerAssetDataScenario: AssetDataScenario.ERC20NonZRXEighteenDecimals,
|
||||
},
|
||||
takerAssetFillAmountScenario: TakerAssetFillAmountScenario.ExactlyRemainingFillableTakerAssetAmount,
|
||||
makerStateScenario: {
|
||||
...defaultFillScenario.makerStateScenario,
|
||||
traderAssetAllowance: AllowanceAmountScenario.Unlimited,
|
||||
},
|
||||
};
|
||||
await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario);
|
||||
});
|
||||
|
||||
it('should successfully fill order when makerAsset and takerAsset are ERC721 and approveAll is set for them', async () => {
|
||||
const fillScenario = {
|
||||
...defaultFillScenario,
|
||||
orderScenario: {
|
||||
...defaultFillScenario.orderScenario,
|
||||
makerAssetDataScenario: AssetDataScenario.ERC721,
|
||||
takerAssetDataScenario: AssetDataScenario.ERC721,
|
||||
},
|
||||
takerAssetFillAmountScenario: TakerAssetFillAmountScenario.ExactlyRemainingFillableTakerAssetAmount,
|
||||
makerStateScenario: {
|
||||
...defaultFillScenario.makerStateScenario,
|
||||
traderAssetAllowance: AllowanceAmountScenario.Unlimited,
|
||||
},
|
||||
takerStateScenario: {
|
||||
...defaultFillScenario.takerStateScenario,
|
||||
traderAssetAllowance: AllowanceAmountScenario.Unlimited,
|
||||
},
|
||||
};
|
||||
await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -76,12 +76,16 @@ describe('matchOrders', () => {
|
||||
takerAddress,
|
||||
feeRecipientAddressLeft,
|
||||
feeRecipientAddressRight,
|
||||
] = accounts);
|
||||
] = _.slice(accounts, 0, 6));
|
||||
// Create wrappers
|
||||
erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner);
|
||||
erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner);
|
||||
// Deploy ERC20 token & ERC20 proxy
|
||||
[erc20TokenA, erc20TokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync();
|
||||
const numDummyErc20ToDeploy = 3;
|
||||
[erc20TokenA, erc20TokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync(
|
||||
numDummyErc20ToDeploy,
|
||||
constants.DUMMY_TOKEN_DECIMALS,
|
||||
);
|
||||
erc20Proxy = await erc20Wrapper.deployProxyAsync();
|
||||
await erc20Wrapper.setBalancesAndAllowancesAsync();
|
||||
// Deploy ERC721 token and proxy
|
||||
|
||||
@@ -3,6 +3,7 @@ import { assetProxyUtils, generatePseudoRandomSalt } from '@0xproject/order-util
|
||||
import { AssetProxyId, OrderWithoutExchangeAddress, RevertReason, SignedOrder } from '@0xproject/types';
|
||||
import { BigNumber } from '@0xproject/utils';
|
||||
import * as chai from 'chai';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { DummyERC20TokenContract } from '../../src/generated_contract_wrappers/dummy_e_r_c20_token';
|
||||
import { ERC20ProxyContract } from '../../src/generated_contract_wrappers/e_r_c20_proxy';
|
||||
@@ -67,11 +68,19 @@ describe('Exchange transactions', () => {
|
||||
});
|
||||
before(async () => {
|
||||
const accounts = await web3Wrapper.getAvailableAddressesAsync();
|
||||
const usedAddresses = ([owner, senderAddress, makerAddress, takerAddress, feeRecipientAddress] = accounts);
|
||||
const usedAddresses = ([owner, senderAddress, makerAddress, takerAddress, feeRecipientAddress] = _.slice(
|
||||
accounts,
|
||||
0,
|
||||
5,
|
||||
));
|
||||
|
||||
erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner);
|
||||
|
||||
[erc20TokenA, erc20TokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync();
|
||||
const numDummyErc20ToDeploy = 3;
|
||||
[erc20TokenA, erc20TokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync(
|
||||
numDummyErc20ToDeploy,
|
||||
constants.DUMMY_TOKEN_DECIMALS,
|
||||
);
|
||||
erc20Proxy = await erc20Wrapper.deployProxyAsync();
|
||||
await erc20Wrapper.setBalancesAndAllowancesAsync();
|
||||
|
||||
|
||||
@@ -60,12 +60,16 @@ describe('Exchange wrappers', () => {
|
||||
});
|
||||
before(async () => {
|
||||
const accounts = await web3Wrapper.getAvailableAddressesAsync();
|
||||
const usedAddresses = ([owner, makerAddress, takerAddress, feeRecipientAddress] = accounts);
|
||||
const usedAddresses = ([owner, makerAddress, takerAddress, feeRecipientAddress] = _.slice(accounts, 0, 4));
|
||||
|
||||
erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner);
|
||||
erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner);
|
||||
|
||||
[erc20TokenA, erc20TokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync();
|
||||
const numDummyErc20ToDeploy = 3;
|
||||
[erc20TokenA, erc20TokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync(
|
||||
numDummyErc20ToDeploy,
|
||||
constants.DUMMY_TOKEN_DECIMALS,
|
||||
);
|
||||
erc20Proxy = await erc20Wrapper.deployProxyAsync();
|
||||
await erc20Wrapper.setBalancesAndAllowancesAsync();
|
||||
|
||||
@@ -370,7 +374,7 @@ describe('Exchange wrappers', () => {
|
||||
// HACK(albrow): We need to hardcode the gas estimate here because
|
||||
// the Geth gas estimator doesn't work with the way we use
|
||||
// delegatecall and swallow errors.
|
||||
gas: 270000,
|
||||
gas: 280000,
|
||||
});
|
||||
// Verify post-conditions
|
||||
const newOwnerMakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId);
|
||||
|
||||
@@ -3,5 +3,5 @@ import { BigNumber } from '@0xproject/utils';
|
||||
export abstract class AbstractOrderFilledCancelledFetcher {
|
||||
public abstract async getFilledTakerAmountAsync(orderHash: string): Promise<BigNumber>;
|
||||
public abstract async isOrderCancelledAsync(orderHash: string): Promise<boolean>;
|
||||
public abstract getZRXTokenAddress(): string;
|
||||
public abstract getZRXAssetData(): string;
|
||||
}
|
||||
|
||||
@@ -90,6 +90,9 @@ export class ExchangeTransferSimulator {
|
||||
amountInBaseUnits: BigNumber,
|
||||
): Promise<void> {
|
||||
const proxyAllowance = await this._store.getProxyAllowanceAsync(assetData, userAddress);
|
||||
// HACK: This code assumes that all tokens with an UNLIMITED_ALLOWANCE_IN_BASE_UNITS set,
|
||||
// are UnlimitedAllowanceTokens. This is however not true, it just so happens that all
|
||||
// DummyERC20Tokens we use in tests are.
|
||||
if (!proxyAllowance.eq(constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS)) {
|
||||
this._store.setProxyAllowance(assetData, userAddress, proxyAllowance.minus(amountInBaseUnits));
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ export { generatePseudoRandomSalt } from './salt';
|
||||
export { OrderError, MessagePrefixType, MessagePrefixOpts, EIP712Parameter, EIP712Schema, EIP712Types } from './types';
|
||||
export { AbstractBalanceAndProxyAllowanceFetcher } from './abstract/abstract_balance_and_proxy_allowance_fetcher';
|
||||
export { AbstractOrderFilledCancelledFetcher } from './abstract/abstract_order_filled_cancelled_fetcher';
|
||||
export { BalanceAndProxyAllowanceLazyStore } from './store/balance_and_proxy_allowance_lazy_store';
|
||||
export { RemainingFillableCalculator } from './remaining_fillable_calculator';
|
||||
export { OrderStateUtils } from './order_state_utils';
|
||||
export { assetProxyUtils } from './asset_proxy_utils';
|
||||
|
||||
@@ -10,40 +10,81 @@ import { BigNumber } from '@0xproject/utils';
|
||||
|
||||
import { AbstractBalanceAndProxyAllowanceFetcher } from './abstract/abstract_balance_and_proxy_allowance_fetcher';
|
||||
import { AbstractOrderFilledCancelledFetcher } from './abstract/abstract_order_filled_cancelled_fetcher';
|
||||
import { assetProxyUtils } from './asset_proxy_utils';
|
||||
import { orderHashUtils } from './order_hash';
|
||||
import { RemainingFillableCalculator } from './remaining_fillable_calculator';
|
||||
import { utils } from './utils';
|
||||
|
||||
interface SidedOrderRelevantState {
|
||||
isMakerSide: boolean;
|
||||
traderBalance: BigNumber;
|
||||
traderProxyAllowance: BigNumber;
|
||||
traderFeeBalance: BigNumber;
|
||||
traderFeeProxyAllowance: BigNumber;
|
||||
filledTakerAssetAmount: BigNumber;
|
||||
remainingFillableAssetAmount: BigNumber;
|
||||
}
|
||||
|
||||
const ACCEPTABLE_RELATIVE_ROUNDING_ERROR = 0.0001;
|
||||
|
||||
export class OrderStateUtils {
|
||||
private _balanceAndProxyAllowanceFetcher: AbstractBalanceAndProxyAllowanceFetcher;
|
||||
private _orderFilledCancelledFetcher: AbstractOrderFilledCancelledFetcher;
|
||||
private static _validateIfOrderIsValid(signedOrder: SignedOrder, orderRelevantState: OrderRelevantState): void {
|
||||
const availableTakerAssetAmount = signedOrder.takerAssetAmount.minus(orderRelevantState.filledTakerAssetAmount);
|
||||
private static _validateIfOrderIsValid(
|
||||
signedOrder: SignedOrder,
|
||||
sidedOrderRelevantState: SidedOrderRelevantState,
|
||||
): void {
|
||||
const isMakerSide = sidedOrderRelevantState.isMakerSide;
|
||||
const availableTakerAssetAmount = signedOrder.takerAssetAmount.minus(
|
||||
sidedOrderRelevantState.filledTakerAssetAmount,
|
||||
);
|
||||
if (availableTakerAssetAmount.eq(0)) {
|
||||
throw new Error(ExchangeContractErrs.OrderRemainingFillAmountZero);
|
||||
}
|
||||
|
||||
if (orderRelevantState.makerBalance.eq(0)) {
|
||||
throw new Error(ExchangeContractErrs.InsufficientMakerBalance);
|
||||
if (sidedOrderRelevantState.traderBalance.eq(0)) {
|
||||
throw new Error(
|
||||
isMakerSide
|
||||
? ExchangeContractErrs.InsufficientMakerBalance
|
||||
: ExchangeContractErrs.InsufficientTakerBalance,
|
||||
);
|
||||
}
|
||||
if (orderRelevantState.makerProxyAllowance.eq(0)) {
|
||||
throw new Error(ExchangeContractErrs.InsufficientMakerAllowance);
|
||||
if (sidedOrderRelevantState.traderProxyAllowance.eq(0)) {
|
||||
throw new Error(
|
||||
isMakerSide
|
||||
? ExchangeContractErrs.InsufficientMakerAllowance
|
||||
: ExchangeContractErrs.InsufficientTakerAllowance,
|
||||
);
|
||||
}
|
||||
if (!signedOrder.makerFee.eq(0)) {
|
||||
if (orderRelevantState.makerFeeBalance.eq(0)) {
|
||||
throw new Error(ExchangeContractErrs.InsufficientMakerFeeBalance);
|
||||
if (sidedOrderRelevantState.traderFeeBalance.eq(0)) {
|
||||
throw new Error(
|
||||
isMakerSide
|
||||
? ExchangeContractErrs.InsufficientMakerFeeBalance
|
||||
: ExchangeContractErrs.InsufficientTakerFeeBalance,
|
||||
);
|
||||
}
|
||||
if (orderRelevantState.makerFeeProxyAllowance.eq(0)) {
|
||||
throw new Error(ExchangeContractErrs.InsufficientMakerFeeAllowance);
|
||||
if (sidedOrderRelevantState.traderFeeProxyAllowance.eq(0)) {
|
||||
throw new Error(
|
||||
isMakerSide
|
||||
? ExchangeContractErrs.InsufficientMakerFeeAllowance
|
||||
: ExchangeContractErrs.InsufficientTakerFeeAllowance,
|
||||
);
|
||||
}
|
||||
}
|
||||
const minFillableTakerAssetAmountWithinNoRoundingErrorRange = signedOrder.takerAssetAmount
|
||||
.dividedBy(ACCEPTABLE_RELATIVE_ROUNDING_ERROR)
|
||||
.dividedBy(signedOrder.makerAssetAmount);
|
||||
|
||||
let minFillableTakerAssetAmountWithinNoRoundingErrorRange;
|
||||
if (isMakerSide) {
|
||||
minFillableTakerAssetAmountWithinNoRoundingErrorRange = signedOrder.takerAssetAmount
|
||||
.dividedBy(ACCEPTABLE_RELATIVE_ROUNDING_ERROR)
|
||||
.dividedBy(signedOrder.makerAssetAmount);
|
||||
} else {
|
||||
minFillableTakerAssetAmountWithinNoRoundingErrorRange = signedOrder.makerAssetAmount
|
||||
.dividedBy(ACCEPTABLE_RELATIVE_ROUNDING_ERROR)
|
||||
.dividedBy(signedOrder.takerAssetAmount);
|
||||
}
|
||||
|
||||
if (
|
||||
orderRelevantState.remainingFillableTakerAssetAmount.lessThan(
|
||||
sidedOrderRelevantState.remainingFillableAssetAmount.lessThan(
|
||||
minFillableTakerAssetAmountWithinNoRoundingErrorRange,
|
||||
)
|
||||
) {
|
||||
@@ -57,11 +98,20 @@ export class OrderStateUtils {
|
||||
this._balanceAndProxyAllowanceFetcher = balanceAndProxyAllowanceFetcher;
|
||||
this._orderFilledCancelledFetcher = orderFilledCancelledFetcher;
|
||||
}
|
||||
public async getOrderStateAsync(signedOrder: SignedOrder): Promise<OrderState> {
|
||||
const orderRelevantState = await this.getOrderRelevantStateAsync(signedOrder);
|
||||
public async getOpenOrderStateAsync(signedOrder: SignedOrder): Promise<OrderState> {
|
||||
const orderRelevantState = await this.getOpenOrderRelevantStateAsync(signedOrder);
|
||||
const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
|
||||
const sidedOrderRelevantState = {
|
||||
isMakerSide: true,
|
||||
traderBalance: orderRelevantState.makerBalance,
|
||||
traderProxyAllowance: orderRelevantState.makerProxyAllowance,
|
||||
traderFeeBalance: orderRelevantState.makerFeeBalance,
|
||||
traderFeeProxyAllowance: orderRelevantState.makerFeeProxyAllowance,
|
||||
filledTakerAssetAmount: orderRelevantState.filledTakerAssetAmount,
|
||||
remainingFillableAssetAmount: orderRelevantState.remainingFillableMakerAssetAmount,
|
||||
};
|
||||
try {
|
||||
OrderStateUtils._validateIfOrderIsValid(signedOrder, orderRelevantState);
|
||||
OrderStateUtils._validateIfOrderIsValid(signedOrder, sidedOrderRelevantState);
|
||||
const orderState: OrderStateValid = {
|
||||
isValid: true,
|
||||
orderHash,
|
||||
@@ -77,63 +127,158 @@ export class OrderStateUtils {
|
||||
return orderState;
|
||||
}
|
||||
}
|
||||
public async getOrderRelevantStateAsync(signedOrder: SignedOrder): Promise<OrderRelevantState> {
|
||||
const zrxTokenAddress = this._orderFilledCancelledFetcher.getZRXTokenAddress();
|
||||
const makerProxyData = assetProxyUtils.decodeERC20AssetData(signedOrder.makerAssetData);
|
||||
const makerAssetAddress = makerProxyData.tokenAddress;
|
||||
const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
|
||||
const makerBalance = await this._balanceAndProxyAllowanceFetcher.getBalanceAsync(
|
||||
makerAssetAddress,
|
||||
signedOrder.makerAddress,
|
||||
public async getOpenOrderRelevantStateAsync(signedOrder: SignedOrder): Promise<OrderRelevantState> {
|
||||
const isMaker = true;
|
||||
const sidedOrderRelevantState = await this._getSidedOrderRelevantStateAsync(
|
||||
isMaker,
|
||||
signedOrder,
|
||||
signedOrder.takerAddress,
|
||||
);
|
||||
const makerProxyAllowance = await this._balanceAndProxyAllowanceFetcher.getProxyAllowanceAsync(
|
||||
makerAssetAddress,
|
||||
signedOrder.makerAddress,
|
||||
);
|
||||
const makerFeeBalance = await this._balanceAndProxyAllowanceFetcher.getBalanceAsync(
|
||||
zrxTokenAddress,
|
||||
signedOrder.makerAddress,
|
||||
);
|
||||
const makerFeeProxyAllowance = await this._balanceAndProxyAllowanceFetcher.getProxyAllowanceAsync(
|
||||
zrxTokenAddress,
|
||||
signedOrder.makerAddress,
|
||||
);
|
||||
const filledTakerAssetAmount = await this._orderFilledCancelledFetcher.getFilledTakerAmountAsync(orderHash);
|
||||
const isOrderCancelled = await this._orderFilledCancelledFetcher.isOrderCancelledAsync(orderHash);
|
||||
const totalMakerAssetAmount = signedOrder.makerAssetAmount;
|
||||
const totalTakerAssetAmount = signedOrder.takerAssetAmount;
|
||||
const remainingTakerAssetAmount = isOrderCancelled
|
||||
? new BigNumber(0)
|
||||
: totalTakerAssetAmount.minus(filledTakerAssetAmount);
|
||||
const remainingMakerAssetAmount = remainingTakerAssetAmount
|
||||
.times(totalMakerAssetAmount)
|
||||
.dividedToIntegerBy(totalTakerAssetAmount);
|
||||
const transferrableMakerAssetAmount = BigNumber.min([makerProxyAllowance, makerBalance]);
|
||||
const transferrableFeeAssetAmount = BigNumber.min([makerFeeProxyAllowance, makerFeeBalance]);
|
||||
|
||||
const zrxAssetData = assetProxyUtils.encodeERC20AssetData(zrxTokenAddress);
|
||||
const isMakerAssetZRX = signedOrder.makerAssetData === zrxAssetData;
|
||||
const remainingFillableCalculator = new RemainingFillableCalculator(
|
||||
signedOrder.makerFee,
|
||||
signedOrder.makerAssetAmount,
|
||||
isMakerAssetZRX,
|
||||
transferrableMakerAssetAmount,
|
||||
transferrableFeeAssetAmount,
|
||||
remainingMakerAssetAmount,
|
||||
);
|
||||
const remainingFillableMakerAssetAmount = remainingFillableCalculator.computeRemainingFillable();
|
||||
const remainingFillableTakerAssetAmount = remainingFillableMakerAssetAmount
|
||||
const remainingFillableTakerAssetAmount = sidedOrderRelevantState.remainingFillableAssetAmount
|
||||
.times(signedOrder.takerAssetAmount)
|
||||
.dividedToIntegerBy(signedOrder.makerAssetAmount);
|
||||
|
||||
const orderRelevantState = {
|
||||
makerBalance,
|
||||
makerProxyAllowance,
|
||||
makerFeeBalance,
|
||||
makerFeeProxyAllowance,
|
||||
filledTakerAssetAmount,
|
||||
remainingFillableMakerAssetAmount,
|
||||
makerBalance: sidedOrderRelevantState.traderBalance,
|
||||
makerProxyAllowance: sidedOrderRelevantState.traderProxyAllowance,
|
||||
makerFeeBalance: sidedOrderRelevantState.traderFeeBalance,
|
||||
makerFeeProxyAllowance: sidedOrderRelevantState.traderFeeProxyAllowance,
|
||||
filledTakerAssetAmount: sidedOrderRelevantState.filledTakerAssetAmount,
|
||||
remainingFillableMakerAssetAmount: sidedOrderRelevantState.remainingFillableAssetAmount,
|
||||
remainingFillableTakerAssetAmount,
|
||||
};
|
||||
return orderRelevantState;
|
||||
}
|
||||
public async getMaxFillableTakerAssetAmountAsync(
|
||||
signedOrder: SignedOrder,
|
||||
takerAddress: string,
|
||||
): Promise<BigNumber> {
|
||||
// Get max fillable amount for an order, considering the makers ability to fill
|
||||
let isMaker = true;
|
||||
const orderRelevantMakerState = await this._getSidedOrderRelevantStateAsync(
|
||||
isMaker,
|
||||
signedOrder,
|
||||
signedOrder.takerAddress,
|
||||
);
|
||||
const remainingFillableTakerAssetAmountGivenMakersStatus = signedOrder.makerAssetAmount.eq(0)
|
||||
? new BigNumber(0)
|
||||
: utils.getPartialAmount(
|
||||
orderRelevantMakerState.remainingFillableAssetAmount,
|
||||
signedOrder.makerAssetAmount,
|
||||
signedOrder.takerAssetAmount,
|
||||
);
|
||||
|
||||
// Get max fillable amount for an order, considering the takers ability to fill
|
||||
isMaker = false;
|
||||
const orderRelevantTakerState = await this._getSidedOrderRelevantStateAsync(isMaker, signedOrder, takerAddress);
|
||||
const remainingFillableTakerAssetAmountGivenTakersStatus = orderRelevantTakerState.remainingFillableAssetAmount;
|
||||
|
||||
// The min of these two in the actualy max fillable by either party
|
||||
const fillableTakerAssetAmount = BigNumber.min([
|
||||
remainingFillableTakerAssetAmountGivenMakersStatus,
|
||||
remainingFillableTakerAssetAmountGivenTakersStatus,
|
||||
]);
|
||||
|
||||
return fillableTakerAssetAmount;
|
||||
}
|
||||
public async getMaxFillableTakerAssetAmountForFailingOrderAsync(
|
||||
signedOrder: SignedOrder,
|
||||
takerAddress: string,
|
||||
): Promise<BigNumber> {
|
||||
// Get min of taker balance & allowance
|
||||
const takerAssetBalanceOfTaker = await this._balanceAndProxyAllowanceFetcher.getBalanceAsync(
|
||||
signedOrder.takerAssetData,
|
||||
takerAddress,
|
||||
);
|
||||
const takerAssetAllowanceOfTaker = await this._balanceAndProxyAllowanceFetcher.getProxyAllowanceAsync(
|
||||
signedOrder.takerAssetData,
|
||||
takerAddress,
|
||||
);
|
||||
const minTakerAssetAmount = BigNumber.min([takerAssetBalanceOfTaker, takerAssetAllowanceOfTaker]);
|
||||
|
||||
// get remainingFillAmount
|
||||
const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
|
||||
const filledTakerAssetAmount = await this._orderFilledCancelledFetcher.getFilledTakerAmountAsync(orderHash);
|
||||
const remainingFillTakerAssetAmount = signedOrder.takerAssetAmount.minus(filledTakerAssetAmount);
|
||||
|
||||
if (minTakerAssetAmount.gte(remainingFillTakerAssetAmount)) {
|
||||
return remainingFillTakerAssetAmount;
|
||||
} else {
|
||||
return minTakerAssetAmount;
|
||||
}
|
||||
}
|
||||
private async _getSidedOrderRelevantStateAsync(
|
||||
isMakerSide: boolean,
|
||||
signedOrder: SignedOrder,
|
||||
takerAddress: string,
|
||||
): Promise<SidedOrderRelevantState> {
|
||||
let traderAddress;
|
||||
let assetData;
|
||||
let assetAmount;
|
||||
let feeAmount;
|
||||
if (isMakerSide) {
|
||||
traderAddress = signedOrder.makerAddress;
|
||||
assetData = signedOrder.makerAssetData;
|
||||
assetAmount = signedOrder.makerAssetAmount;
|
||||
feeAmount = signedOrder.makerFee;
|
||||
} else {
|
||||
traderAddress = takerAddress;
|
||||
assetData = signedOrder.takerAssetData;
|
||||
assetAmount = signedOrder.takerAssetAmount;
|
||||
feeAmount = signedOrder.takerFee;
|
||||
}
|
||||
const zrxAssetData = this._orderFilledCancelledFetcher.getZRXAssetData();
|
||||
const isAssetZRX = assetData === zrxAssetData;
|
||||
|
||||
const traderBalance = await this._balanceAndProxyAllowanceFetcher.getBalanceAsync(assetData, traderAddress);
|
||||
const traderProxyAllowance = await this._balanceAndProxyAllowanceFetcher.getProxyAllowanceAsync(
|
||||
assetData,
|
||||
traderAddress,
|
||||
);
|
||||
const traderFeeBalance = await this._balanceAndProxyAllowanceFetcher.getBalanceAsync(
|
||||
zrxAssetData,
|
||||
traderAddress,
|
||||
);
|
||||
const traderFeeProxyAllowance = await this._balanceAndProxyAllowanceFetcher.getProxyAllowanceAsync(
|
||||
zrxAssetData,
|
||||
traderAddress,
|
||||
);
|
||||
|
||||
const transferrableTraderAssetAmount = BigNumber.min([traderProxyAllowance, traderBalance]);
|
||||
const transferrableFeeAssetAmount = BigNumber.min([traderFeeProxyAllowance, traderFeeBalance]);
|
||||
|
||||
const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
|
||||
const filledTakerAssetAmount = await this._orderFilledCancelledFetcher.getFilledTakerAmountAsync(orderHash);
|
||||
const totalMakerAssetAmount = signedOrder.makerAssetAmount;
|
||||
const totalTakerAssetAmount = signedOrder.takerAssetAmount;
|
||||
const isOrderCancelled = await this._orderFilledCancelledFetcher.isOrderCancelledAsync(orderHash);
|
||||
const remainingTakerAssetAmount = isOrderCancelled
|
||||
? new BigNumber(0)
|
||||
: totalTakerAssetAmount.minus(filledTakerAssetAmount);
|
||||
const remainingMakerAssetAmount = remainingTakerAssetAmount.eq(0)
|
||||
? new BigNumber(0)
|
||||
: remainingTakerAssetAmount.times(totalMakerAssetAmount).dividedToIntegerBy(totalTakerAssetAmount);
|
||||
const remainingAssetAmount = isMakerSide ? remainingMakerAssetAmount : remainingTakerAssetAmount;
|
||||
|
||||
const remainingFillableCalculator = new RemainingFillableCalculator(
|
||||
feeAmount,
|
||||
assetAmount,
|
||||
isAssetZRX,
|
||||
transferrableTraderAssetAmount,
|
||||
transferrableFeeAssetAmount,
|
||||
remainingAssetAmount,
|
||||
);
|
||||
const remainingFillableAssetAmount = remainingFillableCalculator.computeRemainingFillable();
|
||||
|
||||
const sidedOrderRelevantState = {
|
||||
isMakerSide,
|
||||
traderBalance,
|
||||
traderProxyAllowance,
|
||||
traderFeeBalance,
|
||||
traderFeeProxyAllowance,
|
||||
filledTakerAssetAmount,
|
||||
remainingFillableAssetAmount,
|
||||
};
|
||||
return sidedOrderRelevantState;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,19 @@
|
||||
import { ExchangeContractErrs, Order, SignedOrder } from '@0xproject/types';
|
||||
import { RevertReason, SignedOrder } from '@0xproject/types';
|
||||
import { BigNumber } from '@0xproject/utils';
|
||||
import { Provider } from 'ethereum-types';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { OrderError, TradeSide, TransferType } from './types';
|
||||
|
||||
import { AbstractOrderFilledCancelledFetcher } from './abstract/abstract_order_filled_cancelled_fetcher';
|
||||
import { constants } from './constants';
|
||||
import { ExchangeTransferSimulator } from './exchange_transfer_simulator';
|
||||
import { ExchangeContract } from './generated_contract_wrappers/exchange';
|
||||
import { orderHashUtils } from './order_hash';
|
||||
import { isValidSignatureAsync } from './signature_utils';
|
||||
import { utils } from './utils';
|
||||
|
||||
export class OrderValidationUtils {
|
||||
private _exchangeContract: ExchangeContract;
|
||||
// TODO: Write some tests for the function
|
||||
// const numerator = new BigNumber(20);
|
||||
// const denominator = new BigNumber(999);
|
||||
// const target = new BigNumber(50);
|
||||
// rounding error = ((20*50/999) - floor(20*50/999)) / (20*50/999) = 0.1%
|
||||
private _orderFilledCancelledFetcher: AbstractOrderFilledCancelledFetcher;
|
||||
public static isRoundingError(numerator: BigNumber, denominator: BigNumber, target: BigNumber): boolean {
|
||||
// Solidity's mulmod() in JS
|
||||
// Source: https://solidity.readthedocs.io/en/latest/units-and-global-variables.html#mathematical-and-cryptographic-functions
|
||||
@@ -36,136 +31,120 @@ export class OrderValidationUtils {
|
||||
const isError = errPercentageTimes1000000.gt(1000);
|
||||
return isError;
|
||||
}
|
||||
public static validateCancelOrderThrowIfInvalid(
|
||||
order: Order,
|
||||
cancelTakerTokenAmount: BigNumber,
|
||||
filledTakerTokenAmount: BigNumber,
|
||||
): void {
|
||||
if (cancelTakerTokenAmount.eq(0)) {
|
||||
throw new Error(ExchangeContractErrs.OrderCancelAmountZero);
|
||||
}
|
||||
if (order.takerAssetAmount.eq(filledTakerTokenAmount)) {
|
||||
throw new Error(ExchangeContractErrs.OrderAlreadyCancelledOrFilled);
|
||||
}
|
||||
const currentUnixTimestampSec = utils.getCurrentUnixTimestampSec();
|
||||
if (order.expirationTimeSeconds.lessThan(currentUnixTimestampSec)) {
|
||||
throw new Error(ExchangeContractErrs.OrderCancelExpired);
|
||||
}
|
||||
}
|
||||
public static async validateFillOrderBalancesAllowancesThrowIfInvalidAsync(
|
||||
exchangeTradeEmulator: ExchangeTransferSimulator,
|
||||
signedOrder: SignedOrder,
|
||||
fillTakerTokenAmount: BigNumber,
|
||||
fillTakerAssetAmount: BigNumber,
|
||||
senderAddress: string,
|
||||
zrxTokenAddress: string,
|
||||
zrxAssetData: string,
|
||||
): Promise<void> {
|
||||
const fillMakerTokenAmount = OrderValidationUtils._getPartialAmount(
|
||||
fillTakerTokenAmount,
|
||||
signedOrder.takerAssetAmount,
|
||||
signedOrder.makerAssetAmount,
|
||||
);
|
||||
await exchangeTradeEmulator.transferFromAsync(
|
||||
signedOrder.makerAssetData,
|
||||
signedOrder.makerAddress,
|
||||
senderAddress,
|
||||
fillMakerTokenAmount,
|
||||
TradeSide.Maker,
|
||||
TransferType.Trade,
|
||||
);
|
||||
await exchangeTradeEmulator.transferFromAsync(
|
||||
signedOrder.takerAssetData,
|
||||
senderAddress,
|
||||
signedOrder.makerAddress,
|
||||
fillTakerTokenAmount,
|
||||
TradeSide.Taker,
|
||||
TransferType.Trade,
|
||||
);
|
||||
const makerFeeAmount = OrderValidationUtils._getPartialAmount(
|
||||
fillTakerTokenAmount,
|
||||
signedOrder.takerAssetAmount,
|
||||
signedOrder.makerFee,
|
||||
);
|
||||
await exchangeTradeEmulator.transferFromAsync(
|
||||
zrxTokenAddress,
|
||||
signedOrder.makerAddress,
|
||||
signedOrder.feeRecipientAddress,
|
||||
makerFeeAmount,
|
||||
TradeSide.Maker,
|
||||
TransferType.Fee,
|
||||
);
|
||||
const takerFeeAmount = OrderValidationUtils._getPartialAmount(
|
||||
fillTakerTokenAmount,
|
||||
signedOrder.takerAssetAmount,
|
||||
signedOrder.takerFee,
|
||||
);
|
||||
await exchangeTradeEmulator.transferFromAsync(
|
||||
zrxTokenAddress,
|
||||
senderAddress,
|
||||
signedOrder.feeRecipientAddress,
|
||||
takerFeeAmount,
|
||||
TradeSide.Taker,
|
||||
TransferType.Fee,
|
||||
);
|
||||
try {
|
||||
const fillMakerTokenAmount = utils.getPartialAmount(
|
||||
fillTakerAssetAmount,
|
||||
signedOrder.takerAssetAmount,
|
||||
signedOrder.makerAssetAmount,
|
||||
);
|
||||
await exchangeTradeEmulator.transferFromAsync(
|
||||
signedOrder.makerAssetData,
|
||||
signedOrder.makerAddress,
|
||||
senderAddress,
|
||||
fillMakerTokenAmount,
|
||||
TradeSide.Maker,
|
||||
TransferType.Trade,
|
||||
);
|
||||
await exchangeTradeEmulator.transferFromAsync(
|
||||
signedOrder.takerAssetData,
|
||||
senderAddress,
|
||||
signedOrder.makerAddress,
|
||||
fillTakerAssetAmount,
|
||||
TradeSide.Taker,
|
||||
TransferType.Trade,
|
||||
);
|
||||
const makerFeeAmount = utils.getPartialAmount(
|
||||
fillTakerAssetAmount,
|
||||
signedOrder.takerAssetAmount,
|
||||
signedOrder.makerFee,
|
||||
);
|
||||
await exchangeTradeEmulator.transferFromAsync(
|
||||
zrxAssetData,
|
||||
signedOrder.makerAddress,
|
||||
signedOrder.feeRecipientAddress,
|
||||
makerFeeAmount,
|
||||
TradeSide.Maker,
|
||||
TransferType.Fee,
|
||||
);
|
||||
const takerFeeAmount = utils.getPartialAmount(
|
||||
fillTakerAssetAmount,
|
||||
signedOrder.takerAssetAmount,
|
||||
signedOrder.takerFee,
|
||||
);
|
||||
await exchangeTradeEmulator.transferFromAsync(
|
||||
zrxAssetData,
|
||||
senderAddress,
|
||||
signedOrder.feeRecipientAddress,
|
||||
takerFeeAmount,
|
||||
TradeSide.Taker,
|
||||
TransferType.Fee,
|
||||
);
|
||||
} catch (err) {
|
||||
throw new Error(RevertReason.TransferFailed);
|
||||
}
|
||||
}
|
||||
private static _validateRemainingFillAmountNotZeroOrThrow(
|
||||
takerAssetAmount: BigNumber,
|
||||
filledTakerTokenAmount: BigNumber,
|
||||
): void {
|
||||
if (takerAssetAmount.eq(filledTakerTokenAmount)) {
|
||||
throw new Error(ExchangeContractErrs.OrderRemainingFillAmountZero);
|
||||
throw new Error(RevertReason.OrderUnfillable);
|
||||
}
|
||||
}
|
||||
private static _validateOrderNotExpiredOrThrow(expirationTimeSeconds: BigNumber): void {
|
||||
const currentUnixTimestampSec = utils.getCurrentUnixTimestampSec();
|
||||
if (expirationTimeSeconds.lessThan(currentUnixTimestampSec)) {
|
||||
throw new Error(ExchangeContractErrs.OrderFillExpired);
|
||||
throw new Error(RevertReason.OrderUnfillable);
|
||||
}
|
||||
}
|
||||
private static _getPartialAmount(numerator: BigNumber, denominator: BigNumber, target: BigNumber): BigNumber {
|
||||
const fillMakerTokenAmount = numerator
|
||||
.mul(target)
|
||||
.div(denominator)
|
||||
.round(0);
|
||||
return fillMakerTokenAmount;
|
||||
}
|
||||
constructor(exchangeContract: ExchangeContract) {
|
||||
this._exchangeContract = exchangeContract;
|
||||
constructor(orderFilledCancelledFetcher: AbstractOrderFilledCancelledFetcher) {
|
||||
this._orderFilledCancelledFetcher = orderFilledCancelledFetcher;
|
||||
}
|
||||
public async validateOrderFillableOrThrowAsync(
|
||||
exchangeTradeEmulator: ExchangeTransferSimulator,
|
||||
signedOrder: SignedOrder,
|
||||
zrxTokenAddress: string,
|
||||
zrxAssetData: string,
|
||||
expectedFillTakerTokenAmount?: BigNumber,
|
||||
): Promise<void> {
|
||||
const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
|
||||
const filledTakerTokenAmount = await this._exchangeContract.filled.callAsync(orderHash);
|
||||
const filledTakerTokenAmount = await this._orderFilledCancelledFetcher.getFilledTakerAmountAsync(orderHash);
|
||||
OrderValidationUtils._validateRemainingFillAmountNotZeroOrThrow(
|
||||
signedOrder.takerAssetAmount,
|
||||
filledTakerTokenAmount,
|
||||
);
|
||||
OrderValidationUtils._validateOrderNotExpiredOrThrow(signedOrder.expirationTimeSeconds);
|
||||
let fillTakerTokenAmount = signedOrder.takerAssetAmount.minus(filledTakerTokenAmount);
|
||||
let fillTakerAssetAmount = signedOrder.takerAssetAmount.minus(filledTakerTokenAmount);
|
||||
if (!_.isUndefined(expectedFillTakerTokenAmount)) {
|
||||
fillTakerTokenAmount = expectedFillTakerTokenAmount;
|
||||
fillTakerAssetAmount = expectedFillTakerTokenAmount;
|
||||
}
|
||||
await OrderValidationUtils.validateFillOrderBalancesAllowancesThrowIfInvalidAsync(
|
||||
exchangeTradeEmulator,
|
||||
signedOrder,
|
||||
fillTakerTokenAmount,
|
||||
fillTakerAssetAmount,
|
||||
signedOrder.takerAddress,
|
||||
zrxTokenAddress,
|
||||
zrxAssetData,
|
||||
);
|
||||
}
|
||||
public async validateFillOrderThrowIfInvalidAsync(
|
||||
exchangeTradeEmulator: ExchangeTransferSimulator,
|
||||
provider: Provider,
|
||||
signedOrder: SignedOrder,
|
||||
fillTakerTokenAmount: BigNumber,
|
||||
fillTakerAssetAmount: BigNumber,
|
||||
takerAddress: string,
|
||||
zrxTokenAddress: string,
|
||||
zrxAssetData: string,
|
||||
): Promise<BigNumber> {
|
||||
if (fillTakerTokenAmount.eq(0)) {
|
||||
throw new Error(ExchangeContractErrs.OrderFillAmountZero);
|
||||
if (fillTakerAssetAmount.eq(0)) {
|
||||
throw new Error(RevertReason.InvalidTakerAmount);
|
||||
}
|
||||
if (signedOrder.makerAssetAmount.eq(0) || signedOrder.takerAssetAmount.eq(0)) {
|
||||
throw new Error(RevertReason.OrderUnfillable);
|
||||
}
|
||||
const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
|
||||
const isValid = await isValidSignatureAsync(
|
||||
@@ -177,34 +156,34 @@ export class OrderValidationUtils {
|
||||
if (!isValid) {
|
||||
throw new Error(OrderError.InvalidSignature);
|
||||
}
|
||||
const filledTakerTokenAmount = await this._exchangeContract.filled.callAsync(orderHash);
|
||||
const filledTakerTokenAmount = await this._orderFilledCancelledFetcher.getFilledTakerAmountAsync(orderHash);
|
||||
OrderValidationUtils._validateRemainingFillAmountNotZeroOrThrow(
|
||||
signedOrder.takerAssetAmount,
|
||||
filledTakerTokenAmount,
|
||||
);
|
||||
if (signedOrder.takerAddress !== constants.NULL_ADDRESS && signedOrder.takerAddress !== takerAddress) {
|
||||
throw new Error(ExchangeContractErrs.TransactionSenderIsNotFillOrderTaker);
|
||||
throw new Error(RevertReason.InvalidTaker);
|
||||
}
|
||||
OrderValidationUtils._validateOrderNotExpiredOrThrow(signedOrder.expirationTimeSeconds);
|
||||
const remainingTakerTokenAmount = signedOrder.takerAssetAmount.minus(filledTakerTokenAmount);
|
||||
const desiredFillTakerTokenAmount = remainingTakerTokenAmount.lessThan(fillTakerTokenAmount)
|
||||
const desiredFillTakerTokenAmount = remainingTakerTokenAmount.lessThan(fillTakerAssetAmount)
|
||||
? remainingTakerTokenAmount
|
||||
: fillTakerTokenAmount;
|
||||
: fillTakerAssetAmount;
|
||||
await OrderValidationUtils.validateFillOrderBalancesAllowancesThrowIfInvalidAsync(
|
||||
exchangeTradeEmulator,
|
||||
signedOrder,
|
||||
desiredFillTakerTokenAmount,
|
||||
takerAddress,
|
||||
zrxTokenAddress,
|
||||
zrxAssetData,
|
||||
);
|
||||
|
||||
const wouldRoundingErrorOccur = OrderValidationUtils.isRoundingError(
|
||||
filledTakerTokenAmount,
|
||||
desiredFillTakerTokenAmount,
|
||||
signedOrder.takerAssetAmount,
|
||||
signedOrder.makerAssetAmount,
|
||||
);
|
||||
if (wouldRoundingErrorOccur) {
|
||||
throw new Error(ExchangeContractErrs.OrderFillRoundingError);
|
||||
throw new Error(RevertReason.RoundingError);
|
||||
}
|
||||
return filledTakerTokenAmount;
|
||||
}
|
||||
@@ -212,20 +191,20 @@ export class OrderValidationUtils {
|
||||
exchangeTradeEmulator: ExchangeTransferSimulator,
|
||||
provider: Provider,
|
||||
signedOrder: SignedOrder,
|
||||
fillTakerTokenAmount: BigNumber,
|
||||
fillTakerAssetAmount: BigNumber,
|
||||
takerAddress: string,
|
||||
zrxTokenAddress: string,
|
||||
zrxAssetData: string,
|
||||
): Promise<void> {
|
||||
const filledTakerTokenAmount = await this.validateFillOrderThrowIfInvalidAsync(
|
||||
exchangeTradeEmulator,
|
||||
provider,
|
||||
signedOrder,
|
||||
fillTakerTokenAmount,
|
||||
fillTakerAssetAmount,
|
||||
takerAddress,
|
||||
zrxTokenAddress,
|
||||
zrxAssetData,
|
||||
);
|
||||
if (filledTakerTokenAmount !== fillTakerTokenAmount) {
|
||||
throw new Error(ExchangeContractErrs.InsufficientRemainingFillAmount);
|
||||
if (filledTakerTokenAmount !== fillTakerAssetAmount) {
|
||||
throw new Error(RevertReason.OrderUnfillable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,9 +23,9 @@ export class RemainingFillableCalculator {
|
||||
this._transferrableAssetAmount = transferrableAssetAmount;
|
||||
this._transferrableFeeAmount = transferrableFeeAmount;
|
||||
this._remainingOrderAssetAmount = remainingOrderAssetAmount;
|
||||
this._remainingOrderFeeAmount = remainingOrderAssetAmount
|
||||
.times(this._orderFee)
|
||||
.dividedToIntegerBy(this._orderAssetAmount);
|
||||
this._remainingOrderFeeAmount = orderAssetAmount.eq(0)
|
||||
? new BigNumber(0)
|
||||
: remainingOrderAssetAmount.times(orderFee).dividedToIntegerBy(orderAssetAmount);
|
||||
}
|
||||
public computeRemainingFillable(): BigNumber {
|
||||
if (this._hasSufficientFundsForFeeAndTransferAmount()) {
|
||||
|
||||
@@ -19,8 +19,8 @@ export class BalanceAndProxyAllowanceLazyStore implements AbstractBalanceAndProx
|
||||
[userAddress: string]: BigNumber;
|
||||
};
|
||||
};
|
||||
constructor(token: AbstractBalanceAndProxyAllowanceFetcher) {
|
||||
this._balanceAndProxyAllowanceFetcher = token;
|
||||
constructor(balanceAndProxyAllowanceFetcher: AbstractBalanceAndProxyAllowanceFetcher) {
|
||||
this._balanceAndProxyAllowanceFetcher = balanceAndProxyAllowanceFetcher;
|
||||
this._balance = {};
|
||||
this._proxyAllowance = {};
|
||||
}
|
||||
|
||||
@@ -12,4 +12,11 @@ export const utils = {
|
||||
const milisecondsInSecond = 1000;
|
||||
return new BigNumber(Date.now() / milisecondsInSecond).round();
|
||||
},
|
||||
getPartialAmount(numerator: BigNumber, denominator: BigNumber, target: BigNumber): BigNumber {
|
||||
const fillMakerTokenAmount = numerator
|
||||
.mul(target)
|
||||
.div(denominator)
|
||||
.round(0);
|
||||
return fillMakerTokenAmount;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -4,6 +4,7 @@ import { BigNumber } from '@0xproject/utils';
|
||||
import * as chai from 'chai';
|
||||
|
||||
import { artifacts } from '../src/artifacts';
|
||||
import { assetProxyUtils } from '../src/asset_proxy_utils';
|
||||
import { constants } from '../src/constants';
|
||||
import { ExchangeTransferSimulator } from '../src/exchange_transfer_simulator';
|
||||
import { DummyERC20TokenContract } from '../src/generated_contract_wrappers/dummy_e_r_c20_token';
|
||||
@@ -27,7 +28,7 @@ describe('ExchangeTransferSimulator', async () => {
|
||||
let coinbase: string;
|
||||
let sender: string;
|
||||
let recipient: string;
|
||||
let exampleTokenAddress: string;
|
||||
let exampleAssetData: string;
|
||||
let exchangeTransferSimulator: ExchangeTransferSimulator;
|
||||
let txHash: string;
|
||||
let erc20ProxyAddress: string;
|
||||
@@ -65,7 +66,7 @@ describe('ExchangeTransferSimulator', async () => {
|
||||
totalSupply,
|
||||
);
|
||||
|
||||
exampleTokenAddress = dummyERC20Token.address;
|
||||
exampleAssetData = assetProxyUtils.encodeERC20AssetData(dummyERC20Token.address);
|
||||
});
|
||||
beforeEach(async () => {
|
||||
await blockchainLifecycle.startAsync();
|
||||
@@ -91,7 +92,7 @@ describe('ExchangeTransferSimulator', async () => {
|
||||
it("throws if the user doesn't have enough allowance", async () => {
|
||||
return expect(
|
||||
exchangeTransferSimulator.transferFromAsync(
|
||||
exampleTokenAddress,
|
||||
exampleAssetData,
|
||||
sender,
|
||||
recipient,
|
||||
transferAmount,
|
||||
@@ -107,7 +108,7 @@ describe('ExchangeTransferSimulator', async () => {
|
||||
await web3Wrapper.awaitTransactionSuccessAsync(txHash);
|
||||
return expect(
|
||||
exchangeTransferSimulator.transferFromAsync(
|
||||
exampleTokenAddress,
|
||||
exampleAssetData,
|
||||
sender,
|
||||
recipient,
|
||||
transferAmount,
|
||||
@@ -128,7 +129,7 @@ describe('ExchangeTransferSimulator', async () => {
|
||||
await web3Wrapper.awaitTransactionSuccessAsync(txHash);
|
||||
|
||||
await exchangeTransferSimulator.transferFromAsync(
|
||||
exampleTokenAddress,
|
||||
exampleAssetData,
|
||||
sender,
|
||||
recipient,
|
||||
transferAmount,
|
||||
@@ -136,9 +137,9 @@ describe('ExchangeTransferSimulator', async () => {
|
||||
TransferType.Trade,
|
||||
);
|
||||
const store = (exchangeTransferSimulator as any)._store;
|
||||
const senderBalance = await store.getBalanceAsync(exampleTokenAddress, sender);
|
||||
const recipientBalance = await store.getBalanceAsync(exampleTokenAddress, recipient);
|
||||
const senderProxyAllowance = await store.getProxyAllowanceAsync(exampleTokenAddress, sender);
|
||||
const senderBalance = await store.getBalanceAsync(exampleAssetData, sender);
|
||||
const recipientBalance = await store.getBalanceAsync(exampleAssetData, recipient);
|
||||
const senderProxyAllowance = await store.getProxyAllowanceAsync(exampleAssetData, sender);
|
||||
expect(senderBalance).to.be.bignumber.equal(0);
|
||||
expect(recipientBalance).to.be.bignumber.equal(transferAmount);
|
||||
expect(senderProxyAllowance).to.be.bignumber.equal(0);
|
||||
@@ -157,7 +158,7 @@ describe('ExchangeTransferSimulator', async () => {
|
||||
);
|
||||
await web3Wrapper.awaitTransactionSuccessAsync(txHash);
|
||||
await exchangeTransferSimulator.transferFromAsync(
|
||||
exampleTokenAddress,
|
||||
exampleAssetData,
|
||||
sender,
|
||||
recipient,
|
||||
transferAmount,
|
||||
@@ -165,9 +166,9 @@ describe('ExchangeTransferSimulator', async () => {
|
||||
TransferType.Trade,
|
||||
);
|
||||
const store = (exchangeTransferSimulator as any)._store;
|
||||
const senderBalance = await store.getBalanceAsync(exampleTokenAddress, sender);
|
||||
const recipientBalance = await store.getBalanceAsync(exampleTokenAddress, recipient);
|
||||
const senderProxyAllowance = await store.getProxyAllowanceAsync(exampleTokenAddress, sender);
|
||||
const senderBalance = await store.getBalanceAsync(exampleAssetData, sender);
|
||||
const recipientBalance = await store.getBalanceAsync(exampleAssetData, recipient);
|
||||
const senderProxyAllowance = await store.getProxyAllowanceAsync(exampleAssetData, sender);
|
||||
expect(senderBalance).to.be.bignumber.equal(0);
|
||||
expect(recipientBalance).to.be.bignumber.equal(transferAmount);
|
||||
expect(senderProxyAllowance).to.be.bignumber.equal(constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS);
|
||||
|
||||
@@ -6,7 +6,7 @@ import { provider } from './utils/web3_wrapper';
|
||||
before('migrate contracts', async function(): Promise<void> {
|
||||
// HACK: Since the migrations take longer then our global mocha timeout limit
|
||||
// we manually increase it for this before hook.
|
||||
const mochaTestTimeoutMs = 20000;
|
||||
const mochaTestTimeoutMs = 25000;
|
||||
this.timeout(mochaTestTimeoutMs);
|
||||
const txDefaults = {
|
||||
gas: devConstants.GAS_LIMIT,
|
||||
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
import { BigNumber, intervalUtils, logUtils, promisify } from '@0xproject/utils';
|
||||
import { Web3Wrapper } from '@0xproject/web3-wrapper';
|
||||
import * as _ from 'lodash';
|
||||
import * as moment from 'moment';
|
||||
import * as React from 'react';
|
||||
import contract = require('truffle-contract');
|
||||
import { BlockchainWatcher } from 'ts/blockchain_watcher';
|
||||
@@ -43,6 +44,8 @@ import {
|
||||
BlockchainErrs,
|
||||
ContractInstance,
|
||||
Fill,
|
||||
InjectedProviderObservable,
|
||||
InjectedProviderUpdate,
|
||||
Order as PortalOrder,
|
||||
Providers,
|
||||
ProviderType,
|
||||
@@ -79,9 +82,9 @@ export class Blockchain {
|
||||
private _dispatcher: Dispatcher;
|
||||
private _web3Wrapper?: Web3Wrapper;
|
||||
private _blockchainWatcher?: BlockchainWatcher;
|
||||
private _injectedProviderObservable?: InjectedProviderObservable;
|
||||
private _injectedProviderUpdateHandler: (update: InjectedProviderUpdate) => Promise<void>;
|
||||
private _userAddressIfExists: string;
|
||||
private _cachedProvider: Provider;
|
||||
private _cachedProviderNetworkId: number;
|
||||
private _ledgerSubprovider: LedgerSubprovider;
|
||||
private _defaultGasPrice: BigNumber;
|
||||
private static _getNameGivenProvider(provider: Provider): string {
|
||||
@@ -92,16 +95,62 @@ export class Blockchain {
|
||||
}
|
||||
return providerNameIfExists;
|
||||
}
|
||||
private static async _getProviderAsync(injectedWeb3: Web3, networkIdIfExists: number): Promise<Provider> {
|
||||
private static _getInjectedWeb3(): any {
|
||||
return (window as any).web3;
|
||||
}
|
||||
private static async _getInjectedWeb3ProviderNetworkIdIfExistsAsync(): Promise<number | undefined> {
|
||||
// Hack: We need to know the networkId the injectedWeb3 is connected to (if it is defined) in
|
||||
// order to properly instantiate the web3Wrapper. Since we must use the async call, we cannot
|
||||
// retrieve it from within the web3Wrapper constructor. This is and should remain the only
|
||||
// call to a web3 instance outside of web3Wrapper in the entire dapp.
|
||||
// In addition, if the user has an injectedWeb3 instance that is disconnected from a backing
|
||||
// Ethereum node, this call will throw. We need to handle this case gracefully
|
||||
const injectedWeb3IfExists = Blockchain._getInjectedWeb3();
|
||||
let networkIdIfExists: number;
|
||||
if (!_.isUndefined(injectedWeb3IfExists)) {
|
||||
try {
|
||||
networkIdIfExists = _.parseInt(await promisify<string>(injectedWeb3IfExists.version.getNetwork)());
|
||||
} catch (err) {
|
||||
// Ignore error and proceed with networkId undefined
|
||||
}
|
||||
}
|
||||
return networkIdIfExists;
|
||||
}
|
||||
private static async _getProviderAsync(
|
||||
injectedWeb3: Web3,
|
||||
networkIdIfExists: number,
|
||||
shouldUserLedgerProvider: boolean = false,
|
||||
): Promise<[Provider, LedgerSubprovider | undefined]> {
|
||||
const doesInjectedWeb3Exist = !_.isUndefined(injectedWeb3);
|
||||
const isNetworkIdAvailable = !_.isUndefined(networkIdIfExists);
|
||||
const publicNodeUrlsIfExistsForNetworkId = configs.PUBLIC_NODE_URLS_BY_NETWORK_ID[networkIdIfExists];
|
||||
const isPublicNodeAvailableForNetworkId = !_.isUndefined(publicNodeUrlsIfExistsForNetworkId);
|
||||
|
||||
let provider;
|
||||
if (doesInjectedWeb3Exist && isPublicNodeAvailableForNetworkId) {
|
||||
if (shouldUserLedgerProvider && isNetworkIdAvailable) {
|
||||
const isU2FSupported = await utils.isU2FSupportedAsync();
|
||||
if (!isU2FSupported) {
|
||||
throw new Error('Cannot update providerType to LEDGER without U2F support');
|
||||
}
|
||||
const provider = new ProviderEngine();
|
||||
const ledgerWalletConfigs = {
|
||||
networkId: networkIdIfExists,
|
||||
ledgerEthereumClientFactoryAsync: ledgerEthereumBrowserClientFactoryAsync,
|
||||
};
|
||||
const ledgerSubprovider = new LedgerSubprovider(ledgerWalletConfigs);
|
||||
provider.addProvider(ledgerSubprovider);
|
||||
provider.addProvider(new FilterSubprovider());
|
||||
const rpcSubproviders = _.map(configs.PUBLIC_NODE_URLS_BY_NETWORK_ID[networkIdIfExists], publicNodeUrl => {
|
||||
return new RpcSubprovider({
|
||||
rpcUrl: publicNodeUrl,
|
||||
});
|
||||
});
|
||||
provider.addProvider(new RedundantSubprovider(rpcSubproviders as Subprovider[]));
|
||||
provider.start();
|
||||
return [provider, ledgerSubprovider];
|
||||
} else if (doesInjectedWeb3Exist && isPublicNodeAvailableForNetworkId) {
|
||||
// We catch all requests involving a users account and send it to the injectedWeb3
|
||||
// instance. All other requests go to the public hosted node.
|
||||
provider = new ProviderEngine();
|
||||
const provider = new ProviderEngine();
|
||||
provider.addProvider(new InjectedWeb3Subprovider(injectedWeb3.currentProvider));
|
||||
provider.addProvider(new FilterSubprovider());
|
||||
const rpcSubproviders = _.map(publicNodeUrlsIfExistsForNetworkId, publicNodeUrl => {
|
||||
@@ -111,16 +160,17 @@ export class Blockchain {
|
||||
});
|
||||
provider.addProvider(new RedundantSubprovider(rpcSubproviders as Subprovider[]));
|
||||
provider.start();
|
||||
return [provider, undefined];
|
||||
} else if (doesInjectedWeb3Exist) {
|
||||
// Since no public node for this network, all requests go to injectedWeb3 instance
|
||||
provider = injectedWeb3.currentProvider;
|
||||
return [injectedWeb3.currentProvider, undefined];
|
||||
} else {
|
||||
// If no injectedWeb3 instance, all requests fallback to our public hosted mainnet/testnet node
|
||||
// We do this so that users can still browse the 0x Portal DApp even if they do not have web3
|
||||
// injected into their browser.
|
||||
provider = new ProviderEngine();
|
||||
const provider = new ProviderEngine();
|
||||
provider.addProvider(new FilterSubprovider());
|
||||
const networkId = configs.IS_MAINNET_ENABLED ? constants.NETWORK_ID_MAINNET : constants.NETWORK_ID_KOVAN;
|
||||
const networkId = constants.NETWORK_ID_MAINNET;
|
||||
const rpcSubproviders = _.map(configs.PUBLIC_NODE_URLS_BY_NETWORK_ID[networkId], publicNodeUrl => {
|
||||
return new RpcSubprovider({
|
||||
rpcUrl: publicNodeUrl,
|
||||
@@ -128,14 +178,15 @@ export class Blockchain {
|
||||
});
|
||||
provider.addProvider(new RedundantSubprovider(rpcSubproviders as Subprovider[]));
|
||||
provider.start();
|
||||
return [provider, undefined];
|
||||
}
|
||||
|
||||
return provider;
|
||||
}
|
||||
constructor(dispatcher: Dispatcher) {
|
||||
this._dispatcher = dispatcher;
|
||||
const defaultGasPrice = GWEI_IN_WEI * 30;
|
||||
this._defaultGasPrice = new BigNumber(defaultGasPrice);
|
||||
// We need a unique reference to this function so we can use it to unsubcribe.
|
||||
this._injectedProviderUpdateHandler = this._handleInjectedProviderUpdateAsync.bind(this);
|
||||
// tslint:disable-next-line:no-floating-promises
|
||||
this._updateDefaultGasPriceAsync();
|
||||
// tslint:disable-next-line:no-floating-promises
|
||||
@@ -185,84 +236,17 @@ export class Blockchain {
|
||||
this._ledgerSubprovider.setPath(path);
|
||||
}
|
||||
public async updateProviderToLedgerAsync(networkId: number): Promise<void> {
|
||||
utils.assert(!_.isUndefined(this._contractWrappers), 'Contract Wrappers must be instantiated.');
|
||||
|
||||
const isU2FSupported = await utils.isU2FSupportedAsync();
|
||||
if (!isU2FSupported) {
|
||||
throw new Error('Cannot update providerType to LEDGER without U2F support');
|
||||
}
|
||||
|
||||
// Cache injected provider so that we can switch the user back to it easily
|
||||
if (_.isUndefined(this._cachedProvider)) {
|
||||
this._cachedProvider = this._web3Wrapper.getProvider();
|
||||
this._cachedProviderNetworkId = this.networkId;
|
||||
}
|
||||
|
||||
this._blockchainWatcher.destroy();
|
||||
|
||||
delete this._userAddressIfExists;
|
||||
this._dispatcher.updateUserAddress(undefined); // Clear old userAddress
|
||||
|
||||
const provider = new ProviderEngine();
|
||||
const ledgerWalletConfigs = {
|
||||
networkId,
|
||||
ledgerEthereumClientFactoryAsync: ledgerEthereumBrowserClientFactoryAsync,
|
||||
};
|
||||
this._ledgerSubprovider = new LedgerSubprovider(ledgerWalletConfigs);
|
||||
provider.addProvider(this._ledgerSubprovider);
|
||||
provider.addProvider(new FilterSubprovider());
|
||||
const rpcSubproviders = _.map(configs.PUBLIC_NODE_URLS_BY_NETWORK_ID[networkId], publicNodeUrl => {
|
||||
return new RpcSubprovider({
|
||||
rpcUrl: publicNodeUrl,
|
||||
});
|
||||
});
|
||||
provider.addProvider(new RedundantSubprovider(rpcSubproviders as Subprovider[]));
|
||||
provider.start();
|
||||
this.networkId = networkId;
|
||||
this._dispatcher.updateNetworkId(this.networkId);
|
||||
const shouldPollUserAddress = false;
|
||||
this._web3Wrapper = new Web3Wrapper(provider);
|
||||
this._blockchainWatcher = new BlockchainWatcher(
|
||||
this._dispatcher,
|
||||
this._web3Wrapper,
|
||||
this.networkId,
|
||||
shouldPollUserAddress,
|
||||
);
|
||||
this._contractWrappers.setProvider(provider, this.networkId);
|
||||
await this._blockchainWatcher.startEmittingNetworkConnectionAndUserBalanceStateAsync();
|
||||
this._dispatcher.updateProviderType(ProviderType.Ledger);
|
||||
const shouldUserLedgerProvider = true;
|
||||
await this._resetOrInitializeAsync(networkId, shouldPollUserAddress, shouldUserLedgerProvider);
|
||||
}
|
||||
public async updateProviderToInjectedAsync(): Promise<void> {
|
||||
utils.assert(!_.isUndefined(this._contractWrappers), 'Contract Wrappers must be instantiated.');
|
||||
|
||||
if (_.isUndefined(this._cachedProvider)) {
|
||||
return; // Going from injected to injected, so we noop
|
||||
}
|
||||
|
||||
this._blockchainWatcher.destroy();
|
||||
|
||||
const provider = this._cachedProvider;
|
||||
this.networkId = this._cachedProviderNetworkId;
|
||||
|
||||
const shouldPollUserAddress = true;
|
||||
this._web3Wrapper = new Web3Wrapper(provider);
|
||||
this._blockchainWatcher = new BlockchainWatcher(
|
||||
this._dispatcher,
|
||||
this._web3Wrapper,
|
||||
this.networkId,
|
||||
shouldPollUserAddress,
|
||||
);
|
||||
|
||||
const userAddresses = await this._web3Wrapper.getAvailableAddressesAsync();
|
||||
this._userAddressIfExists = userAddresses[0];
|
||||
|
||||
this._contractWrappers.setProvider(provider, this.networkId);
|
||||
|
||||
await this.fetchTokenInformationAsync();
|
||||
await this._blockchainWatcher.startEmittingNetworkConnectionAndUserBalanceStateAsync();
|
||||
this._dispatcher.updateProviderType(ProviderType.Injected);
|
||||
delete this._ledgerSubprovider;
|
||||
delete this._cachedProvider;
|
||||
const shouldUserLedgerProvider = false;
|
||||
this._dispatcher.updateBlockchainIsLoaded(false);
|
||||
// We don't want to be out of sync with the network the injected provider declares.
|
||||
const networkId = await Blockchain._getInjectedWeb3ProviderNetworkIdIfExistsAsync();
|
||||
await this._resetOrInitializeAsync(networkId, shouldPollUserAddress, shouldUserLedgerProvider);
|
||||
}
|
||||
public async setProxyAllowanceAsync(token: Token, amountInBaseUnits: BigNumber): Promise<void> {
|
||||
utils.assert(this.isValidAddress(token.address), BlockchainCallErrs.TokenAddressIsInvalid);
|
||||
@@ -538,6 +522,7 @@ export class Blockchain {
|
||||
}
|
||||
public destroy(): void {
|
||||
this._blockchainWatcher.destroy();
|
||||
this._injectedProviderObservable.unsubscribe(this._injectedProviderUpdateHandler);
|
||||
this._stopWatchingExchangeLogFillEvents();
|
||||
}
|
||||
public async fetchTokenInformationAsync(): Promise<void> {
|
||||
@@ -559,6 +544,7 @@ export class Blockchain {
|
||||
tokenRegistryTokenSymbols,
|
||||
configs.DEFAULT_TRACKED_TOKEN_SYMBOLS,
|
||||
);
|
||||
const currentTimestamp = moment().unix();
|
||||
if (defaultTrackedTokensInRegistry.length !== configs.DEFAULT_TRACKED_TOKEN_SYMBOLS.length) {
|
||||
this._dispatcher.updateShouldBlockchainErrDialogBeOpen(true);
|
||||
this._dispatcher.encounteredBlockchainError(BlockchainErrs.DefaultTokensNotInTokenRegistry);
|
||||
@@ -573,7 +559,7 @@ export class Blockchain {
|
||||
if (_.isEmpty(trackedTokensByAddress)) {
|
||||
_.each(configs.DEFAULT_TRACKED_TOKEN_SYMBOLS, symbol => {
|
||||
const token = _.find(tokenRegistryTokens, t => t.symbol === symbol);
|
||||
token.isTracked = true;
|
||||
token.trackedTimestamp = currentTimestamp;
|
||||
trackedTokensByAddress[token.address] = token;
|
||||
});
|
||||
if (!_.isUndefined(this._userAddressIfExists)) {
|
||||
@@ -582,10 +568,10 @@ export class Blockchain {
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Properly set all tokenRegistry tokens `isTracked` to true if they are in the existing trackedTokens array
|
||||
_.each(trackedTokensByAddress, (_trackedToken: Token, address: string) => {
|
||||
// Properly set all tokenRegistry tokens `trackedTimestamp` if they are in the existing trackedTokens array
|
||||
_.each(trackedTokensByAddress, (trackedToken: Token, address: string) => {
|
||||
if (!_.isUndefined(tokenRegistryTokensByAddress[address])) {
|
||||
tokenRegistryTokensByAddress[address].isTracked = true;
|
||||
tokenRegistryTokensByAddress[address].trackedTimestamp = trackedToken.trackedTimestamp;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -632,6 +618,18 @@ export class Blockchain {
|
||||
private _doesUserAddressExist(): boolean {
|
||||
return !_.isUndefined(this._userAddressIfExists);
|
||||
}
|
||||
private async _handleInjectedProviderUpdateAsync(update: InjectedProviderUpdate): Promise<void> {
|
||||
if (update.networkVersion === 'loading' || !_.isUndefined(this._ledgerSubprovider)) {
|
||||
return;
|
||||
}
|
||||
const updatedNetworkId = _.parseInt(update.networkVersion);
|
||||
if (this.networkId === updatedNetworkId) {
|
||||
return;
|
||||
}
|
||||
const shouldPollUserAddress = true;
|
||||
const shouldUserLedgerProvider = false;
|
||||
await this._resetOrInitializeAsync(updatedNetworkId, shouldPollUserAddress, shouldUserLedgerProvider);
|
||||
}
|
||||
private async _rehydrateStoreWithContractEventsAsync(): Promise<void> {
|
||||
// Ensure we are only ever listening to one set of events
|
||||
this._stopWatchingExchangeLogFillEvents();
|
||||
@@ -765,7 +763,7 @@ export class Blockchain {
|
||||
name: t.name,
|
||||
symbol: t.symbol,
|
||||
decimals: t.decimals,
|
||||
isTracked: false,
|
||||
trackedTimestamp: undefined,
|
||||
isRegistered: true,
|
||||
};
|
||||
tokenByAddress[token.address] = token;
|
||||
@@ -774,49 +772,64 @@ export class Blockchain {
|
||||
}
|
||||
private async _onPageLoadInitFireAndForgetAsync(): Promise<void> {
|
||||
await utils.onPageLoadAsync(); // wait for page to load
|
||||
|
||||
// Hack: We need to know the networkId the injectedWeb3 is connected to (if it is defined) in
|
||||
// order to properly instantiate the web3Wrapper. Since we must use the async call, we cannot
|
||||
// retrieve it from within the web3Wrapper constructor. This is and should remain the only
|
||||
// call to a web3 instance outside of web3Wrapper in the entire dapp.
|
||||
// In addition, if the user has an injectedWeb3 instance that is disconnected from a backing
|
||||
// Ethereum node, this call will throw. We need to handle this case gracefully
|
||||
const injectedWeb3 = (window as any).web3;
|
||||
let networkIdIfExists: number;
|
||||
if (!_.isUndefined(injectedWeb3)) {
|
||||
try {
|
||||
networkIdIfExists = _.parseInt(await promisify<string>(injectedWeb3.version.getNetwork)());
|
||||
} catch (err) {
|
||||
// Ignore error and proceed with networkId undefined
|
||||
const networkIdIfExists = await Blockchain._getInjectedWeb3ProviderNetworkIdIfExistsAsync();
|
||||
this.networkId = !_.isUndefined(networkIdIfExists) ? networkIdIfExists : constants.NETWORK_ID_MAINNET;
|
||||
const injectedWeb3IfExists = Blockchain._getInjectedWeb3();
|
||||
if (!_.isUndefined(injectedWeb3IfExists) && !_.isUndefined(injectedWeb3IfExists.currentProvider)) {
|
||||
const injectedProviderObservable = injectedWeb3IfExists.currentProvider.publicConfigStore;
|
||||
if (!_.isUndefined(injectedProviderObservable) && _.isUndefined(this._injectedProviderObservable)) {
|
||||
this._injectedProviderObservable = injectedProviderObservable;
|
||||
this._injectedProviderObservable.subscribe(this._injectedProviderUpdateHandler);
|
||||
}
|
||||
}
|
||||
|
||||
const provider = await Blockchain._getProviderAsync(injectedWeb3, networkIdIfExists);
|
||||
this.networkId = !_.isUndefined(networkIdIfExists)
|
||||
? networkIdIfExists
|
||||
: configs.IS_MAINNET_ENABLED
|
||||
? constants.NETWORK_ID_MAINNET
|
||||
: constants.NETWORK_ID_KOVAN;
|
||||
this._dispatcher.updateNetworkId(this.networkId);
|
||||
const zeroExConfigs = {
|
||||
networkId: this.networkId,
|
||||
};
|
||||
this._contractWrappers = new ContractWrappers(provider, zeroExConfigs);
|
||||
this._updateProviderName(injectedWeb3);
|
||||
this._updateProviderName(injectedWeb3IfExists);
|
||||
const shouldPollUserAddress = true;
|
||||
this._web3Wrapper = new Web3Wrapper(provider);
|
||||
this._blockchainWatcher = new BlockchainWatcher(
|
||||
this._dispatcher,
|
||||
this._web3Wrapper,
|
||||
this.networkId,
|
||||
shouldPollUserAddress,
|
||||
const shouldUseLedgerProvider = false;
|
||||
await this._resetOrInitializeAsync(this.networkId, shouldPollUserAddress, shouldUseLedgerProvider);
|
||||
}
|
||||
private async _resetOrInitializeAsync(
|
||||
networkId: number,
|
||||
shouldPollUserAddress: boolean = false,
|
||||
shouldUserLedgerProvider: boolean = false,
|
||||
): Promise<void> {
|
||||
if (!shouldUserLedgerProvider) {
|
||||
this._dispatcher.updateBlockchainIsLoaded(false);
|
||||
}
|
||||
this._dispatcher.updateUserWeiBalance(undefined);
|
||||
this.networkId = networkId;
|
||||
const injectedWeb3IfExists = Blockchain._getInjectedWeb3();
|
||||
const [provider, ledgerSubproviderIfExists] = await Blockchain._getProviderAsync(
|
||||
injectedWeb3IfExists,
|
||||
networkId,
|
||||
shouldUserLedgerProvider,
|
||||
);
|
||||
|
||||
const userAddresses = await this._web3Wrapper.getAvailableAddressesAsync();
|
||||
this._userAddressIfExists = userAddresses[0];
|
||||
this._dispatcher.updateUserAddress(this._userAddressIfExists);
|
||||
await this.fetchTokenInformationAsync();
|
||||
await this._blockchainWatcher.startEmittingNetworkConnectionAndUserBalanceStateAsync();
|
||||
if (!_.isUndefined(this._contractWrappers)) {
|
||||
this._contractWrappers.setProvider(provider, networkId);
|
||||
} else {
|
||||
this._contractWrappers = new ContractWrappers(provider, { networkId });
|
||||
}
|
||||
if (!_.isUndefined(this._blockchainWatcher)) {
|
||||
this._blockchainWatcher.destroy();
|
||||
}
|
||||
this._web3Wrapper = new Web3Wrapper(provider);
|
||||
this._blockchainWatcher = new BlockchainWatcher(this._dispatcher, this._web3Wrapper, shouldPollUserAddress);
|
||||
if (shouldUserLedgerProvider && !_.isUndefined(ledgerSubproviderIfExists)) {
|
||||
delete this._userAddressIfExists;
|
||||
this._ledgerSubprovider = ledgerSubproviderIfExists;
|
||||
this._dispatcher.updateUserAddress(undefined);
|
||||
this._dispatcher.updateProviderType(ProviderType.Ledger);
|
||||
} else {
|
||||
delete this._ledgerSubprovider;
|
||||
const userAddresses = await this._web3Wrapper.getAvailableAddressesAsync();
|
||||
this._userAddressIfExists = userAddresses[0];
|
||||
this._dispatcher.updateUserAddress(this._userAddressIfExists);
|
||||
if (!_.isUndefined(injectedWeb3IfExists)) {
|
||||
this._dispatcher.updateProviderType(ProviderType.Injected);
|
||||
}
|
||||
await this.fetchTokenInformationAsync();
|
||||
}
|
||||
await this._blockchainWatcher.startEmittingUserBalanceStateAsync();
|
||||
this._dispatcher.updateNetworkId(networkId);
|
||||
await this._rehydrateStoreWithContractEventsAsync();
|
||||
}
|
||||
private _updateProviderName(injectedWeb3: Web3): void {
|
||||
|
||||
@@ -6,24 +6,17 @@ import { Dispatcher } from 'ts/redux/dispatcher';
|
||||
export class BlockchainWatcher {
|
||||
private _dispatcher: Dispatcher;
|
||||
private _web3Wrapper: Web3Wrapper;
|
||||
private _prevNetworkId: number;
|
||||
private _shouldPollUserAddress: boolean;
|
||||
private _watchNetworkAndBalanceIntervalId: NodeJS.Timer;
|
||||
private _watchBalanceIntervalId: NodeJS.Timer;
|
||||
private _prevUserEtherBalanceInWei?: BigNumber;
|
||||
private _prevUserAddressIfExists: string;
|
||||
constructor(
|
||||
dispatcher: Dispatcher,
|
||||
web3Wrapper: Web3Wrapper,
|
||||
networkIdIfExists: number,
|
||||
shouldPollUserAddress: boolean,
|
||||
) {
|
||||
constructor(dispatcher: Dispatcher, web3Wrapper: Web3Wrapper, shouldPollUserAddress: boolean) {
|
||||
this._dispatcher = dispatcher;
|
||||
this._prevNetworkId = networkIdIfExists;
|
||||
this._shouldPollUserAddress = shouldPollUserAddress;
|
||||
this._web3Wrapper = web3Wrapper;
|
||||
}
|
||||
public destroy(): void {
|
||||
this._stopEmittingNetworkConnectionAndUserBalanceState();
|
||||
this._stopEmittingUserBalanceState();
|
||||
// HACK: stop() is only available on providerEngine instances
|
||||
const provider = this._web3Wrapper.getProvider();
|
||||
if (!_.isUndefined((provider as any).stop)) {
|
||||
@@ -34,36 +27,23 @@ export class BlockchainWatcher {
|
||||
public updatePrevUserAddress(userAddress: string): void {
|
||||
this._prevUserAddressIfExists = userAddress;
|
||||
}
|
||||
public async startEmittingNetworkConnectionAndUserBalanceStateAsync(): Promise<void> {
|
||||
if (!_.isUndefined(this._watchNetworkAndBalanceIntervalId)) {
|
||||
public async startEmittingUserBalanceStateAsync(): Promise<void> {
|
||||
if (!_.isUndefined(this._watchBalanceIntervalId)) {
|
||||
return; // we are already emitting the state
|
||||
}
|
||||
this._prevUserEtherBalanceInWei = undefined;
|
||||
this._dispatcher.updateNetworkId(this._prevNetworkId);
|
||||
await this._updateNetworkAndBalanceAsync();
|
||||
this._watchNetworkAndBalanceIntervalId = intervalUtils.setAsyncExcludingInterval(
|
||||
this._updateNetworkAndBalanceAsync.bind(this),
|
||||
await this._updateBalanceAsync();
|
||||
this._watchBalanceIntervalId = intervalUtils.setAsyncExcludingInterval(
|
||||
this._updateBalanceAsync.bind(this),
|
||||
5000,
|
||||
(err: Error) => {
|
||||
logUtils.log(`Watching network and balances failed: ${err.stack}`);
|
||||
this._stopEmittingNetworkConnectionAndUserBalanceState();
|
||||
this._stopEmittingUserBalanceState();
|
||||
},
|
||||
);
|
||||
}
|
||||
private async _updateNetworkAndBalanceAsync(): Promise<void> {
|
||||
// Check for network state changes
|
||||
private async _updateBalanceAsync(): Promise<void> {
|
||||
let prevNodeVersion: string;
|
||||
let currentNetworkId;
|
||||
try {
|
||||
currentNetworkId = await this._web3Wrapper.getNetworkIdAsync();
|
||||
} catch (err) {
|
||||
// Noop
|
||||
}
|
||||
if (currentNetworkId !== this._prevNetworkId) {
|
||||
this._prevNetworkId = currentNetworkId;
|
||||
this._dispatcher.updateNetworkId(currentNetworkId);
|
||||
}
|
||||
|
||||
// Check for node version changes
|
||||
const currentNodeVersion = await this._web3Wrapper.getNodeVersionAsync();
|
||||
if (currentNodeVersion !== prevNodeVersion) {
|
||||
@@ -99,9 +79,9 @@ export class BlockchainWatcher {
|
||||
this._dispatcher.updateUserWeiBalance(balanceInWei);
|
||||
}
|
||||
}
|
||||
private _stopEmittingNetworkConnectionAndUserBalanceState(): void {
|
||||
if (!_.isUndefined(this._watchNetworkAndBalanceIntervalId)) {
|
||||
intervalUtils.clearAsyncExcludingInterval(this._watchNetworkAndBalanceIntervalId);
|
||||
private _stopEmittingUserBalanceState(): void {
|
||||
if (!_.isUndefined(this._watchBalanceIntervalId)) {
|
||||
intervalUtils.clearAsyncExcludingInterval(this._watchBalanceIntervalId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import FlatButton from 'material-ui/FlatButton';
|
||||
import * as React from 'react';
|
||||
import { Blockchain } from 'ts/blockchain';
|
||||
import { BlockchainErrs } from 'ts/types';
|
||||
import { configs } from 'ts/utils/configs';
|
||||
import { constants } from 'ts/utils/constants';
|
||||
|
||||
interface BlockchainErrDialogProps {
|
||||
@@ -125,8 +124,7 @@ export class BlockchainErrDialog extends React.Component<BlockchainErrDialogProp
|
||||
Parity Signer Chrome extension
|
||||
</a>{' '}
|
||||
lets you connect to a locally running Parity node. Make sure you have started your local Parity node
|
||||
with {configs.IS_MAINNET_ENABLED && '`parity ui` or'} `parity --chain kovan ui` in order to connect
|
||||
to {configs.IS_MAINNET_ENABLED ? 'mainnet or Kovan respectively.' : 'Kovan.'}
|
||||
with `parity ui` or `parity --chain kovan ui` in order to connect to mainnet or Kovan respectively.
|
||||
</div>
|
||||
<div className="pt2">
|
||||
<span className="bold">Note:</span> If you have done one of the above steps and are still seeing
|
||||
@@ -142,10 +140,8 @@ export class BlockchainErrDialog extends React.Component<BlockchainErrDialogProp
|
||||
<div>
|
||||
The 0x smart contracts are not deployed on the Ethereum network you are currently connected to
|
||||
(network Id: {this.props.networkId}). In order to use the 0x portal dApp, please connect to the{' '}
|
||||
{Networks.Kovan} testnet (network Id: {constants.NETWORK_ID_KOVAN})
|
||||
{configs.IS_MAINNET_ENABLED
|
||||
? ` or ${constants.MAINNET_NAME} (network Id: ${constants.NETWORK_ID_MAINNET}).`
|
||||
: `.`}
|
||||
{Networks.Kovan} testnet (network Id: {constants.NETWORK_ID_KOVAN}) or ${constants.MAINNET_NAME}{' '}
|
||||
(network Id: ${constants.NETWORK_ID_MAINNET}).
|
||||
</div>
|
||||
<h4>Metamask</h4>
|
||||
<div>
|
||||
@@ -159,11 +155,8 @@ export class BlockchainErrDialog extends React.Component<BlockchainErrDialogProp
|
||||
If using the{' '}
|
||||
<a href={constants.URL_PARITY_CHROME_STORE} target="_blank">
|
||||
Parity Signer Chrome extension
|
||||
</a>, make sure to start your local Parity node with{' '}
|
||||
{configs.IS_MAINNET_ENABLED
|
||||
? '`parity ui` or `parity --chain Kovan ui` in order to connect to mainnet \
|
||||
or Kovan respectively.'
|
||||
: '`parity --chain kovan ui` in order to connect to Kovan.'}
|
||||
</a>, make sure to start your local Parity node with `parity ui` or `parity --chain Kovan ui` in
|
||||
order to connect to mainnet \ or Kovan respectively.
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -282,6 +282,7 @@ export class LedgerConfigDialog extends React.Component<LedgerConfigDialogProps,
|
||||
if (didSucceed) {
|
||||
this.setState({
|
||||
stepIndex: LedgerSteps.SELECT_ADDRESS,
|
||||
connectionErrMsg: '',
|
||||
});
|
||||
}
|
||||
return didSucceed;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import Dialog from 'material-ui/Dialog';
|
||||
import FlatButton from 'material-ui/FlatButton';
|
||||
import * as moment from 'moment';
|
||||
import * as React from 'react';
|
||||
import { Blockchain } from 'ts/blockchain';
|
||||
import { TrackTokenConfirmation } from 'ts/components/track_token_confirmation';
|
||||
@@ -73,12 +74,13 @@ export class TrackTokenConfirmationDialog extends React.Component<
|
||||
this.setState({
|
||||
isAddingTokenToTracked: true,
|
||||
});
|
||||
const currentTimestamp = moment().unix();
|
||||
for (const token of this.props.tokens) {
|
||||
const newTokenEntry = {
|
||||
...token,
|
||||
trackedTimestamp: currentTimestamp,
|
||||
};
|
||||
|
||||
newTokenEntry.isTracked = true;
|
||||
trackedTokenStorage.addTrackedTokenToUser(this.props.userAddress, this.props.networkId, newTokenEntry);
|
||||
this.props.dispatcher.updateTokenByAddress([newTokenEntry]);
|
||||
}
|
||||
|
||||
@@ -373,26 +373,26 @@ export class FillOrder extends React.Component<FillOrderProps, FillOrderState> {
|
||||
|
||||
const tokensToTrack: Token[] = [];
|
||||
const isUnseenMakerToken = _.isUndefined(makerTokenIfExists);
|
||||
const isMakerTokenTracked = !_.isUndefined(makerTokenIfExists) && makerTokenIfExists.isTracked;
|
||||
const isMakerTokenTracked = !_.isUndefined(makerTokenIfExists) && utils.isTokenTracked(makerTokenIfExists);
|
||||
if (isUnseenMakerToken) {
|
||||
tokensToTrack.push({
|
||||
...this.state.parsedOrder.metadata.makerToken,
|
||||
address: this.state.parsedOrder.signedOrder.makerTokenAddress,
|
||||
iconUrl: undefined,
|
||||
isTracked: false,
|
||||
trackedTimestamp: undefined,
|
||||
isRegistered: false,
|
||||
});
|
||||
} else if (!isMakerTokenTracked) {
|
||||
tokensToTrack.push(makerTokenIfExists);
|
||||
}
|
||||
const isUnseenTakerToken = _.isUndefined(takerTokenIfExists);
|
||||
const isTakerTokenTracked = !_.isUndefined(takerTokenIfExists) && takerTokenIfExists.isTracked;
|
||||
const isTakerTokenTracked = !_.isUndefined(takerTokenIfExists) && utils.isTokenTracked(takerTokenIfExists);
|
||||
if (isUnseenTakerToken) {
|
||||
tokensToTrack.push({
|
||||
...this.state.parsedOrder.metadata.takerToken,
|
||||
address: this.state.parsedOrder.signedOrder.takerTokenAddress,
|
||||
iconUrl: undefined,
|
||||
isTracked: false,
|
||||
trackedTimestamp: undefined,
|
||||
isRegistered: false,
|
||||
});
|
||||
} else if (!isTakerTokenTracked) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as _ from 'lodash';
|
||||
import Dialog from 'material-ui/Dialog';
|
||||
import FlatButton from 'material-ui/FlatButton';
|
||||
import * as moment from 'moment';
|
||||
import * as React from 'react';
|
||||
import { Blockchain } from 'ts/blockchain';
|
||||
import { NewTokenForm } from 'ts/components/generate_order/new_token_form';
|
||||
@@ -10,6 +11,7 @@ import { trackedTokenStorage } from 'ts/local_storage/tracked_token_storage';
|
||||
import { Dispatcher } from 'ts/redux/dispatcher';
|
||||
import { DialogConfigs, Token, TokenByAddress, TokenVisibility } from 'ts/types';
|
||||
import { constants } from 'ts/utils/constants';
|
||||
import { utils } from 'ts/utils/utils';
|
||||
|
||||
const TOKEN_ICON_DIMENSION = 100;
|
||||
const TILE_DIMENSION = 146;
|
||||
@@ -134,8 +136,8 @@ export class AssetPicker extends React.Component<AssetPickerProps, AssetPickerSt
|
||||
let tileStyles;
|
||||
const gridTiles = _.map(this.props.tokenByAddress, (token: Token, address: string) => {
|
||||
if (
|
||||
(this.props.tokenVisibility === TokenVisibility.TRACKED && !token.isTracked) ||
|
||||
(this.props.tokenVisibility === TokenVisibility.UNTRACKED && token.isTracked) ||
|
||||
(this.props.tokenVisibility === TokenVisibility.TRACKED && !utils.isTokenTracked(token)) ||
|
||||
(this.props.tokenVisibility === TokenVisibility.UNTRACKED && utils.isTokenTracked(token)) ||
|
||||
token.symbol === constants.ZRX_TOKEN_SYMBOL ||
|
||||
token.symbol === constants.ETHER_TOKEN_SYMBOL
|
||||
) {
|
||||
@@ -212,7 +214,7 @@ export class AssetPicker extends React.Component<AssetPickerProps, AssetPickerSt
|
||||
}
|
||||
private _onChooseToken(tokenAddress: string): void {
|
||||
const token = this.props.tokenByAddress[tokenAddress];
|
||||
if (token.isTracked) {
|
||||
if (utils.isTokenTracked(token)) {
|
||||
this.props.onTokenChosen(tokenAddress);
|
||||
} else {
|
||||
this.setState({
|
||||
@@ -257,9 +259,8 @@ export class AssetPicker extends React.Component<AssetPickerProps, AssetPickerSt
|
||||
}
|
||||
const newTokenEntry = {
|
||||
...token,
|
||||
trackedTimestamp: moment().unix(),
|
||||
};
|
||||
|
||||
newTokenEntry.isTracked = true;
|
||||
trackedTokenStorage.addTrackedTokenToUser(this.props.userAddress, this.props.networkId, newTokenEntry);
|
||||
|
||||
this.props.dispatcher.updateTokenByAddress([newTokenEntry]);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { colors } from '@0xproject/react-shared';
|
||||
import * as _ from 'lodash';
|
||||
import TextField from 'material-ui/TextField';
|
||||
import * as moment from 'moment';
|
||||
import * as React from 'react';
|
||||
import { Blockchain } from 'ts/blockchain';
|
||||
import { AddressInput } from 'ts/components/inputs/address_input';
|
||||
@@ -147,7 +148,7 @@ export class NewTokenForm extends React.Component<NewTokenFormProps, NewTokenFor
|
||||
iconUrl: undefined,
|
||||
name: this.state.name,
|
||||
symbol: this.state.symbol.toUpperCase(),
|
||||
isTracked: true,
|
||||
trackedTimestamp: moment().unix(),
|
||||
isRegistered: false,
|
||||
};
|
||||
this.props.onNewTokenSubmitted(newToken);
|
||||
|
||||
@@ -23,7 +23,6 @@ import { GenerateOrderForm } from 'ts/containers/generate_order_form';
|
||||
import { localStorage } from 'ts/local_storage/local_storage';
|
||||
import { Dispatcher } from 'ts/redux/dispatcher';
|
||||
import { BlockchainErrs, HashData, Order, ProviderType, ScreenWidths, TokenByAddress, WebsitePaths } from 'ts/types';
|
||||
import { configs } from 'ts/utils/configs';
|
||||
import { constants } from 'ts/utils/constants';
|
||||
import { orderParser } from 'ts/utils/order_parser';
|
||||
import { Translate } from 'ts/utils/translate';
|
||||
@@ -170,67 +169,53 @@ export class LegacyPortal extends React.Component<LegacyPortalProps, LegacyPorta
|
||||
/>
|
||||
<div id="portal" className="mx-auto max-width-4" style={{ width: '100%' }}>
|
||||
<Paper className="mb3 mt2">
|
||||
{!configs.IS_MAINNET_ENABLED && this.props.networkId === constants.NETWORK_ID_MAINNET ? (
|
||||
<div className="p3 center">
|
||||
<div className="h2 py2">Mainnet unavailable</div>
|
||||
<div className="mx-auto pb2 pt2">
|
||||
<img src="/images/zrx_token.png" style={{ width: 150 }} />
|
||||
</div>
|
||||
<div>
|
||||
0x portal is currently unavailable on the Ethereum mainnet.
|
||||
<div>To try it out, switch to the Kovan test network (networkId: 42).</div>
|
||||
<div className="py2">Check back soon!</div>
|
||||
</div>
|
||||
<div className="mx-auto flex">
|
||||
<div className="col col-2 pr2 pt1 sm-hide xs-hide" style={portalMenuContainerStyle}>
|
||||
<LegacyPortalMenu menuItemStyle={{ color: colors.white }} />
|
||||
</div>
|
||||
) : (
|
||||
<div className="mx-auto flex">
|
||||
<div className="col col-2 pr2 pt1 sm-hide xs-hide" style={portalMenuContainerStyle}>
|
||||
<LegacyPortalMenu menuItemStyle={{ color: colors.white }} />
|
||||
</div>
|
||||
<div className="col col-12 lg-col-10 md-col-10 sm-col sm-col-12">
|
||||
<div className="py2" style={{ backgroundColor: colors.grey50 }}>
|
||||
{this.props.blockchainIsLoaded ? (
|
||||
<Switch>
|
||||
<Route
|
||||
path={`${WebsitePaths.Portal}/weth`}
|
||||
render={this._renderEthWrapper.bind(this)}
|
||||
/>
|
||||
<Route
|
||||
path={`${WebsitePaths.Portal}/fill`}
|
||||
render={this._renderFillOrder.bind(this)}
|
||||
/>
|
||||
<Route
|
||||
path={`${WebsitePaths.Portal}/balances`}
|
||||
render={this._renderTokenBalances.bind(this)}
|
||||
/>
|
||||
<Route
|
||||
path={`${WebsitePaths.Portal}/trades`}
|
||||
render={this._renderTradeHistory.bind(this)}
|
||||
/>
|
||||
<Route
|
||||
path={`${WebsitePaths.Home}`}
|
||||
render={this._renderGenerateOrderForm.bind(this)}
|
||||
/>
|
||||
</Switch>
|
||||
) : (
|
||||
<div className="pt4 sm-px2 sm-pt2 sm-m1" style={{ height: 500 }}>
|
||||
<div
|
||||
className="relative sm-px2 sm-pt2 sm-m1"
|
||||
style={{ height: 122, top: '50%', transform: 'translateY(-50%)' }}
|
||||
>
|
||||
<div className="center pb2">
|
||||
<CircularProgress size={40} thickness={5} />
|
||||
</div>
|
||||
<div className="center pt2" style={{ paddingBottom: 11 }}>
|
||||
Loading Portal...
|
||||
</div>
|
||||
<div className="col col-12 lg-col-10 md-col-10 sm-col sm-col-12">
|
||||
<div className="py2" style={{ backgroundColor: colors.grey50 }}>
|
||||
{this.props.blockchainIsLoaded ? (
|
||||
<Switch>
|
||||
<Route
|
||||
path={`${WebsitePaths.Portal}/weth`}
|
||||
render={this._renderEthWrapper.bind(this)}
|
||||
/>
|
||||
<Route
|
||||
path={`${WebsitePaths.Portal}/fill`}
|
||||
render={this._renderFillOrder.bind(this)}
|
||||
/>
|
||||
<Route
|
||||
path={`${WebsitePaths.Portal}/balances`}
|
||||
render={this._renderTokenBalances.bind(this)}
|
||||
/>
|
||||
<Route
|
||||
path={`${WebsitePaths.Portal}/trades`}
|
||||
render={this._renderTradeHistory.bind(this)}
|
||||
/>
|
||||
<Route
|
||||
path={`${WebsitePaths.Home}`}
|
||||
render={this._renderGenerateOrderForm.bind(this)}
|
||||
/>
|
||||
</Switch>
|
||||
) : (
|
||||
<div className="pt4 sm-px2 sm-pt2 sm-m1" style={{ height: 500 }}>
|
||||
<div
|
||||
className="relative sm-px2 sm-pt2 sm-m1"
|
||||
style={{ height: 122, top: '50%', transform: 'translateY(-50%)' }}
|
||||
>
|
||||
<div className="center pb2">
|
||||
<CircularProgress size={40} thickness={5} />
|
||||
</div>
|
||||
<div className="center pt2" style={{ paddingBottom: 11 }}>
|
||||
Loading Portal...
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Paper>
|
||||
<BlockchainErrDialog
|
||||
blockchain={this._blockchain}
|
||||
@@ -249,16 +234,14 @@ export class LegacyPortal extends React.Component<LegacyPortalProps, LegacyPorta
|
||||
onToggleDialog={this._onPortalDisclaimerAccepted.bind(this)}
|
||||
/>
|
||||
<FlashMessage dispatcher={this.props.dispatcher} flashMessage={this.props.flashMessage} />
|
||||
{this.props.blockchainIsLoaded && (
|
||||
<LedgerConfigDialog
|
||||
providerType={this.props.providerType}
|
||||
networkId={this.props.networkId}
|
||||
blockchain={this._blockchain}
|
||||
dispatcher={this.props.dispatcher}
|
||||
toggleDialogFn={this.onToggleLedgerDialog.bind(this)}
|
||||
isOpen={this.state.isLedgerDialogOpen}
|
||||
/>
|
||||
)}
|
||||
<LedgerConfigDialog
|
||||
providerType={this.props.providerType}
|
||||
networkId={this.props.networkId}
|
||||
blockchain={this._blockchain}
|
||||
dispatcher={this.props.dispatcher}
|
||||
toggleDialogFn={this.onToggleLedgerDialog.bind(this)}
|
||||
isOpen={this.state.isLedgerDialogOpen}
|
||||
/>
|
||||
</div>
|
||||
<Footer translate={this.props.translate} dispatcher={this.props.dispatcher} />
|
||||
</div>
|
||||
@@ -292,8 +275,7 @@ export class LegacyPortal extends React.Component<LegacyPortalProps, LegacyPorta
|
||||
);
|
||||
}
|
||||
private _renderTokenBalances(): React.ReactNode {
|
||||
const allTokens = _.values(this.props.tokenByAddress);
|
||||
const trackedTokens = _.filter(allTokens, t => t.isTracked);
|
||||
const trackedTokens = utils.getTrackedTokens(this.props.tokenByAddress);
|
||||
return (
|
||||
<TokenBalances
|
||||
blockchain={this._blockchain}
|
||||
|
||||
@@ -186,9 +186,11 @@ class PlainPortalOnboardingFlow extends React.Component<PortalOnboardingFlowProp
|
||||
const ethToken = utils.getEthToken(this.props.tokenByAddress);
|
||||
const zrxToken = utils.getZrxToken(this.props.tokenByAddress);
|
||||
if (ethToken && zrxToken) {
|
||||
const ethTokenAllowance = this.props.trackedTokenStateByAddress[ethToken.address].allowance;
|
||||
const zrxTokenAllowance = this.props.trackedTokenStateByAddress[zrxToken.address].allowance;
|
||||
return ethTokenAllowance > new BigNumber(0) && zrxTokenAllowance > new BigNumber(0);
|
||||
const ethTokenState = this.props.trackedTokenStateByAddress[ethToken.address];
|
||||
const zrxTokenState = this.props.trackedTokenStateByAddress[zrxToken.address];
|
||||
if (ethTokenState && zrxTokenState) {
|
||||
return ethTokenState.allowance.gt(0) && zrxTokenState.allowance.gt(0);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -251,12 +253,15 @@ class PlainPortalOnboardingFlow extends React.Component<PortalOnboardingFlowProp
|
||||
if (!token) {
|
||||
return null;
|
||||
}
|
||||
const tokenState = this.props.trackedTokenStateByAddress[token.address];
|
||||
const tokenStateIfExists = this.props.trackedTokenStateByAddress[token.address];
|
||||
if (_.isUndefined(tokenStateIfExists)) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<AllowanceToggle
|
||||
token={token}
|
||||
tokenState={tokenState}
|
||||
isDisabled={!tokenState.isLoaded}
|
||||
tokenState={tokenStateIfExists}
|
||||
isDisabled={!tokenStateIfExists.isLoaded}
|
||||
blockchain={this.props.blockchain}
|
||||
// tslint:disable-next-line:jsx-no-lambda
|
||||
refetchTokenStateAsync={async () => this.props.refetchTokenStateAsync(token.address)}
|
||||
|
||||
@@ -152,9 +152,8 @@ export class Portal extends React.Component<PortalProps, PortalState> {
|
||||
}
|
||||
public componentDidUpdate(prevProps: PortalProps): void {
|
||||
if (!prevProps.blockchainIsLoaded && this.props.blockchainIsLoaded) {
|
||||
const trackedTokenAddresses = _.keys(this.state.trackedTokenStateByAddress);
|
||||
// tslint:disable-next-line:no-floating-promises
|
||||
this._fetchBalancesAndAllowancesAsync(trackedTokenAddresses);
|
||||
this._fetchBalancesAndAllowancesAsync(this._getCurrentTrackedTokensAddresses());
|
||||
}
|
||||
}
|
||||
public componentWillReceiveProps(nextProps: PortalProps): void {
|
||||
@@ -182,17 +181,17 @@ export class Portal extends React.Component<PortalProps, PortalState> {
|
||||
prevPathname: nextProps.location.pathname,
|
||||
});
|
||||
}
|
||||
|
||||
// If the address changed, but the network did not, we can just refetch the currently tracked tokens.
|
||||
if (
|
||||
nextProps.userAddress !== this.props.userAddress ||
|
||||
nextProps.networkId !== this.props.networkId ||
|
||||
(nextProps.userAddress !== this.props.userAddress && nextProps.networkId === this.props.networkId) ||
|
||||
nextProps.lastForceTokenStateRefetch !== this.props.lastForceTokenStateRefetch
|
||||
) {
|
||||
const trackedTokenAddresses = _.keys(this.state.trackedTokenStateByAddress);
|
||||
// tslint:disable-next-line:no-floating-promises
|
||||
this._fetchBalancesAndAllowancesAsync(trackedTokenAddresses);
|
||||
this._fetchBalancesAndAllowancesAsync(this._getCurrentTrackedTokensAddresses());
|
||||
}
|
||||
|
||||
const nextTrackedTokens = this._getTrackedTokens(nextProps.tokenByAddress);
|
||||
const nextTrackedTokens = utils.getTrackedTokens(nextProps.tokenByAddress);
|
||||
const trackedTokens = this._getCurrentTrackedTokens();
|
||||
|
||||
if (!_.isEqual(nextTrackedTokens, trackedTokens)) {
|
||||
@@ -200,7 +199,7 @@ export class Portal extends React.Component<PortalProps, PortalState> {
|
||||
const newTokenAddresses = _.map(newTokens, token => token.address);
|
||||
// Add placeholder entry for this token to the state, since fetching the
|
||||
// balance/allowance is asynchronous
|
||||
const trackedTokenStateByAddress = this.state.trackedTokenStateByAddress;
|
||||
const trackedTokenStateByAddress = { ...this.state.trackedTokenStateByAddress };
|
||||
for (const tokenAddress of newTokenAddresses) {
|
||||
trackedTokenStateByAddress[tokenAddress] = {
|
||||
balance: new BigNumber(0),
|
||||
@@ -265,16 +264,16 @@ export class Portal extends React.Component<PortalProps, PortalState> {
|
||||
networkId={this.props.networkId}
|
||||
/>
|
||||
<FlashMessage dispatcher={this.props.dispatcher} flashMessage={this.props.flashMessage} />
|
||||
{this.props.blockchainIsLoaded && (
|
||||
<LedgerConfigDialog
|
||||
providerType={this.props.providerType}
|
||||
networkId={this.props.networkId}
|
||||
blockchain={this._blockchain}
|
||||
dispatcher={this.props.dispatcher}
|
||||
toggleDialogFn={this._onToggleLedgerDialog.bind(this)}
|
||||
isOpen={this.state.isLedgerDialogOpen}
|
||||
/>
|
||||
)}
|
||||
|
||||
<LedgerConfigDialog
|
||||
providerType={this.props.providerType}
|
||||
networkId={this.props.networkId}
|
||||
blockchain={this._blockchain}
|
||||
dispatcher={this.props.dispatcher}
|
||||
toggleDialogFn={this._onToggleLedgerDialog.bind(this)}
|
||||
isOpen={this.state.isLedgerDialogOpen}
|
||||
/>
|
||||
|
||||
<AssetPicker
|
||||
userAddress={this.props.userAddress}
|
||||
networkId={this.props.networkId}
|
||||
@@ -563,9 +562,9 @@ export class Portal extends React.Component<PortalProps, PortalState> {
|
||||
if (this.state.tokenManagementState === TokenManagementState.Remove && !isDefaultTrackedToken) {
|
||||
if (token.isRegistered) {
|
||||
// Remove the token from tracked tokens
|
||||
const newToken = {
|
||||
const newToken: Token = {
|
||||
...token,
|
||||
isTracked: false,
|
||||
trackedTimestamp: undefined,
|
||||
};
|
||||
this.props.dispatcher.updateTokenByAddress([newToken]);
|
||||
} else {
|
||||
@@ -608,17 +607,12 @@ export class Portal extends React.Component<PortalProps, PortalState> {
|
||||
const isSmallScreen = this.props.screenWidth === ScreenWidths.Sm;
|
||||
return isSmallScreen;
|
||||
}
|
||||
|
||||
private _getCurrentTrackedTokens(): Token[] {
|
||||
return this._getTrackedTokens(this.props.tokenByAddress);
|
||||
return utils.getTrackedTokens(this.props.tokenByAddress);
|
||||
}
|
||||
|
||||
private _getTrackedTokens(tokenByAddress: TokenByAddress): Token[] {
|
||||
const allTokens = _.values(tokenByAddress);
|
||||
const trackedTokens = _.filter(allTokens, t => t.isTracked);
|
||||
return trackedTokens;
|
||||
private _getCurrentTrackedTokensAddresses(): string[] {
|
||||
return _.map(this._getCurrentTrackedTokens(), token => token.address);
|
||||
}
|
||||
|
||||
private _getInitialTrackedTokenStateByAddress(trackedTokens: Token[]): TokenStateByAddress {
|
||||
const trackedTokenStateByAddress: TokenStateByAddress = {};
|
||||
_.each(trackedTokens, token => {
|
||||
|
||||
@@ -308,7 +308,7 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala
|
||||
const trackedTokensStartingWithEtherToken = trackedTokens.sort(
|
||||
firstBy((t: Token) => t.symbol !== ETHER_TOKEN_SYMBOL)
|
||||
.thenBy((t: Token) => t.symbol !== ZRX_TOKEN_SYMBOL)
|
||||
.thenBy('address'),
|
||||
.thenBy('trackedTimestamp'),
|
||||
);
|
||||
const tableRows = _.map(
|
||||
trackedTokensStartingWithEtherToken,
|
||||
@@ -424,9 +424,9 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala
|
||||
if (!this.state.isAddingToken && !isDefaultTrackedToken) {
|
||||
if (token.isRegistered) {
|
||||
// Remove the token from tracked tokens
|
||||
const newToken = {
|
||||
const newToken: Token = {
|
||||
...token,
|
||||
isTracked: false,
|
||||
trackedTimestamp: undefined,
|
||||
};
|
||||
this.props.dispatcher.updateTokenByAddress([newToken]);
|
||||
} else {
|
||||
|
||||
@@ -13,7 +13,6 @@ import { ProviderDisplay } from 'ts/components/top_bar/provider_display';
|
||||
import { TopBarMenuItem } from 'ts/components/top_bar/top_bar_menu_item';
|
||||
import { DropDown } from 'ts/components/ui/drop_down';
|
||||
import { Dispatcher } from 'ts/redux/dispatcher';
|
||||
import { zIndex } from 'ts/style/z_index';
|
||||
import { Deco, Key, ProviderType, WebsiteLegacyPaths, WebsitePaths } from 'ts/types';
|
||||
import { constants } from 'ts/utils/constants';
|
||||
import { Translate } from 'ts/utils/translate';
|
||||
@@ -58,7 +57,6 @@ const styles: Styles = {
|
||||
width: '100%',
|
||||
position: 'relative',
|
||||
top: 0,
|
||||
zIndex: zIndex.topBar,
|
||||
paddingBottom: 1,
|
||||
},
|
||||
bottomBar: {
|
||||
|
||||
@@ -156,6 +156,20 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
|
||||
isHoveringSidebar: false,
|
||||
};
|
||||
}
|
||||
public componentDidUpdate(prevProps: WalletProps): void {
|
||||
const currentTrackedTokens = this.props.trackedTokens;
|
||||
const differentTrackedTokens = _.difference(currentTrackedTokens, prevProps.trackedTokens);
|
||||
const firstDifferentTrackedToken = _.head(differentTrackedTokens);
|
||||
// check if there is only one different token, and if that token is a member of the current tracked tokens
|
||||
// this means that the token was added, not removed
|
||||
if (
|
||||
!_.isUndefined(firstDifferentTrackedToken) &&
|
||||
_.size(differentTrackedTokens) === 1 &&
|
||||
_.includes(currentTrackedTokens, firstDifferentTrackedToken)
|
||||
) {
|
||||
document.getElementById(firstDifferentTrackedToken.address).scrollIntoView();
|
||||
}
|
||||
}
|
||||
public render(): React.ReactNode {
|
||||
const isBlockchainLoaded = this.props.blockchainIsLoaded && this.props.blockchainErr === BlockchainErrs.NoError;
|
||||
return (
|
||||
@@ -318,7 +332,7 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
|
||||
const trackedTokensStartingWithEtherToken = trackedTokens.sort(
|
||||
firstBy((t: Token) => t.symbol !== constants.ETHER_TOKEN_SYMBOL)
|
||||
.thenBy((t: Token) => t.symbol !== constants.ZRX_TOKEN_SYMBOL)
|
||||
.thenBy('address'),
|
||||
.thenBy('trackedTimestamp'),
|
||||
);
|
||||
return _.map(trackedTokensStartingWithEtherToken, this._renderTokenRow.bind(this));
|
||||
}
|
||||
@@ -375,7 +389,7 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
|
||||
const style = { ...styles.tokenItem, ...additionalStyle };
|
||||
const etherToken = this._getEthToken();
|
||||
return (
|
||||
<div key={key} className={`flex flex-column ${className || ''}`}>
|
||||
<div id={key} key={key} className={`flex flex-column ${className || ''}`}>
|
||||
<StandardIconRow
|
||||
icon={icon}
|
||||
main={
|
||||
|
||||
@@ -97,6 +97,7 @@ render(
|
||||
<Route path={WebsitePaths.FAQ} component={FAQ as any} />
|
||||
<Route path={WebsitePaths.About} component={About as any} />
|
||||
<Route path={WebsitePaths.Wiki} component={Wiki as any} />
|
||||
<Route path={`${WebsitePaths.Docs}`} component={LazyZeroExJSDocumentation} />
|
||||
<Route path={`${WebsitePaths.ZeroExJs}/:version?`} component={LazyZeroExJSDocumentation} />
|
||||
<Route path={`${WebsitePaths.Connect}/:version?`} component={LazyConnectDocumentation} />
|
||||
<Route
|
||||
|
||||
@@ -11,15 +11,15 @@ export interface NotFoundProps {
|
||||
dispatcher: Dispatcher;
|
||||
}
|
||||
|
||||
export const NotFound = (_props: NotFoundProps) => {
|
||||
export const NotFound = (props: NotFoundProps) => {
|
||||
return (
|
||||
<div>
|
||||
<TopBar blockchainIsLoaded={false} location={this.props.location} translate={this.props.translate} />
|
||||
<TopBar blockchainIsLoaded={false} location={props.location} translate={props.translate} />
|
||||
<FullscreenMessage
|
||||
headerText={'404 Not Found'}
|
||||
bodyText={"Hm... looks like we couldn't find what you are looking for."}
|
||||
/>
|
||||
<Footer translate={this.props.translate} dispatcher={this.props.dispatcher} />
|
||||
<Footer translate={props.translate} dispatcher={props.dispatcher} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -13,8 +13,8 @@ export interface Token {
|
||||
address: string;
|
||||
symbol: string;
|
||||
decimals: number;
|
||||
isTracked: boolean;
|
||||
isRegistered: boolean;
|
||||
trackedTimestamp?: number;
|
||||
}
|
||||
|
||||
export interface TokenByAddress {
|
||||
@@ -356,6 +356,7 @@ export enum WebsiteLegacyPaths {
|
||||
export enum WebsitePaths {
|
||||
Portal = '/portal',
|
||||
Wiki = '/wiki',
|
||||
Docs = '/docs',
|
||||
ZeroExJs = '/docs/0x.js',
|
||||
Home = '/',
|
||||
FAQ = '/faq',
|
||||
@@ -488,6 +489,16 @@ export enum Providers {
|
||||
Mist = 'MIST',
|
||||
}
|
||||
|
||||
export interface InjectedProviderUpdate {
|
||||
selectedAddress: string;
|
||||
networkVersion: string;
|
||||
}
|
||||
|
||||
export interface InjectedProviderObservable {
|
||||
subscribe(updateHandler: (update: InjectedProviderUpdate) => void): void;
|
||||
unsubscribe(updateHandler: (update: InjectedProviderUpdate) => void): void;
|
||||
}
|
||||
|
||||
export interface TimestampMsRange {
|
||||
startTimestampMs: number;
|
||||
endTimestampMs: number;
|
||||
|
||||
@@ -63,10 +63,9 @@ export const configs = {
|
||||
TKN: '/images/token_icons/tokencard.png',
|
||||
TRST: '/images/token_icons/trust.png',
|
||||
} as { [symbol: string]: string },
|
||||
IS_MAINNET_ENABLED: true,
|
||||
GOOGLE_ANALYTICS_ID: 'UA-98720122-1',
|
||||
LAST_LOCAL_STORAGE_FILL_CLEARANCE_DATE: '2017-11-22',
|
||||
LAST_LOCAL_STORAGE_TRACKED_TOKEN_CLEARANCE_DATE: '2017-12-19',
|
||||
LAST_LOCAL_STORAGE_TRACKED_TOKEN_CLEARANCE_DATE: '2018-6-25',
|
||||
OUTDATED_WRAPPED_ETHERS: [
|
||||
{
|
||||
42: {
|
||||
|
||||
@@ -355,6 +355,11 @@ export const utils = {
|
||||
const token = _.find(tokens, { symbol });
|
||||
return token;
|
||||
},
|
||||
getTrackedTokens(tokenByAddress: TokenByAddress): Token[] {
|
||||
const allTokens = _.values(tokenByAddress);
|
||||
const trackedTokens = _.filter(allTokens, t => this.isTokenTracked(t));
|
||||
return trackedTokens;
|
||||
},
|
||||
getFormattedAmountFromToken(token: Token, tokenState: TokenState): string {
|
||||
return utils.getFormattedAmount(tokenState.balance, token.decimals, token.symbol);
|
||||
},
|
||||
@@ -381,4 +386,7 @@ export const utils = {
|
||||
return BrowserType.Other;
|
||||
}
|
||||
},
|
||||
isTokenTracked(token: Token): boolean {
|
||||
return !_.isUndefined(token.trackedTimestamp);
|
||||
},
|
||||
};
|
||||
|
||||
20
yarn.lock
20
yarn.lock
@@ -44,6 +44,24 @@
|
||||
ethereumjs-util "5.1.5"
|
||||
lodash "4.17.10"
|
||||
|
||||
"@0xproject/order-watcher@^0.0.6":
|
||||
version "0.0.6"
|
||||
resolved "https://registry.npmjs.org/@0xproject/order-watcher/-/order-watcher-0.0.6.tgz#85a8fb21e5755bb555f427b12d64d10b89b332e6"
|
||||
dependencies:
|
||||
"@0xproject/assert" "^0.2.12"
|
||||
"@0xproject/base-contract" "^0.3.4"
|
||||
"@0xproject/contract-wrappers" "^0.0.5"
|
||||
"@0xproject/fill-scenarios" "^0.0.4"
|
||||
"@0xproject/json-schemas" "^0.8.1"
|
||||
"@0xproject/order-utils" "^0.0.7"
|
||||
"@0xproject/types" "^0.8.1"
|
||||
"@0xproject/typescript-typings" "^0.4.1"
|
||||
"@0xproject/utils" "^0.7.1"
|
||||
"@0xproject/web3-wrapper" "^0.7.1"
|
||||
bintrees "1.0.2"
|
||||
ethers "3.0.22"
|
||||
lodash "4.17.10"
|
||||
|
||||
"@0xproject/types@^0.5.0":
|
||||
version "0.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@0xproject/types/-/types-0.5.0.tgz#ba3cfbc11a8c6344b57c9680aa7df2ea84b9bf05"
|
||||
@@ -1708,7 +1726,7 @@ bindings@^1.2.1, bindings@^1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.3.0.tgz#b346f6ecf6a95f5a815c5839fc7cdb22502f1ed7"
|
||||
|
||||
bintrees@^1.0.2:
|
||||
bintrees@1.0.2, bintrees@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/bintrees/-/bintrees-1.0.2.tgz#49f896d6e858a4a499df85c38fb399b9aff840f8"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user