Move OrderValidationUtils (+ tests) and ExchangeTransferSimulator to order-utils
This commit is contained in:
		@@ -13,7 +13,7 @@
 | 
				
			|||||||
        "pre_build": "run-s update_artifacts generate_contract_wrappers",
 | 
					        "pre_build": "run-s update_artifacts generate_contract_wrappers",
 | 
				
			||||||
        "transpile": "tsc",
 | 
					        "transpile": "tsc",
 | 
				
			||||||
        "copy_monorepo_scripts": "copyfiles -u 3 './lib/src/monorepo_scripts/**/*' ./scripts",
 | 
					        "copy_monorepo_scripts": "copyfiles -u 3 './lib/src/monorepo_scripts/**/*' ./scripts",
 | 
				
			||||||
        "generate_contract_wrappers": "abi-gen --abis 'lib/src/artifacts/@(Exchange|IWallet|IValidator).json' --template ../contract_templates/contract.handlebars --partials '../contract_templates/partials/**/*.handlebars' --output src/generated_contract_wrappers --backend ethers",
 | 
					        "generate_contract_wrappers": "abi-gen --abis 'lib/src/artifacts/@(Exchange|IWallet|IValidator|DummyERC20Token|ERC20Proxy).json' --template ../contract_templates/contract.handlebars --partials '../contract_templates/partials/**/*.handlebars' --output src/generated_contract_wrappers --backend ethers",
 | 
				
			||||||
        "update_artifacts": "for i in ${npm_package_config_contracts}; do copyfiles -u 4 ../migrations/artifacts/2.0.0/$i.json lib/src/artifacts; done;",
 | 
					        "update_artifacts": "for i in ${npm_package_config_contracts}; do copyfiles -u 4 ../migrations/artifacts/2.0.0/$i.json lib/src/artifacts; done;",
 | 
				
			||||||
        "test": "yarn run_mocha",
 | 
					        "test": "yarn run_mocha",
 | 
				
			||||||
        "rebuild_and_test": "run-s build test",
 | 
					        "rebuild_and_test": "run-s build test",
 | 
				
			||||||
@@ -29,7 +29,7 @@
 | 
				
			|||||||
        "upload_docs_json": "aws s3 cp generated_docs/index.json $S3_URL --profile 0xproject --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers --content-type application/json"
 | 
					        "upload_docs_json": "aws s3 cp generated_docs/index.json $S3_URL --profile 0xproject --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers --content-type application/json"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "config": {
 | 
					    "config": {
 | 
				
			||||||
        "contracts": "IWallet IValidator Exchange",
 | 
					        "contracts": "IWallet IValidator Exchange DummyERC20Token ERC20Proxy",
 | 
				
			||||||
        "postpublish": {
 | 
					        "postpublish": {
 | 
				
			||||||
            "docPublishConfigs": {
 | 
					            "docPublishConfigs": {
 | 
				
			||||||
                "extraFileIncludes": [
 | 
					                "extraFileIncludes": [
 | 
				
			||||||
@@ -52,6 +52,7 @@
 | 
				
			|||||||
    "homepage": "https://github.com/0xProject/0x-monorepo/packages/order-utils/README.md",
 | 
					    "homepage": "https://github.com/0xProject/0x-monorepo/packages/order-utils/README.md",
 | 
				
			||||||
    "devDependencies": {
 | 
					    "devDependencies": {
 | 
				
			||||||
        "@0xproject/dev-utils": "^0.4.2",
 | 
					        "@0xproject/dev-utils": "^0.4.2",
 | 
				
			||||||
 | 
					        "@0xproject/migrations": "^0.0.6",
 | 
				
			||||||
        "@0xproject/monorepo-scripts": "^0.1.20",
 | 
					        "@0xproject/monorepo-scripts": "^0.1.20",
 | 
				
			||||||
        "@0xproject/tslint-config": "^0.4.18",
 | 
					        "@0xproject/tslint-config": "^0.4.18",
 | 
				
			||||||
        "@types/ethereumjs-abi": "^0.6.0",
 | 
					        "@types/ethereumjs-abi": "^0.6.0",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
import { BigNumber } from '@0xproject/utils';
 | 
					import { BigNumber } from '@0xproject/utils';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export abstract class AbstractBalanceAndProxyAllowanceFetcher {
 | 
					export abstract class AbstractBalanceAndProxyAllowanceFetcher {
 | 
				
			||||||
    public abstract async getBalanceAsync(tokenAddress: string, userAddress: string): Promise<BigNumber>;
 | 
					    public abstract async getBalanceAsync(assetData: string, userAddress: string): Promise<BigNumber>;
 | 
				
			||||||
    public abstract async getProxyAllowanceAsync(tokenAddress: string, userAddress: string): Promise<BigNumber>;
 | 
					    public abstract async getProxyAllowanceAsync(assetData: string, userAddress: string): Promise<BigNumber>;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					import { BigNumber } from '@0xproject/utils';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export abstract class AbstractBalanceAndProxyAllowanceLazyStore {
 | 
				
			||||||
 | 
					    public abstract async getBalanceAsync(assetData: string, userAddress: string): Promise<BigNumber>;
 | 
				
			||||||
 | 
					    public abstract async getProxyAllowanceAsync(assetData: string, userAddress: string): Promise<BigNumber>;
 | 
				
			||||||
 | 
					    public abstract setBalance(assetData: string, userAddress: string, balance: BigNumber): void;
 | 
				
			||||||
 | 
					    public abstract deleteBalance(assetData: string, userAddress: string): void;
 | 
				
			||||||
 | 
					    public abstract setProxyAllowance(assetData: string, userAddress: string, proxyAllowance: BigNumber): void;
 | 
				
			||||||
 | 
					    public abstract deleteProxyAllowance(assetData: string, userAddress: string): void;
 | 
				
			||||||
 | 
					    public abstract deleteAll(): void;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,10 +1,14 @@
 | 
				
			|||||||
import { Artifact } from '@0xproject/types';
 | 
					import { ContractArtifact } from '@0xproject/sol-compiler';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import * as DummyERC20Token from './artifacts/DummyERC20Token.json';
 | 
				
			||||||
 | 
					import * as ERC20Proxy from './artifacts/ERC20Proxy.json';
 | 
				
			||||||
import * as Exchange from './artifacts/Exchange.json';
 | 
					import * as Exchange from './artifacts/Exchange.json';
 | 
				
			||||||
import * as IValidator from './artifacts/IValidator.json';
 | 
					import * as IValidator from './artifacts/IValidator.json';
 | 
				
			||||||
import * as IWallet from './artifacts/IWallet.json';
 | 
					import * as IWallet from './artifacts/IWallet.json';
 | 
				
			||||||
export const artifacts = {
 | 
					export const artifacts = {
 | 
				
			||||||
    Exchange: (Exchange as any) as Artifact,
 | 
					    ERC20Proxy: (ERC20Proxy as any) as ContractArtifact,
 | 
				
			||||||
    IWallet: (IWallet as any) as Artifact,
 | 
					    DummyERC20Token: (DummyERC20Token as any) as ContractArtifact,
 | 
				
			||||||
    IValidator: (IValidator as any) as Artifact,
 | 
					    Exchange: (Exchange as any) as ContractArtifact,
 | 
				
			||||||
 | 
					    IWallet: (IWallet as any) as ContractArtifact,
 | 
				
			||||||
 | 
					    IValidator: (IValidator as any) as ContractArtifact,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,3 +1,8 @@
 | 
				
			|||||||
 | 
					import { BigNumber } from '@0xproject/utils';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const constants = {
 | 
					export const constants = {
 | 
				
			||||||
    NULL_ADDRESS: '0x0000000000000000000000000000000000000000',
 | 
					    NULL_ADDRESS: '0x0000000000000000000000000000000000000000',
 | 
				
			||||||
 | 
					    // tslint:disable-next-line:custom-no-magic-numbers
 | 
				
			||||||
 | 
					    UNLIMITED_ALLOWANCE_IN_BASE_UNITS: new BigNumber(2).pow(256).minus(1),
 | 
				
			||||||
 | 
					    TESTRPC_NETWORK_ID: 50,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										114
									
								
								packages/order-utils/src/exchange_transfer_simulator.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								packages/order-utils/src/exchange_transfer_simulator.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,114 @@
 | 
				
			|||||||
 | 
					import { ExchangeContractErrs } from '@0xproject/types';
 | 
				
			||||||
 | 
					import { BigNumber } from '@0xproject/utils';
 | 
				
			||||||
 | 
					import { BlockParamLiteral } from 'ethereum-types';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { AbstractBalanceAndProxyAllowanceLazyStore } from './abstract/abstract_balance_and_proxy_allowance_lazy_store';
 | 
				
			||||||
 | 
					import { constants } from './constants';
 | 
				
			||||||
 | 
					import { TradeSide, TransferType } from './types';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum FailureReason {
 | 
				
			||||||
 | 
					    Balance = 'balance',
 | 
				
			||||||
 | 
					    ProxyAllowance = 'proxyAllowance',
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const ERR_MSG_MAPPING = {
 | 
				
			||||||
 | 
					    [FailureReason.Balance]: {
 | 
				
			||||||
 | 
					        [TradeSide.Maker]: {
 | 
				
			||||||
 | 
					            [TransferType.Trade]: ExchangeContractErrs.InsufficientMakerBalance,
 | 
				
			||||||
 | 
					            [TransferType.Fee]: ExchangeContractErrs.InsufficientMakerFeeBalance,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        [TradeSide.Taker]: {
 | 
				
			||||||
 | 
					            [TransferType.Trade]: ExchangeContractErrs.InsufficientTakerBalance,
 | 
				
			||||||
 | 
					            [TransferType.Fee]: ExchangeContractErrs.InsufficientTakerFeeBalance,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    [FailureReason.ProxyAllowance]: {
 | 
				
			||||||
 | 
					        [TradeSide.Maker]: {
 | 
				
			||||||
 | 
					            [TransferType.Trade]: ExchangeContractErrs.InsufficientMakerAllowance,
 | 
				
			||||||
 | 
					            [TransferType.Fee]: ExchangeContractErrs.InsufficientMakerFeeAllowance,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        [TradeSide.Taker]: {
 | 
				
			||||||
 | 
					            [TransferType.Trade]: ExchangeContractErrs.InsufficientTakerAllowance,
 | 
				
			||||||
 | 
					            [TransferType.Fee]: ExchangeContractErrs.InsufficientTakerFeeAllowance,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class ExchangeTransferSimulator {
 | 
				
			||||||
 | 
					    private _store: AbstractBalanceAndProxyAllowanceLazyStore;
 | 
				
			||||||
 | 
					    private static _throwValidationError(
 | 
				
			||||||
 | 
					        failureReason: FailureReason,
 | 
				
			||||||
 | 
					        tradeSide: TradeSide,
 | 
				
			||||||
 | 
					        transferType: TransferType,
 | 
				
			||||||
 | 
					    ): never {
 | 
				
			||||||
 | 
					        const errMsg = ERR_MSG_MAPPING[failureReason][tradeSide][transferType];
 | 
				
			||||||
 | 
					        throw new Error(errMsg);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    constructor(store: AbstractBalanceAndProxyAllowanceLazyStore) {
 | 
				
			||||||
 | 
					        this._store = store;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Simulates transferFrom call performed by a proxy
 | 
				
			||||||
 | 
					     * @param  assetData         Data of the asset being transferred. Includes
 | 
				
			||||||
 | 
					     *                           it's identifying information and assetType,
 | 
				
			||||||
 | 
					     *                           e.g address for ERC20, address & tokenId for ERC721
 | 
				
			||||||
 | 
					     * @param  from              Owner of the transferred tokens
 | 
				
			||||||
 | 
					     * @param  to                Recipient of the transferred tokens
 | 
				
			||||||
 | 
					     * @param  amountInBaseUnits The amount of tokens being transferred
 | 
				
			||||||
 | 
					     * @param  tradeSide         Is Maker/Taker transferring
 | 
				
			||||||
 | 
					     * @param  transferType      Is it a fee payment or a value transfer
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public async transferFromAsync(
 | 
				
			||||||
 | 
					        assetData: string,
 | 
				
			||||||
 | 
					        from: string,
 | 
				
			||||||
 | 
					        to: string,
 | 
				
			||||||
 | 
					        amountInBaseUnits: BigNumber,
 | 
				
			||||||
 | 
					        tradeSide: TradeSide,
 | 
				
			||||||
 | 
					        transferType: TransferType,
 | 
				
			||||||
 | 
					    ): Promise<void> {
 | 
				
			||||||
 | 
					        // HACK: When simulating an open order (e.g taker is NULL_ADDRESS), we don't want to adjust balances/
 | 
				
			||||||
 | 
					        // allowances for the taker. We do however, want to increase the balance of the maker since the maker
 | 
				
			||||||
 | 
					        // might be relying on those funds to fill subsequent orders or pay the order's fees.
 | 
				
			||||||
 | 
					        if (from === constants.NULL_ADDRESS && tradeSide === TradeSide.Taker) {
 | 
				
			||||||
 | 
					            await this._increaseBalanceAsync(assetData, to, amountInBaseUnits);
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const balance = await this._store.getBalanceAsync(assetData, from);
 | 
				
			||||||
 | 
					        const proxyAllowance = await this._store.getProxyAllowanceAsync(assetData, from);
 | 
				
			||||||
 | 
					        if (proxyAllowance.lessThan(amountInBaseUnits)) {
 | 
				
			||||||
 | 
					            ExchangeTransferSimulator._throwValidationError(FailureReason.ProxyAllowance, tradeSide, transferType);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (balance.lessThan(amountInBaseUnits)) {
 | 
				
			||||||
 | 
					            ExchangeTransferSimulator._throwValidationError(FailureReason.Balance, tradeSide, transferType);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        await this._decreaseProxyAllowanceAsync(assetData, from, amountInBaseUnits);
 | 
				
			||||||
 | 
					        await this._decreaseBalanceAsync(assetData, from, amountInBaseUnits);
 | 
				
			||||||
 | 
					        await this._increaseBalanceAsync(assetData, to, amountInBaseUnits);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    private async _decreaseProxyAllowanceAsync(
 | 
				
			||||||
 | 
					        assetData: string,
 | 
				
			||||||
 | 
					        userAddress: string,
 | 
				
			||||||
 | 
					        amountInBaseUnits: BigNumber,
 | 
				
			||||||
 | 
					    ): Promise<void> {
 | 
				
			||||||
 | 
					        const proxyAllowance = await this._store.getProxyAllowanceAsync(assetData, userAddress);
 | 
				
			||||||
 | 
					        if (!proxyAllowance.eq(constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS)) {
 | 
				
			||||||
 | 
					            this._store.setProxyAllowance(assetData, userAddress, proxyAllowance.minus(amountInBaseUnits));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    private async _increaseBalanceAsync(
 | 
				
			||||||
 | 
					        assetData: string,
 | 
				
			||||||
 | 
					        userAddress: string,
 | 
				
			||||||
 | 
					        amountInBaseUnits: BigNumber,
 | 
				
			||||||
 | 
					    ): Promise<void> {
 | 
				
			||||||
 | 
					        const balance = await this._store.getBalanceAsync(assetData, userAddress);
 | 
				
			||||||
 | 
					        this._store.setBalance(assetData, userAddress, balance.plus(amountInBaseUnits));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    private async _decreaseBalanceAsync(
 | 
				
			||||||
 | 
					        assetData: string,
 | 
				
			||||||
 | 
					        userAddress: string,
 | 
				
			||||||
 | 
					        amountInBaseUnits: BigNumber,
 | 
				
			||||||
 | 
					    ): Promise<void> {
 | 
				
			||||||
 | 
					        const balance = await this._store.getBalanceAsync(assetData, userAddress);
 | 
				
			||||||
 | 
					        this._store.setBalance(assetData, userAddress, balance.minus(amountInBaseUnits));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -19,3 +19,5 @@ export { AbstractOrderFilledCancelledFetcher } from './abstract/abstract_order_f
 | 
				
			|||||||
export { RemainingFillableCalculator } from './remaining_fillable_calculator';
 | 
					export { RemainingFillableCalculator } from './remaining_fillable_calculator';
 | 
				
			||||||
export { OrderStateUtils } from './order_state_utils';
 | 
					export { OrderStateUtils } from './order_state_utils';
 | 
				
			||||||
export { assetProxyUtils } from './asset_proxy_utils';
 | 
					export { assetProxyUtils } from './asset_proxy_utils';
 | 
				
			||||||
 | 
					export { OrderValidationUtils } from './order_validation_utils';
 | 
				
			||||||
 | 
					export { ExchangeTransferSimulator } from './exchange_transfer_simulator';
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										223
									
								
								packages/order-utils/src/order_validation_utils.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										223
									
								
								packages/order-utils/src/order_validation_utils.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,223 @@
 | 
				
			|||||||
 | 
					import { ExchangeContractErrs, Order, SignedOrder } from '@0xproject/types';
 | 
				
			||||||
 | 
					import { BigNumber } from '@0xproject/utils';
 | 
				
			||||||
 | 
					import * as _ from 'lodash';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { OrderError, TradeSide, TransferType } from './types';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { constants } from './constants';
 | 
				
			||||||
 | 
					import { ExchangeTransferSimulator } from './exchange_transfer_simulator';
 | 
				
			||||||
 | 
					import { ExchangeContract } from './generated_contract_wrappers/exchange';
 | 
				
			||||||
 | 
					import { orderHashUtils } from './order_hash';
 | 
				
			||||||
 | 
					import { isValidECSignature, parseECSignature } 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%
 | 
				
			||||||
 | 
					    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
 | 
				
			||||||
 | 
					        if (denominator.eq(0)) {
 | 
				
			||||||
 | 
					            throw new Error('denominator cannot be 0');
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const remainder = target.mul(numerator).mod(denominator);
 | 
				
			||||||
 | 
					        if (remainder.eq(0)) {
 | 
				
			||||||
 | 
					            return false; // no rounding error
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // tslint:disable-next-line:custom-no-magic-numbers
 | 
				
			||||||
 | 
					        const errPercentageTimes1000000 = remainder.mul(1000000).div(numerator.mul(target));
 | 
				
			||||||
 | 
					        // tslint:disable-next-line:custom-no-magic-numbers
 | 
				
			||||||
 | 
					        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,
 | 
				
			||||||
 | 
					        senderAddress: string,
 | 
				
			||||||
 | 
					        zrxTokenAddress: 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,
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    private static _validateRemainingFillAmountNotZeroOrThrow(
 | 
				
			||||||
 | 
					        takerAssetAmount: BigNumber,
 | 
				
			||||||
 | 
					        filledTakerTokenAmount: BigNumber,
 | 
				
			||||||
 | 
					    ): void {
 | 
				
			||||||
 | 
					        if (takerAssetAmount.eq(filledTakerTokenAmount)) {
 | 
				
			||||||
 | 
					            throw new Error(ExchangeContractErrs.OrderRemainingFillAmountZero);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    private static _validateOrderNotExpiredOrThrow(expirationTimeSeconds: BigNumber): void {
 | 
				
			||||||
 | 
					        const currentUnixTimestampSec = utils.getCurrentUnixTimestampSec();
 | 
				
			||||||
 | 
					        if (expirationTimeSeconds.lessThan(currentUnixTimestampSec)) {
 | 
				
			||||||
 | 
					            throw new Error(ExchangeContractErrs.OrderFillExpired);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    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;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    public async validateOrderFillableOrThrowAsync(
 | 
				
			||||||
 | 
					        exchangeTradeEmulator: ExchangeTransferSimulator,
 | 
				
			||||||
 | 
					        signedOrder: SignedOrder,
 | 
				
			||||||
 | 
					        zrxTokenAddress: string,
 | 
				
			||||||
 | 
					        expectedFillTakerTokenAmount?: BigNumber,
 | 
				
			||||||
 | 
					    ): Promise<void> {
 | 
				
			||||||
 | 
					        const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
 | 
				
			||||||
 | 
					        const filledTakerTokenAmount = await this._exchangeContract.filled.callAsync(orderHash);
 | 
				
			||||||
 | 
					        OrderValidationUtils._validateRemainingFillAmountNotZeroOrThrow(
 | 
				
			||||||
 | 
					            signedOrder.takerAssetAmount,
 | 
				
			||||||
 | 
					            filledTakerTokenAmount,
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        OrderValidationUtils._validateOrderNotExpiredOrThrow(signedOrder.expirationTimeSeconds);
 | 
				
			||||||
 | 
					        let fillTakerTokenAmount = signedOrder.takerAssetAmount.minus(filledTakerTokenAmount);
 | 
				
			||||||
 | 
					        if (!_.isUndefined(expectedFillTakerTokenAmount)) {
 | 
				
			||||||
 | 
					            fillTakerTokenAmount = expectedFillTakerTokenAmount;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        await OrderValidationUtils.validateFillOrderBalancesAllowancesThrowIfInvalidAsync(
 | 
				
			||||||
 | 
					            exchangeTradeEmulator,
 | 
				
			||||||
 | 
					            signedOrder,
 | 
				
			||||||
 | 
					            fillTakerTokenAmount,
 | 
				
			||||||
 | 
					            signedOrder.takerAddress,
 | 
				
			||||||
 | 
					            zrxTokenAddress,
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    public async validateFillOrderThrowIfInvalidAsync(
 | 
				
			||||||
 | 
					        exchangeTradeEmulator: ExchangeTransferSimulator,
 | 
				
			||||||
 | 
					        signedOrder: SignedOrder,
 | 
				
			||||||
 | 
					        fillTakerTokenAmount: BigNumber,
 | 
				
			||||||
 | 
					        takerAddress: string,
 | 
				
			||||||
 | 
					        zrxTokenAddress: string,
 | 
				
			||||||
 | 
					    ): Promise<BigNumber> {
 | 
				
			||||||
 | 
					        if (fillTakerTokenAmount.eq(0)) {
 | 
				
			||||||
 | 
					            throw new Error(ExchangeContractErrs.OrderFillAmountZero);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
 | 
				
			||||||
 | 
					        // TODO: Verify all signature types! To do this, we need access to a Provider...
 | 
				
			||||||
 | 
					        const ecSignature = parseECSignature(signedOrder.signature);
 | 
				
			||||||
 | 
					        if (!isValidECSignature(orderHash, ecSignature, signedOrder.makerAddress)) {
 | 
				
			||||||
 | 
					            throw new Error(OrderError.InvalidSignature);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const filledTakerTokenAmount = await this._exchangeContract.filled.callAsync(orderHash);
 | 
				
			||||||
 | 
					        OrderValidationUtils._validateRemainingFillAmountNotZeroOrThrow(
 | 
				
			||||||
 | 
					            signedOrder.takerAssetAmount,
 | 
				
			||||||
 | 
					            filledTakerTokenAmount,
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        if (signedOrder.takerAddress !== constants.NULL_ADDRESS && signedOrder.takerAddress !== takerAddress) {
 | 
				
			||||||
 | 
					            throw new Error(ExchangeContractErrs.TransactionSenderIsNotFillOrderTaker);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        OrderValidationUtils._validateOrderNotExpiredOrThrow(signedOrder.expirationTimeSeconds);
 | 
				
			||||||
 | 
					        const remainingTakerTokenAmount = signedOrder.takerAssetAmount.minus(filledTakerTokenAmount);
 | 
				
			||||||
 | 
					        const desiredFillTakerTokenAmount = remainingTakerTokenAmount.lessThan(fillTakerTokenAmount)
 | 
				
			||||||
 | 
					            ? remainingTakerTokenAmount
 | 
				
			||||||
 | 
					            : fillTakerTokenAmount;
 | 
				
			||||||
 | 
					        await OrderValidationUtils.validateFillOrderBalancesAllowancesThrowIfInvalidAsync(
 | 
				
			||||||
 | 
					            exchangeTradeEmulator,
 | 
				
			||||||
 | 
					            signedOrder,
 | 
				
			||||||
 | 
					            desiredFillTakerTokenAmount,
 | 
				
			||||||
 | 
					            takerAddress,
 | 
				
			||||||
 | 
					            zrxTokenAddress,
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const wouldRoundingErrorOccur = await OrderValidationUtils.isRoundingError(
 | 
				
			||||||
 | 
					            filledTakerTokenAmount,
 | 
				
			||||||
 | 
					            signedOrder.takerAssetAmount,
 | 
				
			||||||
 | 
					            signedOrder.makerAssetAmount,
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        if (wouldRoundingErrorOccur) {
 | 
				
			||||||
 | 
					            throw new Error(ExchangeContractErrs.OrderFillRoundingError);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return filledTakerTokenAmount;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    public async validateFillOrKillOrderThrowIfInvalidAsync(
 | 
				
			||||||
 | 
					        exchangeTradeEmulator: ExchangeTransferSimulator,
 | 
				
			||||||
 | 
					        signedOrder: SignedOrder,
 | 
				
			||||||
 | 
					        fillTakerTokenAmount: BigNumber,
 | 
				
			||||||
 | 
					        takerAddress: string,
 | 
				
			||||||
 | 
					        zrxTokenAddress: string,
 | 
				
			||||||
 | 
					    ): Promise<void> {
 | 
				
			||||||
 | 
					        const filledTakerTokenAmount = await this.validateFillOrderThrowIfInvalidAsync(
 | 
				
			||||||
 | 
					            exchangeTradeEmulator,
 | 
				
			||||||
 | 
					            signedOrder,
 | 
				
			||||||
 | 
					            fillTakerTokenAmount,
 | 
				
			||||||
 | 
					            takerAddress,
 | 
				
			||||||
 | 
					            zrxTokenAddress,
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        if (filledTakerTokenAmount !== fillTakerTokenAmount) {
 | 
				
			||||||
 | 
					            throw new Error(ExchangeContractErrs.InsufficientRemainingFillAmount);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -90,7 +90,7 @@ export async function isValidPresignedSignatureAsync(
 | 
				
			|||||||
    data: string,
 | 
					    data: string,
 | 
				
			||||||
    signerAddress: string,
 | 
					    signerAddress: string,
 | 
				
			||||||
): Promise<boolean> {
 | 
					): Promise<boolean> {
 | 
				
			||||||
    const exchangeContract = new ExchangeContract(artifacts.Exchange.abi, signerAddress, provider);
 | 
					    const exchangeContract = new ExchangeContract(artifacts.Exchange.compilerOutput.abi, signerAddress, provider);
 | 
				
			||||||
    const isValid = await exchangeContract.preSigned.callAsync(data, signerAddress);
 | 
					    const isValid = await exchangeContract.preSigned.callAsync(data, signerAddress);
 | 
				
			||||||
    return isValid;
 | 
					    return isValid;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -110,7 +110,7 @@ export async function isValidWalletSignatureAsync(
 | 
				
			|||||||
): Promise<boolean> {
 | 
					): Promise<boolean> {
 | 
				
			||||||
    // tslint:disable-next-line:custom-no-magic-numbers
 | 
					    // tslint:disable-next-line:custom-no-magic-numbers
 | 
				
			||||||
    const signatureWithoutType = signature.slice(-2);
 | 
					    const signatureWithoutType = signature.slice(-2);
 | 
				
			||||||
    const walletContract = new IWalletContract(artifacts.IWallet.abi, signerAddress, provider);
 | 
					    const walletContract = new IWalletContract(artifacts.IWallet.compilerOutput.abi, signerAddress, provider);
 | 
				
			||||||
    const isValid = await walletContract.isValidSignature.callAsync(data, signatureWithoutType);
 | 
					    const isValid = await walletContract.isValidSignature.callAsync(data, signatureWithoutType);
 | 
				
			||||||
    return isValid;
 | 
					    return isValid;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -129,7 +129,7 @@ export async function isValidValidatorSignatureAsync(
 | 
				
			|||||||
    signerAddress: string,
 | 
					    signerAddress: string,
 | 
				
			||||||
): Promise<boolean> {
 | 
					): Promise<boolean> {
 | 
				
			||||||
    const validatorSignature = parseValidatorSignature(signature);
 | 
					    const validatorSignature = parseValidatorSignature(signature);
 | 
				
			||||||
    const exchangeContract = new ExchangeContract(artifacts.Exchange.abi, signerAddress, provider);
 | 
					    const exchangeContract = new ExchangeContract(artifacts.Exchange.compilerOutput.abi, signerAddress, provider);
 | 
				
			||||||
    const isValidatorApproved = await exchangeContract.allowedValidators.callAsync(
 | 
					    const isValidatorApproved = await exchangeContract.allowedValidators.callAsync(
 | 
				
			||||||
        signerAddress,
 | 
					        signerAddress,
 | 
				
			||||||
        validatorSignature.validatorAddress,
 | 
					        validatorSignature.validatorAddress,
 | 
				
			||||||
@@ -138,7 +138,7 @@ export async function isValidValidatorSignatureAsync(
 | 
				
			|||||||
        throw new Error(`Validator ${validatorSignature.validatorAddress} was not pre-approved by ${signerAddress}.`);
 | 
					        throw new Error(`Validator ${validatorSignature.validatorAddress} was not pre-approved by ${signerAddress}.`);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const validatorContract = new IValidatorContract(artifacts.IValidator.abi, signerAddress, provider);
 | 
					    const validatorContract = new IValidatorContract(artifacts.IValidator.compilerOutput.abi, signerAddress, provider);
 | 
				
			||||||
    const isValid = await validatorContract.isValidSignature.callAsync(
 | 
					    const isValid = await validatorContract.isValidSignature.callAsync(
 | 
				
			||||||
        data,
 | 
					        data,
 | 
				
			||||||
        signerAddress,
 | 
					        signerAddress,
 | 
				
			||||||
@@ -260,12 +260,12 @@ export function addSignedMessagePrefix(message: string, messagePrefixType: Messa
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function hashTrezorPersonalMessage(message: Buffer): Buffer {
 | 
					/**
 | 
				
			||||||
    const prefix = ethUtil.toBuffer('\x19Ethereum Signed Message:\n' + String.fromCharCode(message.length));
 | 
					 * Parse a 0x protocol hex-encoded signature string into it's ECSignature components
 | 
				
			||||||
    return ethUtil.sha3(Buffer.concat([prefix, message]));
 | 
					 * @param signature A hex encoded ecSignature 0x Protocol signature
 | 
				
			||||||
}
 | 
					 * @return An ECSignature object with r,s,v parameters
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
function parseECSignature(signature: string): ECSignature {
 | 
					export function parseECSignature(signature: string): ECSignature {
 | 
				
			||||||
    const ecSignatureTypes = [SignatureType.EthSign, SignatureType.EIP712, SignatureType.Trezor];
 | 
					    const ecSignatureTypes = [SignatureType.EthSign, SignatureType.EIP712, SignatureType.Trezor];
 | 
				
			||||||
    assert.isOneOfExpectedSignatureTypes(signature, ecSignatureTypes);
 | 
					    assert.isOneOfExpectedSignatureTypes(signature, ecSignatureTypes);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -276,6 +276,11 @@ function parseECSignature(signature: string): ECSignature {
 | 
				
			|||||||
    return ecSignature;
 | 
					    return ecSignature;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function hashTrezorPersonalMessage(message: Buffer): Buffer {
 | 
				
			||||||
 | 
					    const prefix = ethUtil.toBuffer('\x19Ethereum Signed Message:\n' + String.fromCharCode(message.length));
 | 
				
			||||||
 | 
					    return ethUtil.sha3(Buffer.concat([prefix, message]));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function parseValidatorSignature(signature: string): ValidatorSignature {
 | 
					function parseValidatorSignature(signature: string): ValidatorSignature {
 | 
				
			||||||
    assert.isOneOfExpectedSignatureTypes(signature, [SignatureType.Validator]);
 | 
					    assert.isOneOfExpectedSignatureTypes(signature, [SignatureType.Validator]);
 | 
				
			||||||
    // tslint:disable:custom-no-magic-numbers
 | 
					    // tslint:disable:custom-no-magic-numbers
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,82 @@
 | 
				
			|||||||
 | 
					import { BigNumber } from '@0xproject/utils';
 | 
				
			||||||
 | 
					import { BlockParamLiteral } from 'ethereum-types';
 | 
				
			||||||
 | 
					import * as _ from 'lodash';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { AbstractBalanceAndProxyAllowanceFetcher } from '../abstract/abstract_balance_and_proxy_allowance_fetcher';
 | 
				
			||||||
 | 
					import { AbstractBalanceAndProxyAllowanceLazyStore } from '../abstract/abstract_balance_and_proxy_allowance_lazy_store';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Copy on read store for balances/proxyAllowances of tokens/accounts
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export class BalanceAndProxyAllowanceLazyStore implements AbstractBalanceAndProxyAllowanceLazyStore {
 | 
				
			||||||
 | 
					    private _balanceAndProxyAllowanceFetcher: AbstractBalanceAndProxyAllowanceFetcher;
 | 
				
			||||||
 | 
					    private _balance: {
 | 
				
			||||||
 | 
					        [assetData: string]: {
 | 
				
			||||||
 | 
					            [userAddress: string]: BigNumber;
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    private _proxyAllowance: {
 | 
				
			||||||
 | 
					        [assetData: string]: {
 | 
				
			||||||
 | 
					            [userAddress: string]: BigNumber;
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    constructor(token: AbstractBalanceAndProxyAllowanceFetcher) {
 | 
				
			||||||
 | 
					        this._balanceAndProxyAllowanceFetcher = token;
 | 
				
			||||||
 | 
					        this._balance = {};
 | 
				
			||||||
 | 
					        this._proxyAllowance = {};
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    public async getBalanceAsync(assetData: string, userAddress: string): Promise<BigNumber> {
 | 
				
			||||||
 | 
					        if (_.isUndefined(this._balance[assetData]) || _.isUndefined(this._balance[assetData][userAddress])) {
 | 
				
			||||||
 | 
					            const balance = await this._balanceAndProxyAllowanceFetcher.getBalanceAsync(assetData, userAddress);
 | 
				
			||||||
 | 
					            this.setBalance(assetData, userAddress, balance);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const cachedBalance = this._balance[assetData][userAddress];
 | 
				
			||||||
 | 
					        return cachedBalance;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    public setBalance(assetData: string, userAddress: string, balance: BigNumber): void {
 | 
				
			||||||
 | 
					        if (_.isUndefined(this._balance[assetData])) {
 | 
				
			||||||
 | 
					            this._balance[assetData] = {};
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        this._balance[assetData][userAddress] = balance;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    public deleteBalance(assetData: string, userAddress: string): void {
 | 
				
			||||||
 | 
					        if (!_.isUndefined(this._balance[assetData])) {
 | 
				
			||||||
 | 
					            delete this._balance[assetData][userAddress];
 | 
				
			||||||
 | 
					            if (_.isEmpty(this._balance[assetData])) {
 | 
				
			||||||
 | 
					                delete this._balance[assetData];
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    public async getProxyAllowanceAsync(assetData: string, userAddress: string): Promise<BigNumber> {
 | 
				
			||||||
 | 
					        if (
 | 
				
			||||||
 | 
					            _.isUndefined(this._proxyAllowance[assetData]) ||
 | 
				
			||||||
 | 
					            _.isUndefined(this._proxyAllowance[assetData][userAddress])
 | 
				
			||||||
 | 
					        ) {
 | 
				
			||||||
 | 
					            const proxyAllowance = await this._balanceAndProxyAllowanceFetcher.getProxyAllowanceAsync(
 | 
				
			||||||
 | 
					                assetData,
 | 
				
			||||||
 | 
					                userAddress,
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					            this.setProxyAllowance(assetData, userAddress, proxyAllowance);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const cachedProxyAllowance = this._proxyAllowance[assetData][userAddress];
 | 
				
			||||||
 | 
					        return cachedProxyAllowance;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    public setProxyAllowance(assetData: string, userAddress: string, proxyAllowance: BigNumber): void {
 | 
				
			||||||
 | 
					        if (_.isUndefined(this._proxyAllowance[assetData])) {
 | 
				
			||||||
 | 
					            this._proxyAllowance[assetData] = {};
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        this._proxyAllowance[assetData][userAddress] = proxyAllowance;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    public deleteProxyAllowance(assetData: string, userAddress: string): void {
 | 
				
			||||||
 | 
					        if (!_.isUndefined(this._proxyAllowance[assetData])) {
 | 
				
			||||||
 | 
					            delete this._proxyAllowance[assetData][userAddress];
 | 
				
			||||||
 | 
					            if (_.isEmpty(this._proxyAllowance[assetData])) {
 | 
				
			||||||
 | 
					                delete this._proxyAllowance[assetData];
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    public deleteAll(): void {
 | 
				
			||||||
 | 
					        this._balance = {};
 | 
				
			||||||
 | 
					        this._proxyAllowance = {};
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -23,3 +23,13 @@ export interface MessagePrefixOpts {
 | 
				
			|||||||
    prefixType: MessagePrefixType;
 | 
					    prefixType: MessagePrefixType;
 | 
				
			||||||
    shouldAddPrefixBeforeCallingEthSign: boolean;
 | 
					    shouldAddPrefixBeforeCallingEthSign: boolean;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export enum TradeSide {
 | 
				
			||||||
 | 
					    Maker = 'maker',
 | 
				
			||||||
 | 
					    Taker = 'taker',
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export enum TransferType {
 | 
				
			||||||
 | 
					    Trade = 'trade',
 | 
				
			||||||
 | 
					    Fee = 'fee',
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,3 +1,5 @@
 | 
				
			|||||||
 | 
					import { BigNumber } from '@0xproject/utils';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const utils = {
 | 
					export const utils = {
 | 
				
			||||||
    getSignatureTypeIndexIfExists(signature: string): number {
 | 
					    getSignatureTypeIndexIfExists(signature: string): number {
 | 
				
			||||||
        // tslint:disable-next-line:custom-no-magic-numbers
 | 
					        // tslint:disable-next-line:custom-no-magic-numbers
 | 
				
			||||||
@@ -6,4 +8,8 @@ export const utils = {
 | 
				
			|||||||
        const signatureTypeInt = parseInt(signatureTypeHex, base);
 | 
					        const signatureTypeInt = parseInt(signatureTypeHex, base);
 | 
				
			||||||
        return signatureTypeInt;
 | 
					        return signatureTypeInt;
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    getCurrentUnixTimestampSec(): BigNumber {
 | 
				
			||||||
 | 
					        const milisecondsInSecond = 1000;
 | 
				
			||||||
 | 
					        return new BigNumber(Date.now() / milisecondsInSecond).round();
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										164
									
								
								packages/order-utils/test/exchange_transfer_simulator_test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										164
									
								
								packages/order-utils/test/exchange_transfer_simulator_test.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,164 @@
 | 
				
			|||||||
 | 
					import { BlockchainLifecycle } from '@0xproject/dev-utils';
 | 
				
			||||||
 | 
					import { ExchangeContractErrs, Token } from '@0xproject/types';
 | 
				
			||||||
 | 
					import { BigNumber } from '@0xproject/utils';
 | 
				
			||||||
 | 
					import * as chai from 'chai';
 | 
				
			||||||
 | 
					import { BlockParamLiteral } from 'ethereum-types';
 | 
				
			||||||
 | 
					import * as _ from 'lodash';
 | 
				
			||||||
 | 
					import 'make-promises-safe';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { artifacts } from '../src/artifacts';
 | 
				
			||||||
 | 
					import { constants } from '../src/constants';
 | 
				
			||||||
 | 
					import { ExchangeTransferSimulator } from '../src/exchange_transfer_simulator';
 | 
				
			||||||
 | 
					import { DummyERC20TokenContract } from '../src/generated_contract_wrappers/dummy_e_r_c20_token';
 | 
				
			||||||
 | 
					import { BalanceAndProxyAllowanceLazyStore } from '../src/store/balance_and_proxy_allowance_lazy_store';
 | 
				
			||||||
 | 
					import { TradeSide, TransferType } from '../src/types';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { chaiSetup } from './utils/chai_setup';
 | 
				
			||||||
 | 
					import { SimpleERC20BalanceAndProxyAllowanceFetcher } from './utils/simple_erc20_balance_and_proxy_allowance_fetcher';
 | 
				
			||||||
 | 
					import { provider, web3Wrapper } from './utils/web3_wrapper';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					chaiSetup.configure();
 | 
				
			||||||
 | 
					const expect = chai.expect;
 | 
				
			||||||
 | 
					const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('ExchangeTransferSimulator', async () => {
 | 
				
			||||||
 | 
					    const transferAmount = new BigNumber(5);
 | 
				
			||||||
 | 
					    let userAddresses: string[];
 | 
				
			||||||
 | 
					    let dummyERC20Token: DummyERC20TokenContract;
 | 
				
			||||||
 | 
					    let coinbase: string;
 | 
				
			||||||
 | 
					    let sender: string;
 | 
				
			||||||
 | 
					    let recipient: string;
 | 
				
			||||||
 | 
					    let exampleTokenAddress: string;
 | 
				
			||||||
 | 
					    let exchangeTransferSimulator: ExchangeTransferSimulator;
 | 
				
			||||||
 | 
					    let txHash: string;
 | 
				
			||||||
 | 
					    let erc20ProxyAddress: string;
 | 
				
			||||||
 | 
					    before(async () => {
 | 
				
			||||||
 | 
					        userAddresses = await web3Wrapper.getAvailableAddressesAsync();
 | 
				
			||||||
 | 
					        [coinbase, sender, recipient] = userAddresses;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        erc20ProxyAddress = getAddressFromArtifact(artifacts.ERC20Proxy, constants.TESTRPC_NETWORK_ID);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const wethArtifact = artifacts.DummyERC20Token;
 | 
				
			||||||
 | 
					        const wethAddress = getAddressFromArtifact(wethArtifact, constants.TESTRPC_NETWORK_ID);
 | 
				
			||||||
 | 
					        dummyERC20Token = new DummyERC20TokenContract(
 | 
				
			||||||
 | 
					            artifacts.DummyERC20Token.compilerOutput.abi,
 | 
				
			||||||
 | 
					            wethAddress,
 | 
				
			||||||
 | 
					            provider,
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        exampleTokenAddress = dummyERC20Token.address;
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    beforeEach(async () => {
 | 
				
			||||||
 | 
					        await blockchainLifecycle.startAsync();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    afterEach(async () => {
 | 
				
			||||||
 | 
					        await blockchainLifecycle.revertAsync();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    describe('#transferFromAsync', function(): void {
 | 
				
			||||||
 | 
					        // HACK: For some reason these tests need a slightly longer timeout
 | 
				
			||||||
 | 
					        const mochaTestTimeoutMs = 3000;
 | 
				
			||||||
 | 
					        this.timeout(mochaTestTimeoutMs);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        beforeEach(() => {
 | 
				
			||||||
 | 
					            const simpleERC20BalanceAndProxyAllowanceFetcher = new SimpleERC20BalanceAndProxyAllowanceFetcher(
 | 
				
			||||||
 | 
					                dummyERC20Token,
 | 
				
			||||||
 | 
					                erc20ProxyAddress,
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					            const balanceAndProxyAllowanceLazyStore = new BalanceAndProxyAllowanceLazyStore(
 | 
				
			||||||
 | 
					                simpleERC20BalanceAndProxyAllowanceFetcher,
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					            exchangeTransferSimulator = new ExchangeTransferSimulator(balanceAndProxyAllowanceLazyStore);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        it("throws if the user doesn't have enough allowance", async () => {
 | 
				
			||||||
 | 
					            return expect(
 | 
				
			||||||
 | 
					                exchangeTransferSimulator.transferFromAsync(
 | 
				
			||||||
 | 
					                    exampleTokenAddress,
 | 
				
			||||||
 | 
					                    sender,
 | 
				
			||||||
 | 
					                    recipient,
 | 
				
			||||||
 | 
					                    transferAmount,
 | 
				
			||||||
 | 
					                    TradeSide.Taker,
 | 
				
			||||||
 | 
					                    TransferType.Trade,
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					            ).to.be.rejectedWith(ExchangeContractErrs.InsufficientTakerAllowance);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        it("throws if the user doesn't have enough balance", async () => {
 | 
				
			||||||
 | 
					            txHash = await dummyERC20Token.approve.sendTransactionAsync(erc20ProxyAddress, transferAmount, {
 | 
				
			||||||
 | 
					                from: sender,
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					            await web3Wrapper.awaitTransactionSuccessAsync(txHash);
 | 
				
			||||||
 | 
					            return expect(
 | 
				
			||||||
 | 
					                exchangeTransferSimulator.transferFromAsync(
 | 
				
			||||||
 | 
					                    exampleTokenAddress,
 | 
				
			||||||
 | 
					                    sender,
 | 
				
			||||||
 | 
					                    recipient,
 | 
				
			||||||
 | 
					                    transferAmount,
 | 
				
			||||||
 | 
					                    TradeSide.Maker,
 | 
				
			||||||
 | 
					                    TransferType.Trade,
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					            ).to.be.rejectedWith(ExchangeContractErrs.InsufficientMakerBalance);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        it('updates balances and proxyAllowance after transfer', async function(): Promise<void> {
 | 
				
			||||||
 | 
					            txHash = await dummyERC20Token.transfer.sendTransactionAsync(sender, transferAmount, {
 | 
				
			||||||
 | 
					                from: coinbase,
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					            await web3Wrapper.awaitTransactionSuccessAsync(txHash);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            txHash = await dummyERC20Token.approve.sendTransactionAsync(erc20ProxyAddress, transferAmount, {
 | 
				
			||||||
 | 
					                from: sender,
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					            await web3Wrapper.awaitTransactionSuccessAsync(txHash);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            await exchangeTransferSimulator.transferFromAsync(
 | 
				
			||||||
 | 
					                exampleTokenAddress,
 | 
				
			||||||
 | 
					                sender,
 | 
				
			||||||
 | 
					                recipient,
 | 
				
			||||||
 | 
					                transferAmount,
 | 
				
			||||||
 | 
					                TradeSide.Taker,
 | 
				
			||||||
 | 
					                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);
 | 
				
			||||||
 | 
					            expect(senderBalance).to.be.bignumber.equal(0);
 | 
				
			||||||
 | 
					            expect(recipientBalance).to.be.bignumber.equal(transferAmount);
 | 
				
			||||||
 | 
					            expect(senderProxyAllowance).to.be.bignumber.equal(0);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        it("doesn't update proxyAllowance after transfer if unlimited", async () => {
 | 
				
			||||||
 | 
					            txHash = await dummyERC20Token.transfer.sendTransactionAsync(sender, transferAmount, {
 | 
				
			||||||
 | 
					                from: coinbase,
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					            await web3Wrapper.awaitTransactionSuccessAsync(txHash);
 | 
				
			||||||
 | 
					            txHash = await dummyERC20Token.approve.sendTransactionAsync(
 | 
				
			||||||
 | 
					                erc20ProxyAddress,
 | 
				
			||||||
 | 
					                constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS,
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    from: sender,
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					            await web3Wrapper.awaitTransactionSuccessAsync(txHash);
 | 
				
			||||||
 | 
					            await exchangeTransferSimulator.transferFromAsync(
 | 
				
			||||||
 | 
					                exampleTokenAddress,
 | 
				
			||||||
 | 
					                sender,
 | 
				
			||||||
 | 
					                recipient,
 | 
				
			||||||
 | 
					                transferAmount,
 | 
				
			||||||
 | 
					                TradeSide.Taker,
 | 
				
			||||||
 | 
					                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);
 | 
				
			||||||
 | 
					            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);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function getAddressFromArtifact(artifact: any, networkId: number): string {
 | 
				
			||||||
 | 
					    if (_.isUndefined(artifact.networks[networkId])) {
 | 
				
			||||||
 | 
					        throw new Error(`Contract ${artifact.contractName} not deployed to network ${networkId}`);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const contractAddress = artifact.networks[networkId].address.toLowerCase();
 | 
				
			||||||
 | 
					    return contractAddress;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										45
									
								
								packages/order-utils/test/global_hooks_test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								packages/order-utils/test/global_hooks_test.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,45 @@
 | 
				
			|||||||
 | 
					import { devConstants } from '@0xproject/dev-utils';
 | 
				
			||||||
 | 
					import { ArtifactWriter } from '@0xproject/migrations';
 | 
				
			||||||
 | 
					import { BigNumber } from '@0xproject/utils';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { artifacts } from '../src/artifacts';
 | 
				
			||||||
 | 
					import { constants } from '../src/constants';
 | 
				
			||||||
 | 
					import { DummyERC20TokenContract } from '../src/generated_contract_wrappers/dummy_e_r_c20_token';
 | 
				
			||||||
 | 
					import { ERC20ProxyContract } from '../src/generated_contract_wrappers/e_r_c20_proxy';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { provider } from './utils/web3_wrapper';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					before('migrate contracts', async function(): Promise<void> {
 | 
				
			||||||
 | 
					    // HACK: Since contract migrations take longer then our global mocha timeout limit
 | 
				
			||||||
 | 
					    // we manually increase it for this before hook.
 | 
				
			||||||
 | 
					    const mochaTestTimeoutMs = 20000;
 | 
				
			||||||
 | 
					    this.timeout(mochaTestTimeoutMs);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const txDefaults = {
 | 
				
			||||||
 | 
					        gas: devConstants.GAS_LIMIT,
 | 
				
			||||||
 | 
					        from: devConstants.TESTRPC_FIRST_ADDRESS,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const networkId = constants.TESTRPC_NETWORK_ID;
 | 
				
			||||||
 | 
					    const artifactsDir = `lib/src/artifacts`;
 | 
				
			||||||
 | 
					    const artifactsWriter = new ArtifactWriter(artifactsDir, networkId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const erc20proxy = await ERC20ProxyContract.deployFrom0xArtifactAsync(artifacts.ERC20Proxy, provider, txDefaults);
 | 
				
			||||||
 | 
					    artifactsWriter.saveArtifact(erc20proxy);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const totalSupply = new BigNumber(100000000000000000000);
 | 
				
			||||||
 | 
					    const name = 'Test';
 | 
				
			||||||
 | 
					    const symbol = 'TST';
 | 
				
			||||||
 | 
					    const decimals = new BigNumber(18);
 | 
				
			||||||
 | 
					    // tslint:disable-next-line:no-unused-variable
 | 
				
			||||||
 | 
					    const dummyErc20Token = await DummyERC20TokenContract.deployFrom0xArtifactAsync(
 | 
				
			||||||
 | 
					        artifacts.DummyERC20Token,
 | 
				
			||||||
 | 
					        provider,
 | 
				
			||||||
 | 
					        txDefaults,
 | 
				
			||||||
 | 
					        name,
 | 
				
			||||||
 | 
					        symbol,
 | 
				
			||||||
 | 
					        decimals,
 | 
				
			||||||
 | 
					        totalSupply,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    artifactsWriter.saveArtifact(dummyErc20Token);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										70
									
								
								packages/order-utils/test/order_validation_utils_test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								packages/order-utils/test/order_validation_utils_test.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,70 @@
 | 
				
			|||||||
 | 
					import { BigNumber } from '@0xproject/utils';
 | 
				
			||||||
 | 
					import * as chai from 'chai';
 | 
				
			||||||
 | 
					import 'mocha';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { OrderValidationUtils } from '../src/order_validation_utils';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { chaiSetup } from './utils/chai_setup';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					chaiSetup.configure();
 | 
				
			||||||
 | 
					const expect = chai.expect;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('OrderValidationUtils', () => {
 | 
				
			||||||
 | 
					    describe('#isRoundingError', () => {
 | 
				
			||||||
 | 
					        it('should return false if there is a rounding error of 0.1%', async () => {
 | 
				
			||||||
 | 
					            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%
 | 
				
			||||||
 | 
					            const isRoundingError = OrderValidationUtils.isRoundingError(numerator, denominator, target);
 | 
				
			||||||
 | 
					            expect(isRoundingError).to.be.false();
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        it('should return false if there is a rounding of 0.09%', async () => {
 | 
				
			||||||
 | 
					            const numerator = new BigNumber(20);
 | 
				
			||||||
 | 
					            const denominator = new BigNumber(9991);
 | 
				
			||||||
 | 
					            const target = new BigNumber(500);
 | 
				
			||||||
 | 
					            // rounding error = ((20*500/9991) - floor(20*500/9991)) / (20*500/9991) = 0.09%
 | 
				
			||||||
 | 
					            const isRoundingError = OrderValidationUtils.isRoundingError(numerator, denominator, target);
 | 
				
			||||||
 | 
					            expect(isRoundingError).to.be.false();
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        it('should return true if there is a rounding error of 0.11%', async () => {
 | 
				
			||||||
 | 
					            const numerator = new BigNumber(20);
 | 
				
			||||||
 | 
					            const denominator = new BigNumber(9989);
 | 
				
			||||||
 | 
					            const target = new BigNumber(500);
 | 
				
			||||||
 | 
					            // rounding error = ((20*500/9989) - floor(20*500/9989)) / (20*500/9989) = 0.011%
 | 
				
			||||||
 | 
					            const isRoundingError = OrderValidationUtils.isRoundingError(numerator, denominator, target);
 | 
				
			||||||
 | 
					            expect(isRoundingError).to.be.true();
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        it('should return true if there is a rounding error > 0.1%', async () => {
 | 
				
			||||||
 | 
					            const numerator = new BigNumber(3);
 | 
				
			||||||
 | 
					            const denominator = new BigNumber(7);
 | 
				
			||||||
 | 
					            const target = new BigNumber(10);
 | 
				
			||||||
 | 
					            // rounding error = ((3*10/7) - floor(3*10/7)) / (3*10/7) = 6.67%
 | 
				
			||||||
 | 
					            const isRoundingError = OrderValidationUtils.isRoundingError(numerator, denominator, target);
 | 
				
			||||||
 | 
					            expect(isRoundingError).to.be.true();
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        it('should return false when there is no rounding error', async () => {
 | 
				
			||||||
 | 
					            const numerator = new BigNumber(1);
 | 
				
			||||||
 | 
					            const denominator = new BigNumber(2);
 | 
				
			||||||
 | 
					            const target = new BigNumber(10);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const isRoundingError = OrderValidationUtils.isRoundingError(numerator, denominator, target);
 | 
				
			||||||
 | 
					            expect(isRoundingError).to.be.false();
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        it('should return false when there is rounding error <= 0.1%', async () => {
 | 
				
			||||||
 | 
					            // randomly generated numbers
 | 
				
			||||||
 | 
					            const numerator = new BigNumber(76564);
 | 
				
			||||||
 | 
					            const denominator = new BigNumber(676373677);
 | 
				
			||||||
 | 
					            const target = new BigNumber(105762562);
 | 
				
			||||||
 | 
					            // rounding error = ((76564*105762562/676373677) - floor(76564*105762562/676373677)) /
 | 
				
			||||||
 | 
					            // (76564*105762562/676373677) = 0.0007%
 | 
				
			||||||
 | 
					            const isRoundingError = OrderValidationUtils.isRoundingError(numerator, denominator, target);
 | 
				
			||||||
 | 
					            expect(isRoundingError).to.be.false();
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
@@ -0,0 +1,26 @@
 | 
				
			|||||||
 | 
					import { BigNumber } from '@0xproject/utils';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { AbstractBalanceAndProxyAllowanceFetcher } from '../../src/abstract/abstract_balance_and_proxy_allowance_fetcher';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { ERC20TokenContract } from '../../src/generated_contract_wrappers/e_r_c20_token';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class SimpleERC20BalanceAndProxyAllowanceFetcher implements AbstractBalanceAndProxyAllowanceFetcher {
 | 
				
			||||||
 | 
					    private _erc20TokenContract: ERC20TokenContract;
 | 
				
			||||||
 | 
					    private _erc20ProxyAddress: string;
 | 
				
			||||||
 | 
					    constructor(erc20TokenWrapper: ERC20TokenContract, erc20ProxyAddress: string) {
 | 
				
			||||||
 | 
					        this._erc20TokenContract = erc20TokenWrapper;
 | 
				
			||||||
 | 
					        this._erc20ProxyAddress = erc20ProxyAddress;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    public async getBalanceAsync(assetData: string, userAddress: string): Promise<BigNumber> {
 | 
				
			||||||
 | 
					        // HACK: We cheat and don't pass in the userData since it's always the same token used
 | 
				
			||||||
 | 
					        // in our tests.
 | 
				
			||||||
 | 
					        const balance = await this._erc20TokenContract.balanceOf.callAsync(userAddress);
 | 
				
			||||||
 | 
					        return balance;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    public async getProxyAllowanceAsync(assetData: string, userAddress: string): Promise<BigNumber> {
 | 
				
			||||||
 | 
					        // HACK: We cheat and don't pass in the userData since it's always the same token used
 | 
				
			||||||
 | 
					        // in our tests.
 | 
				
			||||||
 | 
					        const proxyAllowance = await this._erc20TokenContract.allowance.callAsync(userAddress, this._erc20ProxyAddress);
 | 
				
			||||||
 | 
					        return proxyAllowance;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user