Move ExchangeTransferSimulator into contract-exchange tests since that's the only place it's still used and we no longer want to expose it to external developers
This commit is contained in:
@@ -76,7 +76,7 @@
|
||||
"dependencies": {
|
||||
"@0x/base-contract": "^5.5.0-beta.1",
|
||||
"@0x/contracts-asset-proxy": "^2.3.0-beta.1",
|
||||
"@0x/contracts-dev-utils": "^0.1.0-beta.1",
|
||||
"@0x/contracts-dev-utils": "^2.4.0-beta.1",
|
||||
"@0x/contracts-erc1155": "^1.2.0-beta.1",
|
||||
"@0x/contracts-erc20": "^2.3.0-beta.1",
|
||||
"@0x/contracts-erc721": "^2.2.0-beta.1",
|
||||
|
177
contracts/exchange/test/exchange_transfer_simulator_test.ts
Normal file
177
contracts/exchange/test/exchange_transfer_simulator_test.ts
Normal file
@@ -0,0 +1,177 @@
|
||||
import { artifacts as assetProxyArtifacts, ERC20ProxyContract } from '@0x/contracts-asset-proxy';
|
||||
import { DevUtilsContract } from '@0x/contracts-dev-utils';
|
||||
import { artifacts as erc20Artifacts, DummyERC20TokenContract, ERC20TokenContract } from '@0x/contracts-erc20';
|
||||
import { blockchainTests, chaiSetup, constants } from '@0x/contracts-test-utils';
|
||||
import { TradeSide, TransferType } from '@0x/order-utils';
|
||||
import { ExchangeContractErrs } from '@0x/types';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
|
||||
import { ExchangeTransferSimulator } from './utils/exchange_transfer_simulator';
|
||||
import { SimpleERC20BalanceAndProxyAllowanceFetcher } from './utils/simple_erc20_balance_and_proxy_allowance_fetcher';
|
||||
import { BalanceAndProxyAllowanceLazyStore } from './utils/store/balance_and_proxy_allowance_lazy_store';
|
||||
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
|
||||
const GAS_LIMIT = 9e6;
|
||||
|
||||
blockchainTests('ExchangeTransferSimulator', env => {
|
||||
const transferAmount = new BigNumber(5);
|
||||
let userAddresses: string[];
|
||||
let dummyERC20Token: DummyERC20TokenContract;
|
||||
let coinbase: string;
|
||||
let sender: string;
|
||||
let recipient: string;
|
||||
let exampleAssetData: string;
|
||||
let exchangeTransferSimulator: ExchangeTransferSimulator;
|
||||
let txHash: string;
|
||||
let erc20ProxyAddress: string;
|
||||
const devUtils = new DevUtilsContract(constants.NULL_ADDRESS, env.provider);
|
||||
before(async function(): Promise<void> {
|
||||
const mochaTestTimeoutMs = 20000;
|
||||
this.timeout(mochaTestTimeoutMs); // tslint:disable-line:no-invalid-this
|
||||
|
||||
userAddresses = await env.web3Wrapper.getAvailableAddressesAsync();
|
||||
[coinbase, sender, recipient] = userAddresses;
|
||||
|
||||
const txDefaults = {
|
||||
gas: GAS_LIMIT,
|
||||
from: userAddresses[0],
|
||||
};
|
||||
|
||||
await env.blockchainLifecycle.startAsync();
|
||||
const erc20Proxy = await ERC20ProxyContract.deployFrom0xArtifactAsync(
|
||||
assetProxyArtifacts.ERC20Proxy,
|
||||
env.provider,
|
||||
txDefaults,
|
||||
assetProxyArtifacts,
|
||||
);
|
||||
erc20ProxyAddress = erc20Proxy.address;
|
||||
|
||||
const totalSupply = new BigNumber(100000000000000000000);
|
||||
const name = 'Test';
|
||||
const symbol = 'TST';
|
||||
const decimals = new BigNumber(18);
|
||||
// tslint:disable-next-line:no-unused-variable
|
||||
dummyERC20Token = await DummyERC20TokenContract.deployFrom0xArtifactAsync(
|
||||
erc20Artifacts.DummyERC20Token,
|
||||
env.provider,
|
||||
txDefaults,
|
||||
erc20Artifacts,
|
||||
name,
|
||||
symbol,
|
||||
decimals,
|
||||
totalSupply,
|
||||
);
|
||||
|
||||
exampleAssetData = await devUtils.encodeERC20AssetData.callAsync(dummyERC20Token.address);
|
||||
});
|
||||
beforeEach(async () => {
|
||||
await env.blockchainLifecycle.startAsync();
|
||||
});
|
||||
afterEach(async () => {
|
||||
await env.blockchainLifecycle.revertAsync();
|
||||
});
|
||||
after(async () => {
|
||||
await env.blockchainLifecycle.revertAsync();
|
||||
});
|
||||
describe('#transferFromAsync', function(): void {
|
||||
// HACK: For some reason these tests need a slightly longer timeout
|
||||
const mochaTestTimeoutMs = 3000;
|
||||
this.timeout(mochaTestTimeoutMs); // tslint:disable-line:no-invalid-this
|
||||
beforeEach(() => {
|
||||
const simpleERC20BalanceAndProxyAllowanceFetcher = new SimpleERC20BalanceAndProxyAllowanceFetcher(
|
||||
(dummyERC20Token as any) as ERC20TokenContract,
|
||||
erc20ProxyAddress,
|
||||
);
|
||||
const balanceAndProxyAllowanceLazyStore = new BalanceAndProxyAllowanceLazyStore(
|
||||
simpleERC20BalanceAndProxyAllowanceFetcher,
|
||||
);
|
||||
exchangeTransferSimulator = new ExchangeTransferSimulator(balanceAndProxyAllowanceLazyStore, env.provider);
|
||||
});
|
||||
it("throws if the user doesn't have enough allowance", async () => {
|
||||
return expect(
|
||||
exchangeTransferSimulator.transferFromAsync(
|
||||
exampleAssetData,
|
||||
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 env.web3Wrapper.awaitTransactionSuccessAsync(txHash);
|
||||
return expect(
|
||||
exchangeTransferSimulator.transferFromAsync(
|
||||
exampleAssetData,
|
||||
sender,
|
||||
recipient,
|
||||
transferAmount,
|
||||
TradeSide.Maker,
|
||||
TransferType.Trade,
|
||||
),
|
||||
).to.be.rejectedWith(ExchangeContractErrs.InsufficientMakerBalance);
|
||||
});
|
||||
it('updates balances and proxyAllowance after transfer', async () => {
|
||||
txHash = await dummyERC20Token.transfer.sendTransactionAsync(sender, transferAmount, {
|
||||
from: coinbase,
|
||||
});
|
||||
await env.web3Wrapper.awaitTransactionSuccessAsync(txHash);
|
||||
|
||||
txHash = await dummyERC20Token.approve.sendTransactionAsync(erc20ProxyAddress, transferAmount, {
|
||||
from: sender,
|
||||
});
|
||||
await env.web3Wrapper.awaitTransactionSuccessAsync(txHash);
|
||||
|
||||
await exchangeTransferSimulator.transferFromAsync(
|
||||
exampleAssetData,
|
||||
sender,
|
||||
recipient,
|
||||
transferAmount,
|
||||
TradeSide.Taker,
|
||||
TransferType.Trade,
|
||||
);
|
||||
const store = (exchangeTransferSimulator as any)._store;
|
||||
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);
|
||||
});
|
||||
it("doesn't update proxyAllowance after transfer if unlimited", async () => {
|
||||
txHash = await dummyERC20Token.transfer.sendTransactionAsync(sender, transferAmount, {
|
||||
from: coinbase,
|
||||
});
|
||||
await env.web3Wrapper.awaitTransactionSuccessAsync(txHash);
|
||||
txHash = await dummyERC20Token.approve.sendTransactionAsync(
|
||||
erc20ProxyAddress,
|
||||
constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS,
|
||||
{
|
||||
from: sender,
|
||||
},
|
||||
);
|
||||
await env.web3Wrapper.awaitTransactionSuccessAsync(txHash);
|
||||
await exchangeTransferSimulator.transferFromAsync(
|
||||
exampleAssetData,
|
||||
sender,
|
||||
recipient,
|
||||
transferAmount,
|
||||
TradeSide.Taker,
|
||||
TransferType.Trade,
|
||||
);
|
||||
const store = (exchangeTransferSimulator as any)._store;
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
@@ -0,0 +1,23 @@
|
||||
import { BigNumber } from '@0x/utils';
|
||||
|
||||
/**
|
||||
* An abstract class to be implemented in order to use OrderStateUtils. The class that
|
||||
* implements this interface must be capable of fetching the balance and proxyAllowance
|
||||
* for an Ethereum address and assetData
|
||||
*/
|
||||
export abstract class AbstractBalanceAndProxyAllowanceFetcher {
|
||||
/**
|
||||
* Get balance of assetData for userAddress
|
||||
* @param assetData AssetData for which to fetch the balance
|
||||
* @param userAddress Ethereum address for which to fetch the balance
|
||||
* @return Balance amount in base units
|
||||
*/
|
||||
public abstract async getBalanceAsync(assetData: string, userAddress: string): Promise<BigNumber>;
|
||||
/**
|
||||
* Get the 0x asset proxy allowance of assetData for userAddress
|
||||
* @param assetData AssetData for which to fetch the allowance
|
||||
* @param userAddress Ethereum address for which to fetch the allowance
|
||||
* @return Allowance amount in base units
|
||||
*/
|
||||
public abstract async getProxyAllowanceAsync(assetData: string, userAddress: string): Promise<BigNumber>;
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
import { BigNumber } from '@0x/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;
|
||||
}
|
@@ -0,0 +1,15 @@
|
||||
import { BigNumber } from '@0x/utils';
|
||||
|
||||
/**
|
||||
* An abstract class to be implemented in order to use OrderStateUtils. The class that
|
||||
* implements this interface must be capable of fetching the amount filled of an order
|
||||
* and whether it's been cancelled.
|
||||
*/
|
||||
export abstract class AbstractOrderFilledCancelledFetcher {
|
||||
/**
|
||||
* Get the amount of the order's takerToken amount already filled
|
||||
* @param orderHash OrderHash of order we are interested in
|
||||
* @return FilledTakerAmount
|
||||
*/
|
||||
public abstract async getFilledTakerAmountAsync(orderHash: string): Promise<BigNumber>;
|
||||
}
|
@@ -0,0 +1,10 @@
|
||||
import { BigNumber } from '@0x/utils';
|
||||
|
||||
export abstract class AbstractOrderFilledCancelledLazyStore {
|
||||
public abstract async getFilledTakerAmountAsync(orderHash: string): Promise<BigNumber>;
|
||||
public abstract setFilledTakerAmount(orderHash: string, balance: BigNumber): void;
|
||||
public abstract deleteFilledTakerAmount(orderHash: string): void;
|
||||
public abstract setIsCancelled(orderHash: string, isCancelled: boolean): void;
|
||||
public abstract deleteIsCancelled(orderHash: string): void;
|
||||
public abstract deleteAll(): void;
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
import { DevUtilsContract } from '@0x/contracts-dev-utils';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { AbstractBalanceAndProxyAllowanceFetcher } from './abstract/abstract_balance_and_proxy_allowance_fetcher';
|
||||
|
||||
export class AssetBalanceAndProxyAllowanceFetcher implements AbstractBalanceAndProxyAllowanceFetcher {
|
||||
private readonly _devUtilsContract: DevUtilsContract;
|
||||
constructor(devUtilsContract: DevUtilsContract) {
|
||||
this._devUtilsContract = devUtilsContract;
|
||||
}
|
||||
public async getBalanceAsync(assetData: string, userAddress: string): Promise<BigNumber> {
|
||||
const balance = await this._devUtilsContract.getBalance.callAsync(userAddress, assetData);
|
||||
return balance;
|
||||
}
|
||||
public async getProxyAllowanceAsync(assetData: string, userAddress: string): Promise<BigNumber> {
|
||||
const proxyAllowance = await this._devUtilsContract.getAssetProxyAllowance.callAsync(userAddress, assetData);
|
||||
return proxyAllowance;
|
||||
}
|
||||
}
|
163
contracts/exchange/test/utils/exchange_transfer_simulator.ts
Normal file
163
contracts/exchange/test/utils/exchange_transfer_simulator.ts
Normal file
@@ -0,0 +1,163 @@
|
||||
import { DevUtilsContract } from '@0x/contracts-dev-utils';
|
||||
import { constants } from '@0x/contracts-test-utils';
|
||||
import { SupportedProvider, TradeSide, TransferType } from '@0x/order-utils';
|
||||
import { AssetProxyId, ExchangeContractErrs } from '@0x/types';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
|
||||
import { AbstractBalanceAndProxyAllowanceLazyStore } from './abstract/abstract_balance_and_proxy_allowance_lazy_store';
|
||||
|
||||
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,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* An exchange transfer simulator which simulates asset transfers exactly how the
|
||||
* 0x exchange contract would do them.
|
||||
*/
|
||||
export class ExchangeTransferSimulator {
|
||||
private readonly _store: AbstractBalanceAndProxyAllowanceLazyStore;
|
||||
private readonly _devUtils: DevUtilsContract;
|
||||
private static _throwValidationError(
|
||||
failureReason: FailureReason,
|
||||
tradeSide: TradeSide,
|
||||
transferType: TransferType,
|
||||
): never {
|
||||
const errMsg = ERR_MSG_MAPPING[failureReason][tradeSide][transferType];
|
||||
throw new Error(errMsg);
|
||||
}
|
||||
/**
|
||||
* Instantiate a ExchangeTransferSimulator
|
||||
* @param store A class that implements AbstractBalanceAndProxyAllowanceLazyStore
|
||||
* @return an instance of ExchangeTransferSimulator
|
||||
*/
|
||||
constructor(store: AbstractBalanceAndProxyAllowanceLazyStore, provider: SupportedProvider) {
|
||||
this._store = store;
|
||||
this._devUtils = new DevUtilsContract(constants.NULL_ADDRESS, provider);
|
||||
}
|
||||
/**
|
||||
* 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> {
|
||||
const assetProxyId = await this._devUtils.decodeAssetProxyId.callAsync(assetData);
|
||||
switch (assetProxyId) {
|
||||
case AssetProxyId.ERC1155:
|
||||
case AssetProxyId.ERC20:
|
||||
case AssetProxyId.ERC721: {
|
||||
// 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.isLessThan(amountInBaseUnits)) {
|
||||
ExchangeTransferSimulator._throwValidationError(
|
||||
FailureReason.ProxyAllowance,
|
||||
tradeSide,
|
||||
transferType,
|
||||
);
|
||||
}
|
||||
if (balance.isLessThan(amountInBaseUnits)) {
|
||||
ExchangeTransferSimulator._throwValidationError(FailureReason.Balance, tradeSide, transferType);
|
||||
}
|
||||
if (assetProxyId !== AssetProxyId.ERC1155) {
|
||||
// No need to decrease allowance for ERC115 because it's all or nothing.
|
||||
await this._decreaseProxyAllowanceAsync(assetData, from, amountInBaseUnits);
|
||||
}
|
||||
await this._decreaseBalanceAsync(assetData, from, amountInBaseUnits);
|
||||
await this._increaseBalanceAsync(assetData, to, amountInBaseUnits);
|
||||
break;
|
||||
}
|
||||
case AssetProxyId.MultiAsset: {
|
||||
const decodedAssetData = await this._devUtils.decodeMultiAssetData.callAsync(assetData);
|
||||
await this._decreaseBalanceAsync(assetData, from, amountInBaseUnits);
|
||||
await this._increaseBalanceAsync(assetData, to, amountInBaseUnits);
|
||||
for (const [index, nestedAssetDataElement] of decodedAssetData[2].entries()) {
|
||||
const amountsElement = decodedAssetData[1][index];
|
||||
const totalAmount = amountInBaseUnits.times(amountsElement);
|
||||
await this.transferFromAsync(
|
||||
nestedAssetDataElement,
|
||||
from,
|
||||
to,
|
||||
totalAmount,
|
||||
tradeSide,
|
||||
transferType,
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new Error(`Unhandled asset proxy ID: ${assetProxyId}`);
|
||||
}
|
||||
}
|
||||
private async _decreaseProxyAllowanceAsync(
|
||||
assetData: string,
|
||||
userAddress: string,
|
||||
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));
|
||||
}
|
||||
}
|
||||
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));
|
||||
}
|
||||
}
|
@@ -7,7 +7,7 @@ import {
|
||||
} from '@0x/contracts-asset-proxy';
|
||||
import { DevUtilsContract } from '@0x/contracts-dev-utils';
|
||||
import { constants, expect, LogDecoder, orderUtils, signingUtils } from '@0x/contracts-test-utils';
|
||||
import { BalanceAndProxyAllowanceLazyStore, ExchangeRevertErrors, orderHashUtils } from '@0x/order-utils';
|
||||
import { ExchangeRevertErrors, orderHashUtils } from '@0x/order-utils';
|
||||
import { FillResults, Order, SignatureType, SignedOrder } from '@0x/types';
|
||||
import { BigNumber, errorUtils, providerUtils, RevertError, StringRevertError } from '@0x/utils';
|
||||
import { SupportedProvider, Web3Wrapper } from '@0x/web3-wrapper';
|
||||
@@ -35,6 +35,7 @@ import {
|
||||
import { FillOrderError, FillOrderSimulator } from './fill_order_simulator';
|
||||
import { OrderFactoryFromScenario } from './order_factory_from_scenario';
|
||||
import { SimpleAssetBalanceAndProxyAllowanceFetcher } from './simple_asset_balance_and_proxy_allowance_fetcher';
|
||||
import { BalanceAndProxyAllowanceLazyStore } from './store/balance_and_proxy_allowance_lazy_store';
|
||||
|
||||
const EMPTY_FILL_RESULTS = {
|
||||
takerAssetFilledAmount: constants.ZERO_AMOUNT,
|
||||
@@ -503,7 +504,7 @@ export class FillOrderCombinatorialUtils {
|
||||
takerAssetFillAmount: BigNumber,
|
||||
lazyStore: BalanceAndProxyAllowanceLazyStore,
|
||||
): Promise<FillResults> {
|
||||
const simulator = new FillOrderSimulator(lazyStore);
|
||||
const simulator = new FillOrderSimulator(lazyStore, this.provider);
|
||||
return simulator.simulateFillOrderAsync(signedOrder, this.takerAddress, takerAssetFillAmount);
|
||||
}
|
||||
|
||||
|
@@ -1,8 +1,7 @@
|
||||
import { constants, orderUtils } from '@0x/contracts-test-utils';
|
||||
import {
|
||||
AbstractBalanceAndProxyAllowanceLazyStore as LazyStore,
|
||||
ExchangeTransferSimulator,
|
||||
Order,
|
||||
SupportedProvider,
|
||||
TradeSide,
|
||||
TransferType,
|
||||
} from '@0x/order-utils';
|
||||
@@ -10,6 +9,11 @@ import { FillResults } from '@0x/types';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import {
|
||||
AbstractBalanceAndProxyAllowanceLazyStore as LazyStore,
|
||||
} from './abstract/abstract_balance_and_proxy_allowance_lazy_store';
|
||||
import { ExchangeTransferSimulator } from './exchange_transfer_simulator';
|
||||
|
||||
export enum FillOrderError {
|
||||
OrderUnfillable = 'ORDER_UNFILLABLE',
|
||||
InvalidSender = 'INVALID_SENDER',
|
||||
@@ -27,9 +31,9 @@ export class FillOrderSimulator {
|
||||
public readonly lazyStore: LazyStore;
|
||||
private readonly _transferSimulator: ExchangeTransferSimulator;
|
||||
|
||||
constructor(lazyStore: LazyStore) {
|
||||
constructor(lazyStore: LazyStore, provider: SupportedProvider) {
|
||||
this.lazyStore = lazyStore;
|
||||
this._transferSimulator = new ExchangeTransferSimulator(lazyStore);
|
||||
this._transferSimulator = new ExchangeTransferSimulator(lazyStore, provider);
|
||||
}
|
||||
|
||||
public async simulateFillOrderAsync(
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { AbstractBalanceAndProxyAllowanceFetcher } from '@0x/order-utils';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
|
||||
import { AbstractBalanceAndProxyAllowanceFetcher } from './abstract/abstract_balance_and_proxy_allowance_fetcher';
|
||||
import { AssetWrapper } from './asset_wrapper';
|
||||
|
||||
export class SimpleAssetBalanceAndProxyAllowanceFetcher implements AbstractBalanceAndProxyAllowanceFetcher {
|
||||
|
@@ -0,0 +1,25 @@
|
||||
import { ERC20TokenContract } from '@0x/contracts-erc20';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
|
||||
import { AbstractBalanceAndProxyAllowanceFetcher } from './abstract/abstract_balance_and_proxy_allowance_fetcher';
|
||||
|
||||
export class SimpleERC20BalanceAndProxyAllowanceFetcher implements AbstractBalanceAndProxyAllowanceFetcher {
|
||||
private readonly _erc20TokenContract: ERC20TokenContract;
|
||||
private readonly _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 assetData 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 assetData since it's always the same token used
|
||||
// in our tests.
|
||||
const proxyAllowance = await this._erc20TokenContract.allowance.callAsync(userAddress, this._erc20ProxyAddress);
|
||||
return proxyAllowance;
|
||||
}
|
||||
}
|
@@ -0,0 +1,119 @@
|
||||
import { BigNumber } from '@0x/utils';
|
||||
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 readonly _balanceAndProxyAllowanceFetcher: AbstractBalanceAndProxyAllowanceFetcher;
|
||||
private _balance: {
|
||||
[assetData: string]: {
|
||||
[userAddress: string]: BigNumber;
|
||||
};
|
||||
};
|
||||
private _proxyAllowance: {
|
||||
[assetData: string]: {
|
||||
[userAddress: string]: BigNumber;
|
||||
};
|
||||
};
|
||||
/**
|
||||
* Instantiates a BalanceAndProxyAllowanceLazyStore
|
||||
* @param balanceAndProxyAllowanceFetcher Class the implements the AbstractBalanceAndProxyAllowanceFetcher
|
||||
* @return Instance of BalanceAndProxyAllowanceLazyStore
|
||||
*/
|
||||
constructor(balanceAndProxyAllowanceFetcher: AbstractBalanceAndProxyAllowanceFetcher) {
|
||||
this._balanceAndProxyAllowanceFetcher = balanceAndProxyAllowanceFetcher;
|
||||
this._balance = {};
|
||||
this._proxyAllowance = {};
|
||||
}
|
||||
/**
|
||||
* Get a users balance of an asset
|
||||
* @param assetData AssetData of interest
|
||||
* @param userAddress Ethereum address of interest
|
||||
*/
|
||||
public async getBalanceAsync(assetData: string, userAddress: string): Promise<BigNumber> {
|
||||
if (this._balance[assetData] === undefined || this._balance[assetData][userAddress] === undefined) {
|
||||
const balance = await this._balanceAndProxyAllowanceFetcher.getBalanceAsync(assetData, userAddress);
|
||||
this.setBalance(assetData, userAddress, balance);
|
||||
}
|
||||
const cachedBalance = this._balance[assetData][userAddress];
|
||||
return cachedBalance;
|
||||
}
|
||||
/**
|
||||
* Set the balance of an asset for a user
|
||||
* @param assetData AssetData of interest
|
||||
* @param userAddress Ethereum address of interest
|
||||
*/
|
||||
public setBalance(assetData: string, userAddress: string, balance: BigNumber): void {
|
||||
if (this._balance[assetData] === undefined) {
|
||||
this._balance[assetData] = {};
|
||||
}
|
||||
this._balance[assetData][userAddress] = balance;
|
||||
}
|
||||
/**
|
||||
* Clear the balance of an asset for a user
|
||||
* @param assetData AssetData of interest
|
||||
* @param userAddress Ethereum address of interest
|
||||
*/
|
||||
public deleteBalance(assetData: string, userAddress: string): void {
|
||||
if (this._balance[assetData] !== undefined) {
|
||||
delete this._balance[assetData][userAddress];
|
||||
if (_.isEmpty(this._balance[assetData])) {
|
||||
delete this._balance[assetData];
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get the 0x asset proxy allowance
|
||||
* @param assetData AssetData of interest
|
||||
* @param userAddress Ethereum address of interest
|
||||
*/
|
||||
public async getProxyAllowanceAsync(assetData: string, userAddress: string): Promise<BigNumber> {
|
||||
if (
|
||||
this._proxyAllowance[assetData] === undefined ||
|
||||
this._proxyAllowance[assetData][userAddress] === undefined
|
||||
) {
|
||||
const proxyAllowance = await this._balanceAndProxyAllowanceFetcher.getProxyAllowanceAsync(
|
||||
assetData,
|
||||
userAddress,
|
||||
);
|
||||
this.setProxyAllowance(assetData, userAddress, proxyAllowance);
|
||||
}
|
||||
const cachedProxyAllowance = this._proxyAllowance[assetData][userAddress];
|
||||
return cachedProxyAllowance;
|
||||
}
|
||||
/**
|
||||
* Set the 0x asset proxy allowance
|
||||
* @param assetData AssetData of interest
|
||||
* @param userAddress Ethereum address of interest
|
||||
*/
|
||||
public setProxyAllowance(assetData: string, userAddress: string, proxyAllowance: BigNumber): void {
|
||||
if (this._proxyAllowance[assetData] === undefined) {
|
||||
this._proxyAllowance[assetData] = {};
|
||||
}
|
||||
this._proxyAllowance[assetData][userAddress] = proxyAllowance;
|
||||
}
|
||||
/**
|
||||
* Clear the 0x asset proxy allowance
|
||||
* @param assetData AssetData of interest
|
||||
* @param userAddress Ethereum address of interest
|
||||
*/
|
||||
public deleteProxyAllowance(assetData: string, userAddress: string): void {
|
||||
if (this._proxyAllowance[assetData] !== undefined) {
|
||||
delete this._proxyAllowance[assetData][userAddress];
|
||||
if (_.isEmpty(this._proxyAllowance[assetData])) {
|
||||
delete this._proxyAllowance[assetData];
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Delete all balances & allowances
|
||||
*/
|
||||
public deleteAll(): void {
|
||||
this._balance = {};
|
||||
this._proxyAllowance = {};
|
||||
}
|
||||
}
|
@@ -0,0 +1,90 @@
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { AbstractOrderFilledCancelledFetcher } from '../abstract/abstract_order_filled_cancelled_fetcher';
|
||||
import { AbstractOrderFilledCancelledLazyStore } from '../abstract/abstract_order_filled_cancelled_lazy_store';
|
||||
|
||||
/**
|
||||
* Copy on read store for balances/proxyAllowances of tokens/accounts
|
||||
*/
|
||||
export class OrderFilledCancelledLazyStore implements AbstractOrderFilledCancelledLazyStore {
|
||||
private readonly _orderFilledCancelledFetcher: AbstractOrderFilledCancelledFetcher;
|
||||
private _filledTakerAmount: {
|
||||
[orderHash: string]: BigNumber;
|
||||
};
|
||||
private _isCancelled: {
|
||||
[orderHash: string]: boolean;
|
||||
};
|
||||
/**
|
||||
* Instantiate a OrderFilledCancelledLazyStore
|
||||
* @param orderFilledCancelledFetcher Class instance that implements the AbstractOrderFilledCancelledFetcher
|
||||
* @returns An instance of OrderFilledCancelledLazyStore
|
||||
*/
|
||||
constructor(orderFilledCancelledFetcher: AbstractOrderFilledCancelledFetcher) {
|
||||
this._orderFilledCancelledFetcher = orderFilledCancelledFetcher;
|
||||
this._filledTakerAmount = {};
|
||||
this._isCancelled = {};
|
||||
}
|
||||
/**
|
||||
* Get the filledTakerAssetAmount of an order
|
||||
* @param orderHash OrderHash from order of interest
|
||||
* @return filledTakerAssetAmount
|
||||
*/
|
||||
public async getFilledTakerAmountAsync(orderHash: string): Promise<BigNumber> {
|
||||
if (this._filledTakerAmount[orderHash] === undefined) {
|
||||
const filledTakerAmount = await this._orderFilledCancelledFetcher.getFilledTakerAmountAsync(orderHash);
|
||||
this.setFilledTakerAmount(orderHash, filledTakerAmount);
|
||||
}
|
||||
const cachedFilledTakerAmount = this._filledTakerAmount[orderHash];
|
||||
return cachedFilledTakerAmount;
|
||||
}
|
||||
/**
|
||||
* Set the filledTakerAssetAmount of an order
|
||||
* @param orderHash OrderHash from order of interest
|
||||
* @param filledTakerAmount Desired filledTakerAssetAmount
|
||||
*/
|
||||
public setFilledTakerAmount(orderHash: string, filledTakerAmount: BigNumber): void {
|
||||
this._filledTakerAmount[orderHash] = filledTakerAmount;
|
||||
}
|
||||
/**
|
||||
* Clear the filledTakerAssetAmount of an order
|
||||
* @param orderHash OrderHash from order of interest
|
||||
*/
|
||||
public deleteFilledTakerAmount(orderHash: string): void {
|
||||
delete this._filledTakerAmount[orderHash];
|
||||
}
|
||||
/**
|
||||
* Set whether an order has been cancelled or not
|
||||
* @param orderHash OrderHash from order of interest
|
||||
* @param isCancelled Whether this order should be cancelled or not
|
||||
*/
|
||||
public setIsCancelled(orderHash: string, isCancelled: boolean): void {
|
||||
this._isCancelled[orderHash] = isCancelled;
|
||||
}
|
||||
/**
|
||||
* Clear whether the order has been cancelled if already set
|
||||
* @param orderHash OrderHash from order of interest
|
||||
*/
|
||||
public deleteIsCancelled(orderHash: string): void {
|
||||
delete this._isCancelled[orderHash];
|
||||
}
|
||||
/**
|
||||
* Clear all filled/cancelled state
|
||||
*/
|
||||
public deleteAll(): void {
|
||||
this.deleteAllFilled();
|
||||
this.deleteAllIsCancelled();
|
||||
}
|
||||
/**
|
||||
* Clear all cancelled state
|
||||
*/
|
||||
public deleteAllIsCancelled(): void {
|
||||
this._isCancelled = {};
|
||||
}
|
||||
/**
|
||||
* Clear all filled state
|
||||
*/
|
||||
public deleteAllFilled(): void {
|
||||
this._filledTakerAmount = {};
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user