diff options
author | Brandon Millman <brandon@0xproject.com> | 2018-08-02 06:26:45 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-08-02 06:26:45 +0800 |
commit | 2414b47a301238c8c78e859321590fcb42371817 (patch) | |
tree | f26d6ee0f406dddf1df671b2a46a255fa8b78982 | |
parent | 6f0daa54633d4c30096df7c552ceddc3e65fd47d (diff) | |
parent | ca1f926d6d137f9523a9765c047430ec39d45d86 (diff) | |
download | dexon-sol-tools-2414b47a301238c8c78e859321590fcb42371817.tar dexon-sol-tools-2414b47a301238c8c78e859321590fcb42371817.tar.gz dexon-sol-tools-2414b47a301238c8c78e859321590fcb42371817.tar.bz2 dexon-sol-tools-2414b47a301238c8c78e859321590fcb42371817.tar.lz dexon-sol-tools-2414b47a301238c8c78e859321590fcb42371817.tar.xz dexon-sol-tools-2414b47a301238c8c78e859321590fcb42371817.tar.zst dexon-sol-tools-2414b47a301238c8c78e859321590fcb42371817.zip |
Merge pull request #934 from 0xProject/feature/contract-wrappers/forwader
Initial forwarder contract wrapper
-rw-r--r-- | packages/contract-wrappers/package.json | 4 | ||||
-rw-r--r-- | packages/contract-wrappers/src/artifacts.ts | 2 | ||||
-rw-r--r-- | packages/contract-wrappers/src/contract_wrappers.ts | 12 | ||||
-rw-r--r-- | packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts | 22 | ||||
-rw-r--r-- | packages/contract-wrappers/src/contract_wrappers/forwarder_wrapper.ts | 209 | ||||
-rw-r--r-- | packages/contract-wrappers/src/index.ts | 1 | ||||
-rw-r--r-- | packages/contract-wrappers/src/types.ts | 6 | ||||
-rw-r--r-- | packages/contract-wrappers/src/utils/assert.ts | 60 | ||||
-rw-r--r-- | packages/contract-wrappers/src/utils/constants.ts | 1 | ||||
-rw-r--r-- | packages/contract-wrappers/test/exchange_wrapper_test.ts | 11 | ||||
-rw-r--r-- | packages/contract-wrappers/test/forwarder_wrapper_test.ts | 130 |
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 + }); + }); +}); |