diff options
Diffstat (limited to 'packages/contract-wrappers/src')
5 files changed, 192 insertions, 2 deletions
diff --git a/packages/contract-wrappers/src/contract_wrappers.ts b/packages/contract-wrappers/src/contract_wrappers.ts index 8ca0fc93c..4272cc943 100644 --- a/packages/contract-wrappers/src/contract_wrappers.ts +++ b/packages/contract-wrappers/src/contract_wrappers.ts @@ -111,6 +111,8 @@ export class ContractWrappers { this.exchange = new ExchangeWrapper( this._web3Wrapper, config.networkId, + this.erc20Token, + this.erc721Token, config.exchangeContractAddress, config.zrxContractAddress, blockPollingIntervalMs, diff --git a/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts index cfef0f107..dc9ffcbf7 100644 --- a/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts +++ b/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts @@ -1,12 +1,19 @@ import { schemas } from '@0xproject/json-schemas'; -import { assetDataUtils } from '@0xproject/order-utils'; +import { + assetDataUtils, + BalanceAndProxyAllowanceLazyStore, + ExchangeTransferSimulator, + OrderValidationUtils, +} from '@0xproject/order-utils'; import { AssetProxyId, Order, SignedOrder } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; import { Web3Wrapper } from '@0xproject/web3-wrapper'; -import { ContractAbi, LogWithDecodedArgs } from 'ethereum-types'; +import { BlockParamLiteral, ContractAbi, LogWithDecodedArgs } from 'ethereum-types'; import * as _ from 'lodash'; import { artifacts } from '../artifacts'; +import { AssetBalanceAndProxyAllowanceFetcher } from '../fetchers/asset_balance_and_proxy_allowance_fetcher'; +import { OrderFilledCancelledFetcher } from '../fetchers/order_filled_cancelled_fetcher'; import { methodOptsSchema } from '../schemas/method_opts_schema'; import { orderTxOptsSchema } from '../schemas/order_tx_opts_schema'; import { txOptsSchema } from '../schemas/tx_opts_schema'; @@ -17,13 +24,17 @@ import { IndexedFilterValues, MethodOpts, OrderInfo, + OrderStatus, OrderTransactionOpts, + ValidateOrderFillableOpts, } from '../types'; import { assert } from '../utils/assert'; import { decorators } from '../utils/decorators'; import { TransactionEncoder } from '../utils/transaction_encoder'; import { ContractWrapper } from './contract_wrapper'; +import { ERC20TokenWrapper } from './erc20_token_wrapper'; +import { ERC721TokenWrapper } from './erc721_token_wrapper'; import { ExchangeContract, ExchangeEventArgs, ExchangeEvents } from './generated/exchange'; /** @@ -33,6 +44,8 @@ import { ExchangeContract, ExchangeEventArgs, ExchangeEvents } from './generated export class ExchangeWrapper extends ContractWrapper { public abi: ContractAbi = artifacts.Exchange.compilerOutput.abi; private _exchangeContractIfExists?: ExchangeContract; + private _erc721TokenWrapper: ERC721TokenWrapper; + private _erc20TokenWrapper: ERC20TokenWrapper; private _contractAddressIfExists?: string; private _zrxContractAddressIfExists?: string; /** @@ -48,11 +61,15 @@ export class ExchangeWrapper extends ContractWrapper { constructor( web3Wrapper: Web3Wrapper, networkId: number, + erc20TokenWrapper: ERC20TokenWrapper, + erc721TokenWrapper: ERC721TokenWrapper, contractAddressIfExists?: string, zrxContractAddressIfExists?: string, blockPollingIntervalMs?: number, ) { super(web3Wrapper, networkId, blockPollingIntervalMs); + this._erc20TokenWrapper = erc20TokenWrapper; + this._erc721TokenWrapper = erc721TokenWrapper; this._contractAddressIfExists = contractAddressIfExists; this._zrxContractAddressIfExists = zrxContractAddressIfExists; } @@ -1085,6 +1102,64 @@ export class ExchangeWrapper extends ContractWrapper { return logs; } /** + * Validate if the supplied order is fillable, and throw if it isn't + * @param signedOrder SignedOrder of interest + * @param opts ValidateOrderFillableOpts options (e.g expectedFillTakerTokenAmount. + * If it isn't supplied, we check if the order is fillable for a non-zero amount) + */ + public async validateOrderFillableOrThrowAsync( + signedOrder: SignedOrder, + opts: ValidateOrderFillableOpts = {}, + ): Promise<void> { + const balanceAllowanceFetcher = new AssetBalanceAndProxyAllowanceFetcher( + this._erc20TokenWrapper, + this._erc721TokenWrapper, + BlockParamLiteral.Latest, + ); + const balanceAllowanceStore = new BalanceAndProxyAllowanceLazyStore(balanceAllowanceFetcher); + const exchangeTradeSimulator = new ExchangeTransferSimulator(balanceAllowanceStore); + + const expectedFillTakerTokenAmountIfExists = opts.expectedFillTakerTokenAmount; + const filledCancelledFetcher = new OrderFilledCancelledFetcher(this, BlockParamLiteral.Latest); + const orderValidationUtils = new OrderValidationUtils(filledCancelledFetcher); + await orderValidationUtils.validateOrderFillableOrThrowAsync( + exchangeTradeSimulator, + signedOrder, + this.getZRXAssetData(), + expectedFillTakerTokenAmountIfExists, + ); + } + /** + * Validate a call to FillOrder and throw if it wouldn't succeed + * @param signedOrder SignedOrder of interest + * @param fillTakerAssetAmount Amount we'd like to fill the order for + * @param takerAddress The taker of the order + */ + public async validateFillOrderThrowIfInvalidAsync( + signedOrder: SignedOrder, + fillTakerAssetAmount: BigNumber, + takerAddress: string, + ): Promise<void> { + const balanceAllowanceFetcher = new AssetBalanceAndProxyAllowanceFetcher( + this._erc20TokenWrapper, + this._erc721TokenWrapper, + BlockParamLiteral.Latest, + ); + const balanceAllowanceStore = new BalanceAndProxyAllowanceLazyStore(balanceAllowanceFetcher); + const exchangeTradeSimulator = new ExchangeTransferSimulator(balanceAllowanceStore); + + const filledCancelledFetcher = new OrderFilledCancelledFetcher(this, BlockParamLiteral.Latest); + const orderValidationUtils = new OrderValidationUtils(filledCancelledFetcher); + await orderValidationUtils.validateFillOrderThrowIfInvalidAsync( + exchangeTradeSimulator, + this._web3Wrapper.getProvider(), + signedOrder, + fillTakerAssetAmount, + takerAddress, + this.getZRXAssetData(), + ); + } + /** * Retrieves the Ethereum address of the Exchange contract deployed on the network * that the user-passed web3 provider is connected to. * @returns The Ethereum address of the Exchange contract being used. diff --git a/packages/contract-wrappers/src/fetchers/asset_balance_and_proxy_allowance_fetcher.ts b/packages/contract-wrappers/src/fetchers/asset_balance_and_proxy_allowance_fetcher.ts new file mode 100644 index 000000000..023cd5ac3 --- /dev/null +++ b/packages/contract-wrappers/src/fetchers/asset_balance_and_proxy_allowance_fetcher.ts @@ -0,0 +1,77 @@ +// tslint:disable:no-unnecessary-type-assertion +import { AbstractBalanceAndProxyAllowanceFetcher, assetDataUtils } from '@0xproject/order-utils'; +import { AssetProxyId, ERC20AssetData, ERC721AssetData } from '@0xproject/types'; +import { BigNumber } from '@0xproject/utils'; +import { BlockParamLiteral } from 'ethereum-types'; + +import { ERC20TokenWrapper } from '../contract_wrappers/erc20_token_wrapper'; +import { ERC721TokenWrapper } from '../contract_wrappers/erc721_token_wrapper'; + +export class AssetBalanceAndProxyAllowanceFetcher implements AbstractBalanceAndProxyAllowanceFetcher { + private readonly _erc20Token: ERC20TokenWrapper; + private readonly _erc721Token: ERC721TokenWrapper; + private readonly _stateLayer: BlockParamLiteral; + constructor(erc20Token: ERC20TokenWrapper, erc721Token: ERC721TokenWrapper, stateLayer: BlockParamLiteral) { + this._erc20Token = erc20Token; + this._erc721Token = erc721Token; + this._stateLayer = stateLayer; + } + public async getBalanceAsync(assetData: string, userAddress: string): Promise<BigNumber> { + const decodedAssetData = assetDataUtils.decodeAssetDataOrThrow(assetData); + if (decodedAssetData.assetProxyId === AssetProxyId.ERC20) { + const decodedERC20AssetData = decodedAssetData as ERC20AssetData; + const balance = await this._erc20Token.getBalanceAsync(decodedERC20AssetData.tokenAddress, userAddress, { + defaultBlock: this._stateLayer, + }); + return balance; + } else { + const decodedERC721AssetData = decodedAssetData as ERC721AssetData; + const tokenOwner = await this._erc721Token.getOwnerOfAsync( + decodedERC721AssetData.tokenAddress, + decodedERC721AssetData.tokenId, + { + defaultBlock: this._stateLayer, + }, + ); + const balance = tokenOwner === userAddress ? new BigNumber(1) : new BigNumber(0); + return balance; + } + } + public async getProxyAllowanceAsync(assetData: string, userAddress: string): Promise<BigNumber> { + const decodedAssetData = assetDataUtils.decodeAssetDataOrThrow(assetData); + if (decodedAssetData.assetProxyId === AssetProxyId.ERC20) { + const decodedERC20AssetData = decodedAssetData as ERC20AssetData; + const proxyAllowance = await this._erc20Token.getProxyAllowanceAsync( + decodedERC20AssetData.tokenAddress, + userAddress, + { + defaultBlock: this._stateLayer, + }, + ); + return proxyAllowance; + } else { + const decodedERC721AssetData = decodedAssetData as ERC721AssetData; + + const isApprovedForAll = await this._erc721Token.isProxyApprovedForAllAsync( + decodedERC721AssetData.tokenAddress, + userAddress, + { + defaultBlock: this._stateLayer, + }, + ); + if (isApprovedForAll) { + return new BigNumber(this._erc20Token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS); + } else { + const isApproved = await this._erc721Token.isProxyApprovedAsync( + decodedERC721AssetData.tokenAddress, + decodedERC721AssetData.tokenId, + { + defaultBlock: this._stateLayer, + }, + ); + const proxyAllowance = isApproved ? new BigNumber(1) : new BigNumber(0); + return proxyAllowance; + } + } + } +} diff --git a/packages/contract-wrappers/src/fetchers/order_filled_cancelled_fetcher.ts b/packages/contract-wrappers/src/fetchers/order_filled_cancelled_fetcher.ts new file mode 100644 index 000000000..ba6f5fb5e --- /dev/null +++ b/packages/contract-wrappers/src/fetchers/order_filled_cancelled_fetcher.ts @@ -0,0 +1,30 @@ +// tslint:disable:no-unnecessary-type-assertion +import { AbstractOrderFilledCancelledFetcher } from '@0xproject/order-utils'; +import { BigNumber } from '@0xproject/utils'; +import { BlockParamLiteral } from 'ethereum-types'; + +import { ERC20TokenWrapper } from '../contract_wrappers/erc20_token_wrapper'; +import { ExchangeWrapper } from '../contract_wrappers/exchange_wrapper'; + +export class OrderFilledCancelledFetcher implements AbstractOrderFilledCancelledFetcher { + private readonly _exchange: ExchangeWrapper; + private readonly _stateLayer: BlockParamLiteral; + constructor(exchange: ExchangeWrapper, stateLayer: BlockParamLiteral) { + this._exchange = exchange; + this._stateLayer = stateLayer; + } + public async getFilledTakerAmountAsync(orderHash: string): Promise<BigNumber> { + const filledTakerAmount = this._exchange.getFilledTakerAssetAmountAsync(orderHash, { + defaultBlock: this._stateLayer, + }); + return filledTakerAmount; + } + public async isOrderCancelledAsync(orderHash: string): Promise<boolean> { + const isCancelled = await this._exchange.isCancelledAsync(orderHash); + return isCancelled; + } + public getZRXAssetData(): string { + const zrxAssetData = this._exchange.getZRXAssetData(); + return zrxAssetData; + } +} diff --git a/packages/contract-wrappers/src/index.ts b/packages/contract-wrappers/src/index.ts index b66d8460e..2fcdd2ddb 100644 --- a/packages/contract-wrappers/src/index.ts +++ b/packages/contract-wrappers/src/index.ts @@ -25,6 +25,7 @@ export { BalanceAndAllowance, OrderAndTraderInfo, TraderInfo, + ValidateOrderFillableOpts, } from './types'; export { Order, SignedOrder, AssetProxyId } from '@0xproject/types'; @@ -85,3 +86,8 @@ export { ExchangeEventArgs, ExchangeEvents, } from './contract_wrappers/generated/exchange'; + +export { AbstractBalanceAndProxyAllowanceFetcher, AbstractOrderFilledCancelledFetcher } from '@0xproject/order-utils'; + +export { AssetBalanceAndProxyAllowanceFetcher } from './fetchers/asset_balance_and_proxy_allowance_fetcher'; +export { OrderFilledCancelledFetcher } from './fetchers/order_filled_cancelled_fetcher'; |