aboutsummaryrefslogtreecommitdiffstats
path: root/packages/contracts/test/utils/forwarder_wrapper.ts
diff options
context:
space:
mode:
authorJacob Evans <jacob@dekz.net>2018-07-06 13:00:09 +0800
committerJacob Evans <jacob@dekz.net>2018-07-06 13:30:07 +0800
commit0557d6a9bfc07b8d360970ffbcf582f8a26943cb (patch)
tree76b233a7a50c2e10ee3eb09a848c2494d0512c26 /packages/contracts/test/utils/forwarder_wrapper.ts
parentb9b00e10d39c3c84bc72892ef37f1313e904414d (diff)
downloaddexon-sol-tools-0557d6a9bfc07b8d360970ffbcf582f8a26943cb.tar
dexon-sol-tools-0557d6a9bfc07b8d360970ffbcf582f8a26943cb.tar.gz
dexon-sol-tools-0557d6a9bfc07b8d360970ffbcf582f8a26943cb.tar.bz2
dexon-sol-tools-0557d6a9bfc07b8d360970ffbcf582f8a26943cb.tar.lz
dexon-sol-tools-0557d6a9bfc07b8d360970ffbcf582f8a26943cb.tar.xz
dexon-sol-tools-0557d6a9bfc07b8d360970ffbcf582f8a26943cb.tar.zst
dexon-sol-tools-0557d6a9bfc07b8d360970ffbcf582f8a26943cb.zip
Forwarding contract (squashed commits)
Diffstat (limited to 'packages/contracts/test/utils/forwarder_wrapper.ts')
-rw-r--r--packages/contracts/test/utils/forwarder_wrapper.ts220
1 files changed, 220 insertions, 0 deletions
diff --git a/packages/contracts/test/utils/forwarder_wrapper.ts b/packages/contracts/test/utils/forwarder_wrapper.ts
new file mode 100644
index 000000000..d227420ee
--- /dev/null
+++ b/packages/contracts/test/utils/forwarder_wrapper.ts
@@ -0,0 +1,220 @@
+import { assetProxyUtils } from '@0xproject/order-utils';
+import { AssetProxyId, SignedOrder } from '@0xproject/types';
+import { BigNumber } from '@0xproject/utils';
+import { Web3Wrapper } from '@0xproject/web3-wrapper';
+import { Provider, TransactionReceiptWithDecodedLogs, TxDataPayable } from 'ethereum-types';
+import * as _ from 'lodash';
+
+import { ForwarderContract } from '../../generated_contract_wrappers/forwarder';
+
+import { constants } from './constants';
+import { formatters } from './formatters';
+import { LogDecoder } from './log_decoder';
+import { MarketSellOrders } from './types';
+
+const DEFAULT_FEE_PROPORTION = 0;
+const PERCENTAGE_DENOMINATOR = 10000;
+const ZERO_AMOUNT = new BigNumber(0);
+const INSUFFICENT_ORDERS_FOR_MAKER_AMOUNT = 'Unable to satisfy makerAssetFillAmount with provided orders';
+
+export class ForwarderWrapper {
+ private _web3Wrapper: Web3Wrapper;
+ private _forwarderContract: ForwarderContract;
+ private _logDecoder: LogDecoder;
+ private _zrxAddress: string;
+ private static _createOptimizedSellOrders(signedOrders: SignedOrder[]): MarketSellOrders {
+ const marketSellOrders = formatters.createMarketSellOrders(signedOrders, ZERO_AMOUNT);
+ const assetDataId = assetProxyUtils.decodeAssetDataId(signedOrders[0].makerAssetData);
+ // Contract will fill this in for us as all of the assetData is assumed to be the same
+ for (let i = 0; i < signedOrders.length; i++) {
+ if (i !== 0 && assetDataId === AssetProxyId.ERC20) {
+ // Forwarding contract will fill this in from the first order
+ marketSellOrders.orders[i].makerAssetData = constants.NULL_BYTES;
+ }
+ marketSellOrders.orders[i].takerAssetData = constants.NULL_BYTES;
+ }
+ return marketSellOrders;
+ }
+ private static _createOptimizedZRXSellOrders(signedOrders: SignedOrder[]): MarketSellOrders {
+ const marketSellOrders = formatters.createMarketSellOrders(signedOrders, ZERO_AMOUNT);
+ // Contract will fill this in for us as all of the assetData is assumed to be the same
+ for (let i = 0; i < signedOrders.length; i++) {
+ marketSellOrders.orders[i].makerAssetData = constants.NULL_BYTES;
+ marketSellOrders.orders[i].takerAssetData = constants.NULL_BYTES;
+ }
+ return marketSellOrders;
+ }
+ private static _calculateAdditionalFeeProportionAmount(feeProportion: number, fillAmountWei: BigNumber): BigNumber {
+ if (feeProportion > 0) {
+ // Add to the total ETH transaction to ensure all NFTs can be filled after fees
+ // 150 = 1.5% = 0.015
+ const denominator = new BigNumber(1).minus(new BigNumber(feeProportion).dividedBy(PERCENTAGE_DENOMINATOR));
+ return fillAmountWei.dividedBy(denominator).round(0, BigNumber.ROUND_FLOOR);
+ }
+ return fillAmountWei;
+ }
+ constructor(contractInstance: ForwarderContract, provider: Provider, zrxAddress: string) {
+ this._forwarderContract = contractInstance;
+ this._web3Wrapper = new Web3Wrapper(provider);
+ this._logDecoder = new LogDecoder(this._web3Wrapper, this._forwarderContract.address);
+ // this._web3Wrapper.abiDecoder.addABI(contractInstance.abi);
+ this._zrxAddress = zrxAddress;
+ }
+ public async marketBuyTokensWithEthAsync(
+ orders: SignedOrder[],
+ feeOrders: SignedOrder[],
+ makerTokenBuyAmount: BigNumber,
+ txData: TxDataPayable,
+ opts: { feeProportion?: number; feeRecipient?: string } = {},
+ ): Promise<TransactionReceiptWithDecodedLogs> {
+ const params = ForwarderWrapper._createOptimizedSellOrders(orders);
+ const feeParams = ForwarderWrapper._createOptimizedZRXSellOrders(feeOrders);
+ const feeProportion = _.isUndefined(opts.feeProportion) ? DEFAULT_FEE_PROPORTION : opts.feeProportion;
+ const feeRecipient = _.isUndefined(opts.feeRecipient) ? constants.NULL_ADDRESS : opts.feeRecipient;
+ const txHash: string = await this._forwarderContract.marketBuyTokensWithEth.sendTransactionAsync(
+ params.orders,
+ params.signatures,
+ feeParams.orders,
+ feeParams.signatures,
+ makerTokenBuyAmount,
+ feeProportion,
+ feeRecipient,
+ txData,
+ );
+ const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash);
+ return tx;
+ }
+ public async marketSellEthForERC20Async(
+ orders: SignedOrder[],
+ feeOrders: SignedOrder[],
+ txData: TxDataPayable,
+ opts: { feeProportion?: number; feeRecipient?: string } = {},
+ ): Promise<TransactionReceiptWithDecodedLogs> {
+ const assetDataId = assetProxyUtils.decodeAssetDataId(orders[0].makerAssetData);
+ if (assetDataId !== AssetProxyId.ERC20) {
+ throw new Error('Asset type not supported by marketSellEthForERC20');
+ }
+ const params = ForwarderWrapper._createOptimizedSellOrders(orders);
+ const feeParams = ForwarderWrapper._createOptimizedZRXSellOrders(feeOrders);
+ const feeProportion = _.isUndefined(opts.feeProportion) ? DEFAULT_FEE_PROPORTION : opts.feeProportion;
+ const feeRecipient = _.isUndefined(opts.feeRecipient) ? constants.NULL_ADDRESS : opts.feeRecipient;
+ const txHash: string = await this._forwarderContract.marketSellEthForERC20.sendTransactionAsync(
+ params.orders,
+ params.signatures,
+ feeParams.orders,
+ feeParams.signatures,
+ feeProportion,
+ feeRecipient,
+ txData,
+ );
+ const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash);
+ return tx;
+ }
+ public async calculateMarketBuyFillAmountWeiAsync(
+ orders: SignedOrder[],
+ feeOrders: SignedOrder[],
+ feeProportion: number,
+ makerAssetFillAmount: BigNumber,
+ ): Promise<BigNumber> {
+ const assetProxyId = assetProxyUtils.decodeAssetDataId(orders[0].makerAssetData);
+ switch (assetProxyId) {
+ case AssetProxyId.ERC20: {
+ const fillAmountWei = this._calculateMarketBuyERC20FillAmountAsync(
+ orders,
+ feeOrders,
+ feeProportion,
+ makerAssetFillAmount,
+ );
+ return fillAmountWei;
+ }
+ case AssetProxyId.ERC721: {
+ const fillAmountWei = await this._calculateMarketBuyERC721FillAmountAsync(
+ orders,
+ feeOrders,
+ feeProportion,
+ );
+ return fillAmountWei;
+ }
+ default:
+ throw new Error(`Invalid Asset Proxy Id: ${assetProxyId}`);
+ }
+ }
+ private async _calculateMarketBuyERC20FillAmountAsync(
+ orders: SignedOrder[],
+ feeOrders: SignedOrder[],
+ feeProportion: number,
+ makerAssetFillAmount: BigNumber,
+ ): Promise<BigNumber> {
+ const makerAssetData = assetProxyUtils.decodeAssetData(orders[0].makerAssetData);
+ const makerAssetToken = makerAssetData.tokenAddress;
+ const params = formatters.createMarketBuyOrders(orders, makerAssetFillAmount);
+
+ let fillAmountWei;
+ if (makerAssetToken === this._zrxAddress) {
+ // If buying ZRX we buy the tokens and fees from the ZRX order in one step
+ const expectedBuyFeeTokensFillResults = await this._forwarderContract.calculateMarketBuyZrxResults.callAsync(
+ params.orders,
+ makerAssetFillAmount,
+ );
+ if (expectedBuyFeeTokensFillResults.makerAssetFilledAmount.lessThan(makerAssetFillAmount)) {
+ throw new Error(INSUFFICENT_ORDERS_FOR_MAKER_AMOUNT);
+ }
+ fillAmountWei = expectedBuyFeeTokensFillResults.takerAssetFilledAmount;
+ } else {
+ const expectedMarketBuyFillResults = await this._forwarderContract.calculateMarketBuyResults.callAsync(
+ params.orders,
+ makerAssetFillAmount,
+ );
+ if (expectedMarketBuyFillResults.makerAssetFilledAmount.lessThan(makerAssetFillAmount)) {
+ throw new Error(INSUFFICENT_ORDERS_FOR_MAKER_AMOUNT);
+ }
+ fillAmountWei = expectedMarketBuyFillResults.takerAssetFilledAmount;
+ const expectedFeeAmount = expectedMarketBuyFillResults.takerFeePaid;
+ if (expectedFeeAmount.greaterThan(ZERO_AMOUNT)) {
+ const expectedFeeFillFillAmountWei = await this._calculateMarketBuyERC20FillAmountAsync(
+ feeOrders,
+ [],
+ DEFAULT_FEE_PROPORTION,
+ expectedFeeAmount,
+ );
+ fillAmountWei = fillAmountWei.plus(expectedFeeFillFillAmountWei);
+ }
+ }
+ fillAmountWei = ForwarderWrapper._calculateAdditionalFeeProportionAmount(feeProportion, fillAmountWei);
+ return fillAmountWei;
+ }
+ private async _calculateMarketBuyERC721FillAmountAsync(
+ orders: SignedOrder[],
+ feeOrders: SignedOrder[],
+ feeProportion: number,
+ ): Promise<BigNumber> {
+ // Total cost when buying ERC721 is the total cost of all ERC721 orders + any fee abstraction
+ let fillAmountWei = _.reduce(
+ orders,
+ (totalAmount: BigNumber, order: SignedOrder) => {
+ return totalAmount.plus(order.takerAssetAmount);
+ },
+ ZERO_AMOUNT,
+ );
+ const totalFees = _.reduce(
+ orders,
+ (totalAmount: BigNumber, order: SignedOrder) => {
+ return totalAmount.plus(order.takerFee);
+ },
+ ZERO_AMOUNT,
+ );
+ if (totalFees.greaterThan(ZERO_AMOUNT)) {
+ // Calculate the ZRX fee abstraction cost
+ const emptyFeeOrders: SignedOrder[] = [];
+ const expectedFeeAmountWei = await this._calculateMarketBuyERC20FillAmountAsync(
+ feeOrders,
+ emptyFeeOrders,
+ DEFAULT_FEE_PROPORTION,
+ totalFees,
+ );
+ fillAmountWei = fillAmountWei.plus(expectedFeeAmountWei);
+ }
+ fillAmountWei = ForwarderWrapper._calculateAdditionalFeeProportionAmount(feeProportion, fillAmountWei);
+ return fillAmountWei;
+ }
+}