aboutsummaryrefslogtreecommitdiffstats
path: root/packages/contracts/test/utils
diff options
context:
space:
mode:
Diffstat (limited to 'packages/contracts/test/utils')
-rw-r--r--packages/contracts/test/utils/artifacts.ts2
-rw-r--r--packages/contracts/test/utils/erc20_wrapper.ts8
-rw-r--r--packages/contracts/test/utils/forwarder_wrapper.ts220
-rw-r--r--packages/contracts/test/utils/types.ts8
4 files changed, 238 insertions, 0 deletions
diff --git a/packages/contracts/test/utils/artifacts.ts b/packages/contracts/test/utils/artifacts.ts
index 23e93c085..d3f808218 100644
--- a/packages/contracts/test/utils/artifacts.ts
+++ b/packages/contracts/test/utils/artifacts.ts
@@ -8,6 +8,7 @@ import * as ERC20Proxy from '../../artifacts/ERC20Proxy.json';
import * as ERC721Proxy from '../../artifacts/ERC721Proxy.json';
import * as Exchange from '../../artifacts/Exchange.json';
import * as ExchangeWrapper from '../../artifacts/ExchangeWrapper.json';
+import * as Forwarder from '../../artifacts/Forwarder.json';
import * as IAssetProxy from '../../artifacts/IAssetProxy.json';
import * as MixinAuthorizable from '../../artifacts/MixinAuthorizable.json';
import * as MultiSigWallet from '../../artifacts/MultiSigWallet.json';
@@ -34,6 +35,7 @@ export const artifacts = {
Exchange: (Exchange as any) as ContractArtifact,
ExchangeWrapper: (ExchangeWrapper as any) as ContractArtifact,
EtherToken: (EtherToken as any) as ContractArtifact,
+ Forwarder: (Forwarder as any) as ContractArtifact,
IAssetProxy: (IAssetProxy as any) as ContractArtifact,
MixinAuthorizable: (MixinAuthorizable as any) as ContractArtifact,
MultiSigWallet: (MultiSigWallet as any) as ContractArtifact,
diff --git a/packages/contracts/test/utils/erc20_wrapper.ts b/packages/contracts/test/utils/erc20_wrapper.ts
index 53e9791bc..9351b1e3d 100644
--- a/packages/contracts/test/utils/erc20_wrapper.ts
+++ b/packages/contracts/test/utils/erc20_wrapper.ts
@@ -138,6 +138,14 @@ export class ERC20Wrapper {
});
return balancesByOwner;
}
+ public addDummyTokenContract(dummy: DummyERC20TokenContract): void {
+ if (!_.isUndefined(this._dummyTokenContracts)) {
+ this._dummyTokenContracts.push(dummy);
+ }
+ }
+ public addTokenOwnerAddress(address: string): void {
+ this._tokenOwnerAddresses.push(address);
+ }
public getTokenOwnerAddresses(): string[] {
return this._tokenOwnerAddresses;
}
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;
+ }
+}
diff --git a/packages/contracts/test/utils/types.ts b/packages/contracts/test/utils/types.ts
index b792bb90a..67313b647 100644
--- a/packages/contracts/test/utils/types.ts
+++ b/packages/contracts/test/utils/types.ts
@@ -102,6 +102,7 @@ export enum ContractName {
TestWallet = 'TestWallet',
Authorizable = 'Authorizable',
Whitelist = 'Whitelist',
+ Forwarder = 'Forwarder',
}
export interface SignedTransaction {
@@ -227,3 +228,10 @@ export interface FillScenario {
makerStateScenario: TraderStateScenario;
takerStateScenario: TraderStateScenario;
}
+
+export interface FillResults {
+ makerAssetFilledAmount: BigNumber;
+ takerAssetFilledAmount: BigNumber;
+ makerFeePaid: BigNumber;
+ takerFeePaid: BigNumber;
+}