aboutsummaryrefslogtreecommitdiffstats
path: root/packages/contract-wrappers
diff options
context:
space:
mode:
authorFabio Berger <me@fabioberger.com>2018-08-14 04:01:32 +0800
committerFabio Berger <me@fabioberger.com>2018-08-14 04:01:32 +0800
commit9d3c287918389d07f884245bd1bc968955768b6f (patch)
tree460fded537c7d64154972b7d14332f88554d14c0 /packages/contract-wrappers
parentc2b5fe3d844d35966c5498326000bd8317fb547c (diff)
parent15e15f994a1b18cf2e9be151194c826d53a01601 (diff)
downloaddexon-sol-tools-9d3c287918389d07f884245bd1bc968955768b6f.tar
dexon-sol-tools-9d3c287918389d07f884245bd1bc968955768b6f.tar.gz
dexon-sol-tools-9d3c287918389d07f884245bd1bc968955768b6f.tar.bz2
dexon-sol-tools-9d3c287918389d07f884245bd1bc968955768b6f.tar.lz
dexon-sol-tools-9d3c287918389d07f884245bd1bc968955768b6f.tar.xz
dexon-sol-tools-9d3c287918389d07f884245bd1bc968955768b6f.tar.zst
dexon-sol-tools-9d3c287918389d07f884245bd1bc968955768b6f.zip
Merge branch 'sol-cov-fixes' of github.com:0xProject/0x-monorepo into sol-cov-fixes
* 'sol-cov-fixes' of github.com:0xProject/0x-monorepo: (49 commits) Add @return comments Import marshaller directly Update comment about ethers checksummed address behavior Add packages/coverage/.gitkeep file Update CI config and package.json to run @0xproject/utils tests on CI Update remaining CHANGELOG.json files Change amir picture Update CHANGELOG.json for contract-wrappers Update ethers typings for TypeScript 2.9.2 Update CHANGELOG.json for base-contract Move some ethers-related types to typescript-typings/ethers Apply prettier Add strictArgumentEncodingCheck to BaseContract and use it in contract templates fix(monorepo-scripts): Fix typo in git tag command feat(monorepo-scripts): Add confirmation prompt before publishing fix comments and styling for MixinSignatureValidator Update TypeScript to version 2.9.2 Use asm for hashEIP712Message, increment free memory pointer after asm hashing functions Fix comments, styling, and optimize hashOrder Remove assertion comments ...
Diffstat (limited to 'packages/contract-wrappers')
-rw-r--r--packages/contract-wrappers/CHANGELOG.json17
-rw-r--r--packages/contract-wrappers/package.json6
-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.ts220
-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/calldata_optimization_utils.ts44
-rw-r--r--packages/contract-wrappers/src/utils/constants.ts2
-rw-r--r--packages/contract-wrappers/test/calldata_optimization_utils_test.ts60
-rw-r--r--packages/contract-wrappers/test/exchange_wrapper_test.ts11
-rw-r--r--packages/contract-wrappers/test/forwarder_wrapper_test.ts130
14 files changed, 582 insertions, 11 deletions
diff --git a/packages/contract-wrappers/CHANGELOG.json b/packages/contract-wrappers/CHANGELOG.json
index d9eef089f..fb077ce73 100644
--- a/packages/contract-wrappers/CHANGELOG.json
+++ b/packages/contract-wrappers/CHANGELOG.json
@@ -1,5 +1,22 @@
[
{
+ "version": "1.0.1-rc.3",
+ "changes": [
+ {
+ "pr": 915,
+ "note": "Added strict encoding/decoding checks for sendTransaction and call"
+ },
+ {
+ "note": "Add ForwarderWrapper",
+ "pr": 934
+ },
+ {
+ "note": "Optimize orders in ForwarderWrapper",
+ "pr": 936
+ }
+ ]
+ },
+ {
"version": "1.0.1-rc.2",
"changes": [
{
diff --git a/packages/contract-wrappers/package.json b/packages/contract-wrappers/package.json
index ed0278caa..64de5b0c2 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": {
@@ -68,7 +68,7 @@
"sinon": "^4.0.0",
"source-map-support": "^0.5.0",
"tslint": "5.11.0",
- "typescript": "2.7.1",
+ "typescript": "2.9.2",
"web3-provider-engine": "14.0.6"
},
"dependencies": {
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..13ef0fe01
--- /dev/null
+++ b/packages/contract-wrappers/src/contract_wrappers/forwarder_wrapper.ts
@@ -0,0 +1,220 @@
+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 { calldataOptimizationUtils } from '../utils/calldata_optimization_utils';
+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(),
+ );
+ // lowercase input addresses
+ const normalizedTakerAddress = takerAddress.toLowerCase();
+ const normalizedFeeRecipientAddress = feeRecipientAddress.toLowerCase();
+ // optimize orders
+ const optimizedMarketOrders = calldataOptimizationUtils.optimizeForwarderOrders(signedOrders);
+ const optimizedFeeOrders = calldataOptimizationUtils.optimizeForwarderFeeOrders(signedFeeOrders);
+ // send transaction
+ const forwarderContractInstance = await this._getForwarderContractAsync();
+ const txHash = await forwarderContractInstance.marketSellOrdersWithEth.sendTransactionAsync(
+ optimizedMarketOrders,
+ _.map(optimizedMarketOrders, order => order.signature),
+ optimizedFeeOrders,
+ _.map(optimizedFeeOrders, 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(),
+ );
+ // lowercase input addresses
+ const normalizedTakerAddress = takerAddress.toLowerCase();
+ const normalizedFeeRecipientAddress = feeRecipientAddress.toLowerCase();
+ // optimize orders
+ const optimizedMarketOrders = calldataOptimizationUtils.optimizeForwarderOrders(signedOrders);
+ const optimizedFeeOrders = calldataOptimizationUtils.optimizeForwarderFeeOrders(signedFeeOrders);
+ // send transaction
+ const forwarderContractInstance = await this._getForwarderContractAsync();
+ const txHash = await forwarderContractInstance.marketBuyOrdersWithEth.sendTransactionAsync(
+ optimizedMarketOrders,
+ makerAssetFillAmount,
+ _.map(optimizedMarketOrders, order => order.signature),
+ optimizedFeeOrders,
+ _.map(optimizedFeeOrders, 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/calldata_optimization_utils.ts b/packages/contract-wrappers/src/utils/calldata_optimization_utils.ts
new file mode 100644
index 000000000..3172cf531
--- /dev/null
+++ b/packages/contract-wrappers/src/utils/calldata_optimization_utils.ts
@@ -0,0 +1,44 @@
+import { SignedOrder } from '@0xproject/types';
+import * as _ from 'lodash';
+
+import { constants } from './constants';
+
+export const calldataOptimizationUtils = {
+ /**
+ * Takes an array of orders and outputs an array of equivalent orders where all takerAssetData are '0x' and
+ * all makerAssetData are '0x' except for that of the first order, which retains its original value
+ * @param orders An array of SignedOrder objects
+ * @returns optimized orders
+ */
+ optimizeForwarderOrders(orders: SignedOrder[]): SignedOrder[] {
+ const optimizedOrders = _.map(orders, (order, index) =>
+ transformOrder(order, {
+ makerAssetData: index === 0 ? order.makerAssetData : constants.NULL_BYTES,
+ takerAssetData: constants.NULL_BYTES,
+ }),
+ );
+ return optimizedOrders;
+ },
+ /**
+ * Takes an array of orders and outputs an array of equivalent orders where all takerAssetData are '0x' and
+ * all makerAssetData are '0x'
+ * @param orders An array of SignedOrder objects
+ * @returns optimized orders
+ */
+ optimizeForwarderFeeOrders(orders: SignedOrder[]): SignedOrder[] {
+ const optimizedOrders = _.map(orders, (order, index) =>
+ transformOrder(order, {
+ makerAssetData: constants.NULL_BYTES,
+ takerAssetData: constants.NULL_BYTES,
+ }),
+ );
+ return optimizedOrders;
+ },
+};
+
+const transformOrder = (order: SignedOrder, partialOrder: Partial<SignedOrder>) => {
+ return {
+ ...order,
+ ...partialOrder,
+ };
+};
diff --git a/packages/contract-wrappers/src/utils/constants.ts b/packages/contract-wrappers/src/utils/constants.ts
index 039475b7f..2df11538c 100644
--- a/packages/contract-wrappers/src/utils/constants.ts
+++ b/packages/contract-wrappers/src/utils/constants.ts
@@ -2,6 +2,7 @@ import { BigNumber } from '@0xproject/utils';
export const constants = {
NULL_ADDRESS: '0x0000000000000000000000000000000000000000',
+ NULL_BYTES: '0x',
TESTRPC_NETWORK_ID: 50,
INVALID_JUMP_PATTERN: 'invalid JUMP at',
REVERT: 'revert',
@@ -10,4 +11,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/calldata_optimization_utils_test.ts b/packages/contract-wrappers/test/calldata_optimization_utils_test.ts
new file mode 100644
index 000000000..a4cea772f
--- /dev/null
+++ b/packages/contract-wrappers/test/calldata_optimization_utils_test.ts
@@ -0,0 +1,60 @@
+import { orderFactory } from '@0xproject/order-utils';
+import * as chai from 'chai';
+import * as _ from 'lodash';
+import 'mocha';
+
+import { assert } from '../src/utils/assert';
+import { calldataOptimizationUtils } from '../src/utils/calldata_optimization_utils';
+import { constants } from '../src/utils/constants';
+
+import { chaiSetup } from './utils/chai_setup';
+
+chaiSetup.configure();
+const expect = chai.expect;
+
+// utility for generating a set of order objects with mostly NULL values
+// except for a specified makerAssetData and takerAssetData
+const FAKE_ORDERS_COUNT = 5;
+const generateFakeOrders = (makerAssetData: string, takerAssetData: string) =>
+ _.map(_.range(FAKE_ORDERS_COUNT), index => {
+ const order = orderFactory.createOrder(
+ constants.NULL_ADDRESS,
+ constants.ZERO_AMOUNT,
+ makerAssetData,
+ constants.ZERO_AMOUNT,
+ takerAssetData,
+ constants.NULL_ADDRESS,
+ );
+ return {
+ ...order,
+ signature: 'dummy signature',
+ };
+ });
+
+describe('calldataOptimizationUtils', () => {
+ const fakeMakerAssetData = 'fakeMakerAssetData';
+ const fakeTakerAssetData = 'fakeTakerAssetData';
+ const orders = generateFakeOrders(fakeMakerAssetData, fakeTakerAssetData);
+ describe('#optimizeForwarderOrders', () => {
+ it('should make makerAssetData `0x` unless first order', () => {
+ const optimizedOrders = calldataOptimizationUtils.optimizeForwarderOrders(orders);
+ expect(optimizedOrders[0].makerAssetData).to.equal(fakeMakerAssetData);
+ const ordersWithoutHead = _.slice(optimizedOrders, 1);
+ _.forEach(ordersWithoutHead, order => expect(order.makerAssetData).to.equal(constants.NULL_BYTES));
+ });
+ it('should make all takerAssetData `0x`', () => {
+ const optimizedOrders = calldataOptimizationUtils.optimizeForwarderOrders(orders);
+ _.forEach(optimizedOrders, order => expect(order.takerAssetData).to.equal(constants.NULL_BYTES));
+ });
+ });
+ describe('#optimizeForwarderFeeOrders', () => {
+ it('should make all makerAssetData `0x`', () => {
+ const optimizedOrders = calldataOptimizationUtils.optimizeForwarderFeeOrders(orders);
+ _.forEach(optimizedOrders, order => expect(order.makerAssetData).to.equal(constants.NULL_BYTES));
+ });
+ it('should make all takerAssetData `0x`', () => {
+ const optimizedOrders = calldataOptimizationUtils.optimizeForwarderFeeOrders(orders);
+ _.forEach(optimizedOrders, order => expect(order.takerAssetData).to.equal(constants.NULL_BYTES));
+ });
+ });
+});
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
+ });
+ });
+});