aboutsummaryrefslogtreecommitdiffstats
path: root/packages/contract-wrappers
diff options
context:
space:
mode:
Diffstat (limited to 'packages/contract-wrappers')
-rw-r--r--packages/contract-wrappers/package.json4
-rw-r--r--packages/contract-wrappers/src/artifacts.ts2
-rw-r--r--packages/contract-wrappers/src/contract_wrappers.ts12
-rw-r--r--packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts22
-rw-r--r--packages/contract-wrappers/src/contract_wrappers/forwarder_wrapper.ts209
-rw-r--r--packages/contract-wrappers/src/index.ts1
-rw-r--r--packages/contract-wrappers/src/types.ts6
-rw-r--r--packages/contract-wrappers/src/utils/assert.ts60
-rw-r--r--packages/contract-wrappers/src/utils/constants.ts1
-rw-r--r--packages/contract-wrappers/test/exchange_wrapper_test.ts11
-rw-r--r--packages/contract-wrappers/test/forwarder_wrapper_test.ts130
11 files changed, 448 insertions, 10 deletions
diff --git a/packages/contract-wrappers/package.json b/packages/contract-wrappers/package.json
index ed0278caa..f27afaba9 100644
--- a/packages/contract-wrappers/package.json
+++ b/packages/contract-wrappers/package.json
@@ -14,7 +14,7 @@
"watch_without_deps": "yarn pre_build && tsc -w",
"build": "yarn pre_build && tsc && copyfiles -u 3 './lib/src/monorepo_scripts/**/*' ./scripts",
"pre_build": "run-s update_artifacts_v2_beta update_artifacts_v2 generate_contract_wrappers copy_artifacts",
- "generate_contract_wrappers": "abi-gen --abis 'src/artifacts/@(Exchange|DummyERC20Token|DummyERC721Token|ZRXToken|ERC20Token|ERC721Token|WETH9|ERC20Proxy|ERC721Proxy).json' --template ../contract_templates/contract.handlebars --partials '../contract_templates/partials/**/*.handlebars' --output src/contract_wrappers/generated --backend ethers",
+ "generate_contract_wrappers": "abi-gen --abis 'src/artifacts/@(Exchange|DummyERC20Token|DummyERC721Token|ZRXToken|ERC20Token|ERC721Token|WETH9|ERC20Proxy|ERC721Proxy|Forwarder).json' --template ../contract_templates/contract.handlebars --partials '../contract_templates/partials/**/*.handlebars' --output src/contract_wrappers/generated --backend ethers",
"lint": "tslint --project . --exclude **/src/contract_wrappers/**/* --exclude **/lib/**/*",
"test:circleci": "run-s test:coverage",
"test": "yarn run_mocha",
@@ -29,7 +29,7 @@
"manual:postpublish": "yarn build; node ./scripts/postpublish.js"
},
"config": {
- "contracts_v2_beta": "Exchange ERC20Proxy ERC20Token ERC721Proxy ERC721Token WETH9 ZRXToken",
+ "contracts_v2_beta": "Exchange ERC20Proxy ERC20Token ERC721Proxy ERC721Token WETH9 ZRXToken Forwarder",
"contracts_v2": "DummyERC20Token DummyERC721Token"
},
"repository": {
diff --git a/packages/contract-wrappers/src/artifacts.ts b/packages/contract-wrappers/src/artifacts.ts
index 742d0e1b2..2481b311a 100644
--- a/packages/contract-wrappers/src/artifacts.ts
+++ b/packages/contract-wrappers/src/artifacts.ts
@@ -7,6 +7,7 @@ import * as ERC20Token from './artifacts/ERC20Token.json';
import * as ERC721Proxy from './artifacts/ERC721Proxy.json';
import * as ERC721Token from './artifacts/ERC721Token.json';
import * as Exchange from './artifacts/Exchange.json';
+import * as Forwarder from './artifacts/Forwarder.json';
import * as EtherToken from './artifacts/WETH9.json';
import * as ZRXToken from './artifacts/ZRXToken.json';
@@ -20,4 +21,5 @@ export const artifacts = {
EtherToken: (EtherToken as any) as ContractArtifact,
ERC20Proxy: (ERC20Proxy as any) as ContractArtifact,
ERC721Proxy: (ERC721Proxy as any) as ContractArtifact,
+ Forwarder: (Forwarder as any) as ContractArtifact,
};
diff --git a/packages/contract-wrappers/src/contract_wrappers.ts b/packages/contract-wrappers/src/contract_wrappers.ts
index 8010242c5..4277a0746 100644
--- a/packages/contract-wrappers/src/contract_wrappers.ts
+++ b/packages/contract-wrappers/src/contract_wrappers.ts
@@ -11,6 +11,7 @@ import { ERC721ProxyWrapper } from './contract_wrappers/erc721_proxy_wrapper';
import { ERC721TokenWrapper } from './contract_wrappers/erc721_token_wrapper';
import { EtherTokenWrapper } from './contract_wrappers/ether_token_wrapper';
import { ExchangeWrapper } from './contract_wrappers/exchange_wrapper';
+import { ForwarderWrapper } from './contract_wrappers/forwarder_wrapper';
import { ContractWrappersConfigSchema } from './schemas/contract_wrappers_config_schema';
import { contractWrappersPrivateNetworkConfigSchema } from './schemas/contract_wrappers_private_network_config_schema';
import { contractWrappersPublicNetworkConfigSchema } from './schemas/contract_wrappers_public_network_config_schema';
@@ -47,6 +48,11 @@ export class ContractWrappers {
* erc721Proxy smart contract.
*/
public erc721Proxy: ERC721ProxyWrapper;
+ /**
+ * An instance of the ForwarderWrapper class containing methods for interacting with any Forwarder smart contract.
+ */
+ public forwarder: ForwarderWrapper;
+
private _web3Wrapper: Web3Wrapper;
/**
* Instantiates a new ContractWrappers instance.
@@ -104,6 +110,12 @@ export class ContractWrappers {
config.zrxContractAddress,
blockPollingIntervalMs,
);
+ this.forwarder = new ForwarderWrapper(
+ this._web3Wrapper,
+ config.networkId,
+ config.forwarderContractAddress,
+ config.zrxContractAddress,
+ );
}
/**
* Sets a new web3 provider for 0x.js. Updating the provider will stop all
diff --git a/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts
index 3e7619228..48bd00f90 100644
--- a/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts
+++ b/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts
@@ -869,16 +869,36 @@ export class ExchangeWrapper extends ContractWrapper {
*/
@decorators.asyncZeroExErrorHandler
public async getOrderInfoAsync(order: Order | SignedOrder, methodOpts: MethodOpts = {}): Promise<OrderInfo> {
+ assert.doesConformToSchema('order', order, schemas.orderSchema);
if (!_.isUndefined(methodOpts)) {
assert.doesConformToSchema('methodOpts', methodOpts, methodOptsSchema);
}
const exchangeInstance = await this._getExchangeContractAsync();
-
const txData = {};
const orderInfo = await exchangeInstance.getOrderInfo.callAsync(order, txData, methodOpts.defaultBlock);
return orderInfo;
}
/**
+ * Get order info for multiple orders
+ * @param orders Orders
+ * @param methodOpts Optional arguments this method accepts.
+ * @returns Array of Order infos
+ */
+ @decorators.asyncZeroExErrorHandler
+ public async getOrdersInfoAsync(
+ orders: Array<Order | SignedOrder>,
+ methodOpts: MethodOpts = {},
+ ): Promise<OrderInfo[]> {
+ assert.doesConformToSchema('orders', orders, schemas.ordersSchema);
+ if (!_.isUndefined(methodOpts)) {
+ assert.doesConformToSchema('methodOpts', methodOpts, methodOptsSchema);
+ }
+ const exchangeInstance = await this._getExchangeContractAsync();
+ const txData = {};
+ const ordersInfo = await exchangeInstance.getOrdersInfo.callAsync(orders, txData, methodOpts.defaultBlock);
+ return ordersInfo;
+ }
+ /**
* Cancel a given order.
* @param order An object that conforms to the Order or SignedOrder interface. The order you would like to cancel.
* @param transactionOpts Optional arguments this method accepts.
diff --git a/packages/contract-wrappers/src/contract_wrappers/forwarder_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/forwarder_wrapper.ts
new file mode 100644
index 000000000..90cfbe2af
--- /dev/null
+++ b/packages/contract-wrappers/src/contract_wrappers/forwarder_wrapper.ts
@@ -0,0 +1,209 @@
+import { schemas } from '@0xproject/json-schemas';
+import { AssetProxyId, SignedOrder } from '@0xproject/types';
+import { BigNumber } from '@0xproject/utils';
+import { Web3Wrapper } from '@0xproject/web3-wrapper';
+import { ContractAbi } from 'ethereum-types';
+import * as _ from 'lodash';
+
+import { artifacts } from '../artifacts';
+import { orderTxOptsSchema } from '../schemas/order_tx_opts_schema';
+import { txOptsSchema } from '../schemas/tx_opts_schema';
+import { TransactionOpts } from '../types';
+import { assert } from '../utils/assert';
+import { constants } from '../utils/constants';
+
+import { ContractWrapper } from './contract_wrapper';
+import { ForwarderContract } from './generated/forwarder';
+
+/**
+ * This class includes the functionality related to interacting with the Forwarder contract.
+ */
+export class ForwarderWrapper extends ContractWrapper {
+ public abi: ContractAbi = artifacts.Forwarder.compilerOutput.abi;
+ private _forwarderContractIfExists?: ForwarderContract;
+ private _contractAddressIfExists?: string;
+ private _zrxContractAddressIfExists?: string;
+ constructor(
+ web3Wrapper: Web3Wrapper,
+ networkId: number,
+ contractAddressIfExists?: string,
+ zrxContractAddressIfExists?: string,
+ ) {
+ super(web3Wrapper, networkId);
+ this._contractAddressIfExists = contractAddressIfExists;
+ this._zrxContractAddressIfExists = zrxContractAddressIfExists;
+ }
+ /**
+ * Purchases as much of orders' makerAssets as possible by selling up to 95% of transaction's ETH value.
+ * Any ZRX required to pay fees for primary orders will automatically be purchased by this contract.
+ * 5% of ETH value is reserved for paying fees to order feeRecipients (in ZRX) and forwarding contract feeRecipient (in ETH).
+ * Any ETH not spent will be refunded to sender.
+ * @param signedOrders An array of objects that conform to the SignedOrder interface. All orders must specify the same makerAsset.
+ * All orders must specify WETH as the takerAsset
+ * @param takerAddress The user Ethereum address who would like to fill this order. Must be available via the supplied
+ * Provider provided at instantiation.
+ * @param ethAmount The amount of eth to send with the transaction (in wei).
+ * @param signedFeeOrders An array of objects that conform to the SignedOrder interface. All orders must specify ZRX as makerAsset and WETH as takerAsset.
+ * Used to purchase ZRX for primary order fees.
+ * @param feePercentage The percentage of WETH sold that will payed as fee to forwarding contract feeRecipient.
+ * Defaults to 0.
+ * @param feeRecipientAddress The address that will receive ETH when signedFeeOrders are filled.
+ * @param txOpts Transaction parameters.
+ * @return Transaction hash.
+ */
+ public async marketSellOrdersWithEthAsync(
+ signedOrders: SignedOrder[],
+ takerAddress: string,
+ ethAmount: BigNumber,
+ signedFeeOrders: SignedOrder[] = [],
+ feePercentage: BigNumber = constants.ZERO_AMOUNT,
+ feeRecipientAddress: string = constants.NULL_ADDRESS,
+ txOpts: TransactionOpts = {},
+ ): Promise<string> {
+ // type assertions
+ assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema);
+ await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper);
+ assert.isBigNumber('ethAmount', ethAmount);
+ assert.doesConformToSchema('signedFeeOrders', signedFeeOrders, schemas.signedOrdersSchema);
+ assert.isBigNumber('feePercentage', feePercentage);
+ assert.isETHAddressHex('feeRecipientAddress', feeRecipientAddress);
+ assert.doesConformToSchema('txOpts', txOpts, txOptsSchema);
+ // other assertions
+ assert.ordersCanBeUsedForForwarderContract(signedOrders, this.getEtherTokenAddress());
+ assert.feeOrdersCanBeUsedForForwarderContract(
+ signedFeeOrders,
+ this.getZRXTokenAddress(),
+ this.getEtherTokenAddress(),
+ );
+ const normalizedTakerAddress = takerAddress.toLowerCase();
+ const normalizedFeeRecipientAddress = feeRecipientAddress.toLowerCase();
+ const forwarderContractInstance = await this._getForwarderContractAsync();
+ const txHash = await forwarderContractInstance.marketSellOrdersWithEth.sendTransactionAsync(
+ signedOrders,
+ _.map(signedOrders, order => order.signature),
+ signedFeeOrders,
+ _.map(signedFeeOrders, order => order.signature),
+ feePercentage,
+ feeRecipientAddress,
+ {
+ value: ethAmount,
+ from: normalizedTakerAddress,
+ gas: txOpts.gasLimit,
+ gasPrice: txOpts.gasPrice,
+ },
+ );
+ return txHash;
+ }
+ /**
+ * Attempt to purchase makerAssetFillAmount of makerAsset by selling ethAmount provided with transaction.
+ * Any ZRX required to pay fees for primary orders will automatically be purchased by the contract.
+ * Any ETH not spent will be refunded to sender.
+ * @param signedOrders An array of objects that conform to the SignedOrder interface. All orders must specify the same makerAsset.
+ * All orders must specify WETH as the takerAsset
+ * @param makerAssetFillAmount The amount of the order (in taker asset baseUnits) that you wish to fill.
+ * @param takerAddress The user Ethereum address who would like to fill this order. Must be available via the supplied
+ * Provider provided at instantiation.
+ * @param ethAmount The amount of eth to send with the transaction (in wei).
+ * @param signedFeeOrders An array of objects that conform to the SignedOrder interface. All orders must specify ZRX as makerAsset and WETH as takerAsset.
+ * Used to purchase ZRX for primary order fees.
+ * @param feePercentage The percentage of WETH sold that will payed as fee to forwarding contract feeRecipient.
+ * Defaults to 0.
+ * @param feeRecipientAddress The address that will receive ETH when signedFeeOrders are filled.
+ * @param txOpts Transaction parameters.
+ * @return Transaction hash.
+ */
+ public async marketBuyOrdersWithEthAsync(
+ signedOrders: SignedOrder[],
+ makerAssetFillAmount: BigNumber,
+ takerAddress: string,
+ ethAmount: BigNumber,
+ signedFeeOrders: SignedOrder[] = [],
+ feePercentage: BigNumber = constants.ZERO_AMOUNT,
+ feeRecipientAddress: string = constants.NULL_ADDRESS,
+ txOpts: TransactionOpts = {},
+ ): Promise<string> {
+ // type assertions
+ assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema);
+ assert.isBigNumber('makerAssetFillAmount', makerAssetFillAmount);
+ await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper);
+ assert.isBigNumber('ethAmount', ethAmount);
+ assert.doesConformToSchema('signedFeeOrders', signedFeeOrders, schemas.signedOrdersSchema);
+ assert.isBigNumber('feePercentage', feePercentage);
+ assert.isETHAddressHex('feeRecipientAddress', feeRecipientAddress);
+ assert.doesConformToSchema('txOpts', txOpts, txOptsSchema);
+ // other assertions
+ assert.ordersCanBeUsedForForwarderContract(signedOrders, this.getEtherTokenAddress());
+ assert.feeOrdersCanBeUsedForForwarderContract(
+ signedFeeOrders,
+ this.getZRXTokenAddress(),
+ this.getEtherTokenAddress(),
+ );
+ const normalizedTakerAddress = takerAddress.toLowerCase();
+ const normalizedFeeRecipientAddress = feeRecipientAddress.toLowerCase();
+ const forwarderContractInstance = await this._getForwarderContractAsync();
+ const txHash = await forwarderContractInstance.marketBuyOrdersWithEth.sendTransactionAsync(
+ signedOrders,
+ makerAssetFillAmount,
+ _.map(signedOrders, order => order.signature),
+ signedFeeOrders,
+ _.map(signedFeeOrders, order => order.signature),
+ feePercentage,
+ feeRecipientAddress,
+ {
+ value: ethAmount,
+ from: normalizedTakerAddress,
+ gas: txOpts.gasLimit,
+ gasPrice: txOpts.gasPrice,
+ },
+ );
+ return txHash;
+ }
+ /**
+ * Retrieves the Ethereum address of the Forwarder contract deployed on the network
+ * that the user-passed web3 provider is connected to.
+ * @returns The Ethereum address of the Forwarder contract being used.
+ */
+ public getContractAddress(): string {
+ const contractAddress = this._getContractAddress(artifacts.Forwarder, this._contractAddressIfExists);
+ return contractAddress;
+ }
+ /**
+ * Returns the ZRX token address used by the forwarder contract.
+ * @return Address of ZRX token
+ */
+ public getZRXTokenAddress(): string {
+ const contractAddress = this._getContractAddress(artifacts.ZRXToken, this._zrxContractAddressIfExists);
+ return contractAddress;
+ }
+ /**
+ * Returns the Ether token address used by the forwarder contract.
+ * @return Address of Ether token
+ */
+ public getEtherTokenAddress(): string {
+ const contractAddress = this._getContractAddress(artifacts.EtherToken);
+ return contractAddress;
+ }
+ // HACK: We don't want this method to be visible to the other units within that package but not to the end user.
+ // TS doesn't give that possibility and therefore we make it private and access it over an any cast. Because of that tslint sees it as unused.
+ // tslint:disable-next-line:no-unused-variable
+ private _invalidateContractInstance(): void {
+ delete this._forwarderContractIfExists;
+ }
+ private async _getForwarderContractAsync(): Promise<ForwarderContract> {
+ if (!_.isUndefined(this._forwarderContractIfExists)) {
+ return this._forwarderContractIfExists;
+ }
+ const [abi, address] = await this._getContractAbiAndAddressFromArtifactsAsync(
+ artifacts.Forwarder,
+ this._contractAddressIfExists,
+ );
+ const contractInstance = new ForwarderContract(
+ abi,
+ address,
+ this._web3Wrapper.getProvider(),
+ this._web3Wrapper.getContractDefaults(),
+ );
+ this._forwarderContractIfExists = contractInstance;
+ return this._forwarderContractIfExists;
+ }
+}
diff --git a/packages/contract-wrappers/src/index.ts b/packages/contract-wrappers/src/index.ts
index e5485d7a6..1986e0004 100644
--- a/packages/contract-wrappers/src/index.ts
+++ b/packages/contract-wrappers/src/index.ts
@@ -5,6 +5,7 @@ export { EtherTokenWrapper } from './contract_wrappers/ether_token_wrapper';
export { ExchangeWrapper } from './contract_wrappers/exchange_wrapper';
export { ERC20ProxyWrapper } from './contract_wrappers/erc20_proxy_wrapper';
export { ERC721ProxyWrapper } from './contract_wrappers/erc721_proxy_wrapper';
+export { ForwarderWrapper } from './contract_wrappers/forwarder_wrapper';
export {
ContractWrappersError,
diff --git a/packages/contract-wrappers/src/types.ts b/packages/contract-wrappers/src/types.ts
index f9d7a6b9f..2b3cdc591 100644
--- a/packages/contract-wrappers/src/types.ts
+++ b/packages/contract-wrappers/src/types.ts
@@ -109,6 +109,7 @@ export type SyncMethod = (...args: any[]) => any;
* zrxContractAddress: The address of the ZRX contract to use
* erc20ProxyContractAddress: The address of the erc20 token transfer proxy contract to use
* erc721ProxyContractAddress: The address of the erc721 token transfer proxy contract to use
+ * forwarderContractAddress: The address of the forwarder contract to use
* orderWatcherConfig: All the configs related to the orderWatcher
* blockPollingIntervalMs: The interval to use for block polling in event watching methods (defaults to 1000)
*/
@@ -119,6 +120,7 @@ export interface ContractWrappersConfig {
zrxContractAddress?: string;
erc20ProxyContractAddress?: string;
erc721ProxyContractAddress?: string;
+ forwarderContractAddress?: string;
blockPollingIntervalMs?: number;
}
@@ -172,13 +174,13 @@ export enum TransferType {
export type OnOrderStateChangeCallback = (err: Error | null, orderState?: OrderState) => void;
export interface OrderInfo {
- orderStatus: number;
+ orderStatus: OrderStatus;
orderHash: string;
orderTakerAssetFilledAmount: BigNumber;
}
export enum OrderStatus {
- INVALID,
+ INVALID = 0,
INVALID_MAKER_ASSET_AMOUNT,
INVALID_TAKER_ASSET_AMOUNT,
FILLABLE,
diff --git a/packages/contract-wrappers/src/utils/assert.ts b/packages/contract-wrappers/src/utils/assert.ts
index 842b16fa0..183642170 100644
--- a/packages/contract-wrappers/src/utils/assert.ts
+++ b/packages/contract-wrappers/src/utils/assert.ts
@@ -1,11 +1,14 @@
import { assert as sharedAssert } from '@0xproject/assert';
// HACK: We need those two unused imports because they're actually used by sharedAssert which gets injected here
import { Schema } from '@0xproject/json-schemas'; // tslint:disable-line:no-unused-variable
-import { isValidSignatureAsync } from '@0xproject/order-utils';
-import { ECSignature } from '@0xproject/types'; // tslint:disable-line:no-unused-variable
+import { assetDataUtils, isValidSignatureAsync } from '@0xproject/order-utils';
+import { ECSignature, Order } from '@0xproject/types'; // tslint:disable-line:no-unused-variable
import { BigNumber } from '@0xproject/utils'; // tslint:disable-line:no-unused-variable
import { Web3Wrapper } from '@0xproject/web3-wrapper';
import { Provider } from 'ethereum-types';
+import * as _ from 'lodash';
+
+import { constants } from './constants';
export const assert = {
...sharedAssert,
@@ -16,12 +19,12 @@ export const assert = {
signerAddress: string,
): Promise<void> {
const isValid = await isValidSignatureAsync(provider, orderHash, signature, signerAddress);
- this.assert(isValid, `Expected order with hash '${orderHash}' to have a valid signature`);
+ sharedAssert.assert(isValid, `Expected order with hash '${orderHash}' to have a valid signature`);
},
isValidSubscriptionToken(variableName: string, subscriptionToken: string): void {
const uuidRegex = new RegExp('^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$');
const isValid = uuidRegex.test(subscriptionToken);
- this.assert(isValid, `Expected ${variableName} to be a valid subscription token`);
+ sharedAssert.assert(isValid, `Expected ${variableName} to be a valid subscription token`);
},
async isSenderAddressAsync(
variableName: string,
@@ -35,4 +38,53 @@ export const assert = {
`Specified ${variableName} ${senderAddressHex} isn't available through the supplied web3 provider`,
);
},
+ ordersCanBeUsedForForwarderContract(orders: Order[], etherTokenAddress: string): void {
+ sharedAssert.assert(!_.isEmpty(orders), 'Expected at least 1 signed order. Found no orders');
+ assert.ordersHaveAtMostOneUniqueValueForProperty(orders, 'makerAssetData');
+ assert.allTakerAssetDatasAreErc20Token(orders, etherTokenAddress);
+ assert.allTakerAddressesAreNull(orders);
+ },
+ feeOrdersCanBeUsedForForwarderContract(orders: Order[], zrxTokenAddress: string, etherTokenAddress: string): void {
+ if (!_.isEmpty(orders)) {
+ assert.allMakerAssetDatasAreErc20Token(orders, zrxTokenAddress);
+ assert.allTakerAssetDatasAreErc20Token(orders, etherTokenAddress);
+ }
+ },
+ allTakerAddressesAreNull(orders: Order[]): void {
+ assert.ordersHaveAtMostOneUniqueValueForProperty(orders, 'takerAddress', constants.NULL_ADDRESS);
+ },
+ allMakerAssetDatasAreErc20Token(orders: Order[], tokenAddress: string): void {
+ assert.ordersHaveAtMostOneUniqueValueForProperty(
+ orders,
+ 'makerAssetData',
+ assetDataUtils.encodeERC20AssetData(tokenAddress),
+ );
+ },
+ allTakerAssetDatasAreErc20Token(orders: Order[], tokenAddress: string): void {
+ assert.ordersHaveAtMostOneUniqueValueForProperty(
+ orders,
+ 'takerAssetData',
+ assetDataUtils.encodeERC20AssetData(tokenAddress),
+ );
+ },
+ /*
+ * Asserts that all the orders have the same value for the provided propertyName
+ * If the value parameter is provided, this asserts that all orders have the prope
+ */
+ ordersHaveAtMostOneUniqueValueForProperty(orders: Order[], propertyName: string, value?: any): void {
+ const allValues = _.map(orders, order => _.get(order, propertyName));
+ sharedAssert.hasAtMostOneUniqueValue(
+ allValues,
+ `Expected all orders to have the same ${propertyName} field. Found the following ${propertyName} values: ${JSON.stringify(
+ allValues,
+ )}`,
+ );
+ if (!_.isUndefined(value)) {
+ const firstValue = _.head(allValues);
+ sharedAssert.assert(
+ firstValue === value,
+ `Expected all orders to have a ${propertyName} field with value: ${value}. Found: ${firstValue}`,
+ );
+ }
+ },
};
diff --git a/packages/contract-wrappers/src/utils/constants.ts b/packages/contract-wrappers/src/utils/constants.ts
index 039475b7f..d436efefc 100644
--- a/packages/contract-wrappers/src/utils/constants.ts
+++ b/packages/contract-wrappers/src/utils/constants.ts
@@ -10,4 +10,5 @@ export const constants = {
// tslint:disable-next-line:custom-no-magic-numbers
UNLIMITED_ALLOWANCE_IN_BASE_UNITS: new BigNumber(2).pow(256).minus(1),
DEFAULT_BLOCK_POLLING_INTERVAL: 1000,
+ ZERO_AMOUNT: new BigNumber(0),
};
diff --git a/packages/contract-wrappers/test/exchange_wrapper_test.ts b/packages/contract-wrappers/test/exchange_wrapper_test.ts
index dca212f65..fa3b49eb9 100644
--- a/packages/contract-wrappers/test/exchange_wrapper_test.ts
+++ b/packages/contract-wrappers/test/exchange_wrapper_test.ts
@@ -277,6 +277,15 @@ describe('ExchangeWrapper', () => {
expect(orderInfo.orderHash).to.be.equal(orderHash);
});
});
+ describe('#getOrdersInfoAsync', () => {
+ it('should get the orders info', async () => {
+ const ordersInfo = await contractWrappers.exchange.getOrdersInfoAsync([signedOrder, anotherSignedOrder]);
+ const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
+ expect(ordersInfo[0].orderHash).to.be.equal(orderHash);
+ const anotherOrderHash = orderHashUtils.getOrderHashHex(anotherSignedOrder);
+ expect(ordersInfo[1].orderHash).to.be.equal(anotherOrderHash);
+ });
+ });
describe('#isValidSignature', () => {
it('should check if the signature is valid', async () => {
const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
@@ -295,7 +304,7 @@ describe('ExchangeWrapper', () => {
});
});
describe('#isAllowedValidatorAsync', () => {
- it('should check if the validator is alllowed', async () => {
+ it('should check if the validator is allowed', async () => {
const signerAddress = makerAddress;
const validatorAddress = constants.NULL_ADDRESS;
const isAllowed = await contractWrappers.exchange.isAllowedValidatorAsync(signerAddress, validatorAddress);
diff --git a/packages/contract-wrappers/test/forwarder_wrapper_test.ts b/packages/contract-wrappers/test/forwarder_wrapper_test.ts
new file mode 100644
index 000000000..3f3b40e0b
--- /dev/null
+++ b/packages/contract-wrappers/test/forwarder_wrapper_test.ts
@@ -0,0 +1,130 @@
+import { BlockchainLifecycle, callbackErrorReporter } from '@0xproject/dev-utils';
+import { FillScenarios } from '@0xproject/fill-scenarios';
+import { assetDataUtils, orderHashUtils } from '@0xproject/order-utils';
+import { DoneCallback, SignedOrder } from '@0xproject/types';
+import { BigNumber } from '@0xproject/utils';
+import * as chai from 'chai';
+import { BlockParamLiteral } from 'ethereum-types';
+import 'mocha';
+
+import {
+ ContractWrappers,
+ DecodedLogEvent,
+ ExchangeCancelEventArgs,
+ ExchangeEvents,
+ ExchangeFillEventArgs,
+ OrderStatus,
+} from '../src';
+
+import { chaiSetup } from './utils/chai_setup';
+import { constants } from './utils/constants';
+import { tokenUtils } from './utils/token_utils';
+import { provider, web3Wrapper } from './utils/web3_wrapper';
+
+chaiSetup.configure();
+const expect = chai.expect;
+const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
+
+describe('ForwarderWrapper', () => {
+ const contractWrappersConfig = {
+ networkId: constants.TESTRPC_NETWORK_ID,
+ blockPollingIntervalMs: 0,
+ };
+ const fillableAmount = new BigNumber(5);
+ const takerTokenFillAmount = new BigNumber(5);
+ let contractWrappers: ContractWrappers;
+ let fillScenarios: FillScenarios;
+ let forwarderContractAddress: string;
+ let exchangeContractAddress: string;
+ let zrxTokenAddress: string;
+ let userAddresses: string[];
+ let coinbase: string;
+ let makerAddress: string;
+ let takerAddress: string;
+ let feeRecipient: string;
+ let anotherMakerAddress: string;
+ let makerTokenAddress: string;
+ let takerTokenAddress: string;
+ let makerAssetData: string;
+ let takerAssetData: string;
+ let signedOrder: SignedOrder;
+ let anotherSignedOrder: SignedOrder;
+ before(async () => {
+ await blockchainLifecycle.startAsync();
+ contractWrappers = new ContractWrappers(provider, contractWrappersConfig);
+ forwarderContractAddress = contractWrappers.forwarder.getContractAddress();
+ exchangeContractAddress = contractWrappers.exchange.getContractAddress();
+ userAddresses = await web3Wrapper.getAvailableAddressesAsync();
+ zrxTokenAddress = tokenUtils.getProtocolTokenAddress();
+ fillScenarios = new FillScenarios(
+ provider,
+ userAddresses,
+ zrxTokenAddress,
+ exchangeContractAddress,
+ contractWrappers.erc20Proxy.getContractAddress(),
+ contractWrappers.erc721Proxy.getContractAddress(),
+ );
+ [coinbase, makerAddress, takerAddress, feeRecipient, anotherMakerAddress] = userAddresses;
+ [makerTokenAddress] = tokenUtils.getDummyERC20TokenAddresses();
+ takerTokenAddress = tokenUtils.getWethTokenAddress();
+ [makerAssetData, takerAssetData] = [
+ assetDataUtils.encodeERC20AssetData(makerTokenAddress),
+ assetDataUtils.encodeERC20AssetData(takerTokenAddress),
+ ];
+ signedOrder = await fillScenarios.createFillableSignedOrderAsync(
+ makerAssetData,
+ takerAssetData,
+ makerAddress,
+ constants.NULL_ADDRESS,
+ fillableAmount,
+ );
+ anotherSignedOrder = await fillScenarios.createFillableSignedOrderAsync(
+ makerAssetData,
+ takerAssetData,
+ makerAddress,
+ constants.NULL_ADDRESS,
+ fillableAmount,
+ );
+ });
+ after(async () => {
+ await blockchainLifecycle.revertAsync();
+ });
+ beforeEach(async () => {
+ await blockchainLifecycle.startAsync();
+ });
+ afterEach(async () => {
+ await blockchainLifecycle.revertAsync();
+ });
+ describe('#marketBuyOrdersWithEthAsync', () => {
+ it('should market buy orders with eth', async () => {
+ const signedOrders = [signedOrder, anotherSignedOrder];
+ const makerAssetFillAmount = signedOrder.makerAssetAmount.plus(anotherSignedOrder.makerAssetAmount);
+ const txHash = await contractWrappers.forwarder.marketBuyOrdersWithEthAsync(
+ signedOrders,
+ makerAssetFillAmount,
+ takerAddress,
+ makerAssetFillAmount,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(txHash, constants.AWAIT_TRANSACTION_MINED_MS);
+ const ordersInfo = await contractWrappers.exchange.getOrdersInfoAsync([signedOrder, anotherSignedOrder]);
+ expect(ordersInfo[0].orderStatus).to.be.equal(OrderStatus.FULLY_FILLED);
+ expect(ordersInfo[1].orderStatus).to.be.equal(OrderStatus.FULLY_FILLED);
+ });
+ });
+ describe('#marketSellOrdersWithEthAsync', () => {
+ it('should market sell orders with eth', async () => {
+ const signedOrders = [signedOrder, anotherSignedOrder];
+ const makerAssetFillAmount = signedOrder.makerAssetAmount.plus(anotherSignedOrder.makerAssetAmount);
+ const txHash = await contractWrappers.forwarder.marketSellOrdersWithEthAsync(
+ signedOrders,
+ takerAddress,
+ makerAssetFillAmount,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(txHash, constants.AWAIT_TRANSACTION_MINED_MS);
+ const ordersInfo = await contractWrappers.exchange.getOrdersInfoAsync([signedOrder, anotherSignedOrder]);
+ expect(ordersInfo[0].orderStatus).to.be.equal(OrderStatus.FULLY_FILLED);
+ expect(ordersInfo[1].orderStatus).to.be.equal(OrderStatus.FILLABLE);
+ expect(ordersInfo[1].orderTakerAssetFilledAmount).to.be.bignumber.equal(new BigNumber(4)); // only 95% of ETH is sold
+ });
+ });
+});