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:
fabioberger
2019-11-07 12:46:34 +00:00
parent 2915ee08ea
commit fba3870ef1
20 changed files with 56 additions and 2140 deletions

View File

@@ -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",

View 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);
});
});
});

View File

@@ -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>;
}

View File

@@ -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;
}

View File

@@ -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>;
}

View File

@@ -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;
}

View File

@@ -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;
}
}

View 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));
}
}

View File

@@ -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);
}

View File

@@ -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(

View File

@@ -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 {

View File

@@ -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;
}
}

View File

@@ -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 = {};
}
}

View File

@@ -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 = {};
}
}