From 1ad395cf86b2006c09bdae814607c2baf9790b91 Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Mon, 4 Sep 2017 18:14:48 +0200 Subject: Make the functions immidiately return txHash instead of awaiting for a transaction to be mined --- package.json | 2 +- src/0x.ts | 1 - src/artifacts/EtherToken.json | 2 +- src/contract.ts | 28 +++++--- src/contract_wrappers/contract_wrapper.ts | 47 ++----------- src/contract_wrappers/ether_token_wrapper.ts | 4 +- src/contract_wrappers/exchange_wrapper.ts | 76 ++++++++++------------ src/contract_wrappers/token_registry_wrapper.ts | 4 +- .../token_transfer_proxy_wrapper.ts | 4 +- src/contract_wrappers/token_wrapper.ts | 4 +- src/globals.d.ts | 20 ++---- src/types.ts | 29 +++------ src/web3_wrapper.ts | 28 ++++++++ test/exchange_wrapper_test.ts | 64 +++++------------- test/utils/blockchain_lifecycle.ts | 3 + yarn.lock | 6 +- 16 files changed, 139 insertions(+), 183 deletions(-) diff --git a/package.json b/package.json index 1167b0ca1..909769220 100644 --- a/package.json +++ b/package.json @@ -86,7 +86,7 @@ "types-ethereumjs-util": "^0.0.5", "typescript": "^2.4.1", "web3-provider-engine": "^13.0.1", - "web3-typescript-typings": "^0.3.2", + "web3-typescript-typings": "^0.4.1", "web3_beta": "ethereum/web3.js#1.0", "webpack": "^3.1.0" }, diff --git a/src/0x.ts b/src/0x.ts index ba222f2c9..3cf672a1b 100644 --- a/src/0x.ts +++ b/src/0x.ts @@ -3,7 +3,6 @@ import * as BigNumber from 'bignumber.js'; import {SchemaValidator, schemas} from '0x-json-schemas'; import {bigNumberConfigs} from './bignumber_config'; import * as ethUtil from 'ethereumjs-util'; -import contract = require('truffle-contract'); import findVersions = require('find-versions'); import compareVersions = require('compare-versions'); import {Web3Wrapper} from './web3_wrapper'; diff --git a/src/artifacts/EtherToken.json b/src/artifacts/EtherToken.json index eca348530..54b5a032e 100644 --- a/src/artifacts/EtherToken.json +++ b/src/artifacts/EtherToken.json @@ -391,4 +391,4 @@ }, "schema_version": "0.0.5", "updated_at": 1503318938233 -} \ No newline at end of file +} diff --git a/src/contract.ts b/src/contract.ts index b4a54ca09..0c76571cc 100644 --- a/src/contract.ts +++ b/src/contract.ts @@ -2,27 +2,37 @@ import * as Web3 from 'web3'; import * as _ from 'lodash'; import promisify = require('es6-promisify'); -export class Contract implements Web3.ContractInstance { +export class Contract implements Web3.ContractInstance { public address: string; public abi: Web3.ContractAbi; - private contract: A; + private contract: Web3.ContractInstance; [name: string]: any; - constructor(web3ContractInstance: A) { + constructor(web3ContractInstance: Web3.ContractInstance) { this.contract = web3ContractInstance; this.address = web3ContractInstance.address; this.abi = web3ContractInstance.abi; + this.populateEvents(); + this.populateFunctions(); + } + private populateFunctions(): void { const functionsAbi = _.filter(this.abi, abiPart => abiPart.type === 'function'); _.forEach(functionsAbi, (functionAbi: Web3.MethodAbi) => { - const cbStyleFunction = web3ContractInstance[functionAbi.name]; - this[functionAbi.name] = promisify(cbStyleFunction, web3ContractInstance); + const cbStyleFunction = this.contract[functionAbi.name]; + this[functionAbi.name] = promisify(cbStyleFunction, this.contract); if (functionAbi.constant) { - const cbStyleCallFunction = web3ContractInstance[functionAbi.name].call; - this[functionAbi.name].call = promisify(cbStyleCallFunction, web3ContractInstance); + const cbStyleCallFunction = this.contract[functionAbi.name].call; + this[functionAbi.name].call = promisify(cbStyleCallFunction, this.contract); } else { - const cbStyleEstimateGasFunction = web3ContractInstance[functionAbi.name].estimateGas; + const cbStyleEstimateGasFunction = this.contract[functionAbi.name].estimateGas; this[functionAbi.name].estimateGas = - promisify(cbStyleEstimateGasFunction, web3ContractInstance); + promisify(cbStyleEstimateGasFunction, this.contract); } }); } + private populateEvents(): void { + const eventsAbi = _.filter(this.abi, abiPart => abiPart.type === 'event'); + _.forEach(eventsAbi, (eventAbi: Web3.EventAbi) => { + this[eventAbi.name] = this.contract[eventAbi.name]; + }); + } } diff --git a/src/contract_wrappers/contract_wrapper.ts b/src/contract_wrappers/contract_wrapper.ts index 28df82cee..3de26148f 100644 --- a/src/contract_wrappers/contract_wrapper.ts +++ b/src/contract_wrappers/contract_wrapper.ts @@ -1,7 +1,7 @@ import * as _ from 'lodash'; -import contract = require('truffle-contract'); +import * as Web3 from 'web3'; import {Web3Wrapper} from '../web3_wrapper'; -import {ZeroExError, Artifact, ContractInstance} from '../types'; +import {ZeroExError} from '../types'; import {utils} from '../utils/utils'; export class ContractWrapper { @@ -11,43 +11,10 @@ export class ContractWrapper { this._web3Wrapper = web3Wrapper; this._gasPrice = gasPrice; } - protected async _instantiateContractIfExistsAsync(artifact: Artifact, address?: string): Promise { - const c = await contract(artifact); - c.defaults({ - gasPrice: this._gasPrice, - }); - const providerObj = this._web3Wrapper.getCurrentProvider(); - c.setProvider(providerObj); - - const networkIdIfExists = await this._web3Wrapper.getNetworkIdIfExistsAsync(); - const artifactNetworkConfigs = _.isUndefined(networkIdIfExists) ? - undefined : - artifact.networks[networkIdIfExists]; - let contractAddress; - if (!_.isUndefined(address)) { - contractAddress = address; - } else if (!_.isUndefined(artifactNetworkConfigs)) { - contractAddress = artifactNetworkConfigs.address.toLowerCase(); - } - - if (!_.isUndefined(contractAddress)) { - const doesContractExist = await this._web3Wrapper.doesContractExistAtAddressAsync(contractAddress); - if (!doesContractExist) { - throw new Error(ZeroExError.ContractDoesNotExist); - } - } - - try { - const contractInstance = _.isUndefined(address) ? await c.deployed() : await c.at(address); - return contractInstance; - } catch (err) { - const errMsg = `${err}`; - if (_.includes(errMsg, 'not been deployed to detected network')) { - throw new Error(ZeroExError.ContractDoesNotExist); - } else { - utils.consoleLog(`Notice: Error encountered: ${err} ${err.stack}`); - throw new Error(ZeroExError.UnhandledError); - } - } + protected async _instantiateContractIfExistsAsync(artifact: Artifact, + address?: string): Promise { + const contractInstance = + await this._web3Wrapper.getContractInstanceFromArtifactAsync(artifact, address); + return contractInstance; } } diff --git a/src/contract_wrappers/ether_token_wrapper.ts b/src/contract_wrappers/ether_token_wrapper.ts index 3c282510f..a2486b15e 100644 --- a/src/contract_wrappers/ether_token_wrapper.ts +++ b/src/contract_wrappers/ether_token_wrapper.ts @@ -71,7 +71,9 @@ export class EtherTokenWrapper extends ContractWrapper { if (!_.isUndefined(this._etherTokenContractIfExists)) { return this._etherTokenContractIfExists; } - const contractInstance = await this._instantiateContractIfExistsAsync((EtherTokenArtifacts as any)); + const contractInstance = await this._instantiateContractIfExistsAsync( + EtherTokenArtifacts as any as Artifact, + ); this._etherTokenContractIfExists = contractInstance as EtherTokenContract; return this._etherTokenContractIfExists; } diff --git a/src/contract_wrappers/exchange_wrapper.ts b/src/contract_wrappers/exchange_wrapper.ts index d09df236b..1196394e9 100644 --- a/src/contract_wrappers/exchange_wrapper.ts +++ b/src/contract_wrappers/exchange_wrapper.ts @@ -141,12 +141,12 @@ export class ExchangeWrapper extends ContractWrapper { * @param takerAddress The user Ethereum address who would like to fill this order. * Must be available via the supplied Web3.Provider * passed to 0x.js. - * @return The amount of the order that was filled (in taker token baseUnits). + * @return Transaction hash. */ @decorators.contractCallErrorHandler public async fillOrderAsync(signedOrder: SignedOrder, fillTakerTokenAmount: BigNumber.BigNumber, shouldThrowOnInsufficientBalanceOrAllowance: boolean, - takerAddress: string): Promise { + takerAddress: string): Promise { assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema); assert.isBigNumber('fillTakerTokenAmount', fillTakerTokenAmount); assert.isBoolean('shouldThrowOnInsufficientBalanceOrAllowance', shouldThrowOnInsufficientBalanceOrAllowance); @@ -169,7 +169,7 @@ export class ExchangeWrapper extends ContractWrapper { from: takerAddress, }, ); - const response: ContractResponse = await exchangeInstance.fillOrder( + const txHash: string = await exchangeInstance.fillOrder( orderAddresses, orderValues, fillTakerTokenAmount, @@ -182,10 +182,7 @@ export class ExchangeWrapper extends ContractWrapper { gas, }, ); - this._throwErrorLogsAsErrors(response.logs); - const logFillArgs = response.logs[0].args as LogFillContractEventArgs; - const filledTakerTokenAmount = new BigNumber(logFillArgs.filledTakerTokenAmount); - return filledTakerTokenAmount; + return txHash; } /** * Sequentially and atomically fills signedOrders up to the specified takerTokenFillAmount. @@ -201,12 +198,12 @@ export class ExchangeWrapper extends ContractWrapper { * @param takerAddress The user Ethereum address who would like to fill these * orders. Must be available via the supplied Web3.Provider * passed to 0x.js. - * @return The amount of the orders that was filled (in taker token baseUnits). + * @return Transaction hash. */ @decorators.contractCallErrorHandler public async fillOrdersUpToAsync(signedOrders: SignedOrder[], fillTakerTokenAmount: BigNumber.BigNumber, shouldThrowOnInsufficientBalanceOrAllowance: boolean, - takerAddress: string): Promise { + takerAddress: string): Promise { assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema); const takerTokenAddresses = _.map(signedOrders, signedOrder => signedOrder.takerTokenAddress); assert.hasAtMostOneUniqueValue(takerTokenAddresses, @@ -222,7 +219,7 @@ export class ExchangeWrapper extends ContractWrapper { signedOrder, fillTakerTokenAmount, takerAddress); } if (_.isEmpty(signedOrders)) { - return new BigNumber(0); // no-op + throw new Error(ExchangeContractErrs.BatchOrdersMustHaveAtLeastOneItem); } const orderAddressesValuesAndSignatureArray = _.map(signedOrders, signedOrder => { @@ -251,7 +248,7 @@ export class ExchangeWrapper extends ContractWrapper { from: takerAddress, }, ); - const response: ContractResponse = await exchangeInstance.fillOrdersUpTo( + const txHash = await exchangeInstance.fillOrdersUpTo( orderAddressesArray, orderValuesArray, fillTakerTokenAmount, @@ -264,13 +261,7 @@ export class ExchangeWrapper extends ContractWrapper { gas, }, ); - this._throwErrorLogsAsErrors(response.logs); - let filledTakerTokenAmount = new BigNumber(0); - _.each(response.logs, log => { - filledTakerTokenAmount = filledTakerTokenAmount.plus( - (log.args as LogFillContractEventArgs).filledTakerTokenAmount); - }); - return filledTakerTokenAmount; + return txHash; } /** * Batch version of fillOrderAsync. @@ -288,11 +279,12 @@ export class ExchangeWrapper extends ContractWrapper { * @param takerAddress The user Ethereum address who would like to fill * these orders. Must be available via the supplied * Web3.Provider passed to 0x.js. + * @return Transaction hash. */ @decorators.contractCallErrorHandler public async batchFillOrdersAsync(orderFillRequests: OrderFillRequest[], shouldThrowOnInsufficientBalanceOrAllowance: boolean, - takerAddress: string): Promise { + takerAddress: string): Promise { assert.doesConformToSchema('orderFillRequests', orderFillRequests, schemas.orderFillRequestsSchema); const exchangeContractAddresses = _.map( orderFillRequests, @@ -307,7 +299,7 @@ export class ExchangeWrapper extends ContractWrapper { orderFillRequest.signedOrder, orderFillRequest.takerTokenFillAmount, takerAddress); } if (_.isEmpty(orderFillRequests)) { - return; // no-op + throw new Error(ExchangeContractErrs.BatchOrdersMustHaveAtLeastOneItem); } const orderAddressesValuesAmountsAndSignatureArray = _.map(orderFillRequests, orderFillRequest => { @@ -337,7 +329,7 @@ export class ExchangeWrapper extends ContractWrapper { from: takerAddress, }, ); - const response: ContractResponse = await exchangeInstance.batchFillOrders( + const txHash = await exchangeInstance.batchFillOrders( orderAddressesArray, orderValuesArray, fillTakerTokenAmounts, @@ -350,7 +342,7 @@ export class ExchangeWrapper extends ContractWrapper { gas, }, ); - this._throwErrorLogsAsErrors(response.logs); + return txHash; } /** * Attempts to fill a specific amount of an order. If the entire amount specified cannot be filled, @@ -360,10 +352,11 @@ export class ExchangeWrapper extends ContractWrapper { * @param fillTakerTokenAmount The total amount of the takerTokens you would like to fill. * @param takerAddress The user Ethereum address who would like to fill this order. * Must be available via the supplied Web3.Provider passed to 0x.js. + * @return Transaction hash. */ @decorators.contractCallErrorHandler public async fillOrKillOrderAsync(signedOrder: SignedOrder, fillTakerTokenAmount: BigNumber.BigNumber, - takerAddress: string): Promise { + takerAddress: string): Promise { assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema); assert.isBigNumber('fillTakerTokenAmount', fillTakerTokenAmount); await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper); @@ -385,7 +378,7 @@ export class ExchangeWrapper extends ContractWrapper { from: takerAddress, }, ); - const response: ContractResponse = await exchangeInstance.fillOrKillOrder( + const txHash = await exchangeInstance.fillOrKillOrder( orderAddresses, orderValues, fillTakerTokenAmount, @@ -397,7 +390,7 @@ export class ExchangeWrapper extends ContractWrapper { gas, }, ); - this._throwErrorLogsAsErrors(response.logs); + return txHash; } /** * Batch version of fillOrKill. Allows a taker to specify a batch of orders that will either be atomically @@ -405,10 +398,11 @@ export class ExchangeWrapper extends ContractWrapper { * @param orderFillOrKillRequests An array of objects that conform to the OrderFillOrKillRequest interface. * @param takerAddress The user Ethereum address who would like to fill there orders. * Must be available via the supplied Web3.Provider passed to 0x.js. + * @return Transaction hash. */ @decorators.contractCallErrorHandler public async batchFillOrKillAsync(orderFillOrKillRequests: OrderFillOrKillRequest[], - takerAddress: string): Promise { + takerAddress: string): Promise { assert.doesConformToSchema('orderFillOrKillRequests', orderFillOrKillRequests, schemas.orderFillOrKillRequestsSchema); const exchangeContractAddresses = _.map( @@ -419,7 +413,7 @@ export class ExchangeWrapper extends ContractWrapper { ExchangeContractErrs.BatchOrdersMustHaveSameExchangeAddress); await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper); if (_.isEmpty(orderFillOrKillRequests)) { - return; // no-op + throw new Error(ExchangeContractErrs.BatchOrdersMustHaveAtLeastOneItem); } const exchangeInstance = await this._getExchangeContractAsync(); for (const request of orderFillOrKillRequests) { @@ -452,7 +446,7 @@ export class ExchangeWrapper extends ContractWrapper { from: takerAddress, }, ); - const response: ContractResponse = await exchangeInstance.batchFillOrKillOrders( + const txHash = await exchangeInstance.batchFillOrKillOrders( orderAddresses, orderValues, fillTakerTokenAmounts, @@ -464,18 +458,18 @@ export class ExchangeWrapper extends ContractWrapper { gas, }, ); - this._throwErrorLogsAsErrors(response.logs); + return txHash; } /** * Cancel a given fill amount of an order. Cancellations are cumulative. * @param order An object that conforms to the Order or SignedOrder interface. * The order you would like to cancel. * @param cancelTakerTokenAmount The amount (specified in taker tokens) that you would like to cancel. - * @return The amount of the order that was cancelled (in taker token baseUnits). + * @return Transaction hash. */ @decorators.contractCallErrorHandler public async cancelOrderAsync( - order: Order|SignedOrder, cancelTakerTokenAmount: BigNumber.BigNumber): Promise { + order: Order|SignedOrder, cancelTakerTokenAmount: BigNumber.BigNumber): Promise { assert.doesConformToSchema('order', order, schemas.orderSchema); assert.isBigNumber('takerTokenCancelAmount', cancelTakerTokenAmount); await assert.isSenderAddressAsync('order.maker', order.maker, this._web3Wrapper); @@ -492,7 +486,7 @@ export class ExchangeWrapper extends ContractWrapper { from: order.maker, }, ); - const response: ContractResponse = await exchangeInstance.cancelOrder( + const txHash = await exchangeInstance.cancelOrder( orderAddresses, orderValues, cancelTakerTokenAmount, @@ -501,19 +495,17 @@ export class ExchangeWrapper extends ContractWrapper { gas, }, ); - this._throwErrorLogsAsErrors(response.logs); - const logFillArgs = response.logs[0].args as LogCancelContractEventArgs; - const cancelledTakerTokenAmount = new BigNumber(logFillArgs.cancelledTakerTokenAmount); - return cancelledTakerTokenAmount; + return txHash; } /** * Batch version of cancelOrderAsync. Atomically cancels multiple orders in a single transaction. * All orders must be from the same maker. * @param orderCancellationRequests An array of objects that conform to the OrderCancellationRequest * interface. + * @return Transaction hash. */ @decorators.contractCallErrorHandler - public async batchCancelOrdersAsync(orderCancellationRequests: OrderCancellationRequest[]): Promise { + public async batchCancelOrdersAsync(orderCancellationRequests: OrderCancellationRequest[]): Promise { assert.doesConformToSchema('orderCancellationRequests', orderCancellationRequests, schemas.orderCancellationRequestsSchema); const exchangeContractAddresses = _.map( @@ -532,7 +524,7 @@ export class ExchangeWrapper extends ContractWrapper { ); } if (_.isEmpty(orderCancellationRequests)) { - return; // no-op + throw new Error(ExchangeContractErrs.BatchOrdersMustHaveAtLeastOneItem); } const exchangeInstance = await this._getExchangeContractAsync(); const orderAddressesValuesAndTakerTokenCancelAmounts = _.map(orderCancellationRequests, cancellationRequest => { @@ -552,7 +544,7 @@ export class ExchangeWrapper extends ContractWrapper { from: maker, }, ); - const response: ContractResponse = await exchangeInstance.batchCancelOrders( + const txHash = await exchangeInstance.batchCancelOrders( orderAddresses, orderValues, cancelTakerTokenAmounts, @@ -561,7 +553,7 @@ export class ExchangeWrapper extends ContractWrapper { gas, }, ); - this._throwErrorLogsAsErrors(response.logs); + return txHash; } /** * Subscribe to an event type emitted by the Exchange smart contract @@ -730,7 +722,9 @@ export class ExchangeWrapper extends ContractWrapper { if (!_.isUndefined(this._exchangeContractIfExists)) { return this._exchangeContractIfExists; } - const contractInstance = await this._instantiateContractIfExistsAsync((ExchangeArtifacts as any)); + const contractInstance = await this._instantiateContractIfExistsAsync( + (ExchangeArtifacts as any), + ); this._exchangeContractIfExists = contractInstance as ExchangeContract; return this._exchangeContractIfExists; } diff --git a/src/contract_wrappers/token_registry_wrapper.ts b/src/contract_wrappers/token_registry_wrapper.ts index 822e69460..1550bfa3e 100644 --- a/src/contract_wrappers/token_registry_wrapper.ts +++ b/src/contract_wrappers/token_registry_wrapper.ts @@ -101,7 +101,9 @@ export class TokenRegistryWrapper extends ContractWrapper { if (!_.isUndefined(this._tokenRegistryContractIfExists)) { return this._tokenRegistryContractIfExists; } - const contractInstance = await this._instantiateContractIfExistsAsync((TokenRegistryArtifacts as any)); + const contractInstance = await this._instantiateContractIfExistsAsync( + TokenRegistryArtifacts as any as Artifact, + ); this._tokenRegistryContractIfExists = contractInstance as TokenRegistryContract; return this._tokenRegistryContractIfExists; } diff --git a/src/contract_wrappers/token_transfer_proxy_wrapper.ts b/src/contract_wrappers/token_transfer_proxy_wrapper.ts index da17d79ff..2b4b32961 100644 --- a/src/contract_wrappers/token_transfer_proxy_wrapper.ts +++ b/src/contract_wrappers/token_transfer_proxy_wrapper.ts @@ -44,7 +44,9 @@ export class TokenTransferProxyWrapper extends ContractWrapper { if (!_.isUndefined(this._tokenTransferProxyContractIfExists)) { return this._tokenTransferProxyContractIfExists; } - const contractInstance = await this._instantiateContractIfExistsAsync((TokenTransferProxyArtifacts as any)); + const contractInstance = await this._instantiateContractIfExistsAsync( + TokenTransferProxyArtifacts as any as Artifact, + ); this._tokenTransferProxyContractIfExists = contractInstance as TokenTransferProxyContract; return this._tokenTransferProxyContractIfExists; } diff --git a/src/contract_wrappers/token_wrapper.ts b/src/contract_wrappers/token_wrapper.ts index f7070f1f4..e98744cbc 100644 --- a/src/contract_wrappers/token_wrapper.ts +++ b/src/contract_wrappers/token_wrapper.ts @@ -267,7 +267,9 @@ export class TokenWrapper extends ContractWrapper { if (!_.isUndefined(tokenContract)) { return tokenContract; } - const contractInstance = await this._instantiateContractIfExistsAsync((TokenArtifacts as any), tokenAddress); + const contractInstance = await this._instantiateContractIfExistsAsync( + TokenArtifacts as any as Artifact, tokenAddress, + ); tokenContract = contractInstance as TokenContract; this._tokenContractsByAddress[tokenAddress] = tokenContract; return tokenContract; diff --git a/src/globals.d.ts b/src/globals.d.ts index 9230ab02d..6f5f13b4e 100644 --- a/src/globals.d.ts +++ b/src/globals.d.ts @@ -33,23 +33,11 @@ declare module '*.json' { /* tslint:enable */ } -// truffle-contract declarations -declare interface ContractInstance { - address: string; -} -declare interface ContractFactory { - setProvider: (providerObj: any) => void; - deployed: () => ContractInstance; - // Both any's are Web3.CallData, but I was unable to import it in this file - defaults: (config: any) => any; - at: (address: string) => ContractInstance; -} declare interface Artifact { - networks: {[networkId: number]: any}; -} -declare function contract(artifacts: Artifact): ContractFactory; -declare module 'truffle-contract' { - export = contract; + abi: any; + networks: {[networkId: number]: { + address: string; + }}; } // find-version declarations diff --git a/src/types.ts b/src/types.ts index 2400a9a60..9f8ba9729 100644 --- a/src/types.ts +++ b/src/types.ts @@ -14,6 +14,7 @@ export enum ZeroExError { InsufficientWEthBalanceForWithdrawal = 'INSUFFICIENT_WETH_BALANCE_FOR_WITHDRAWAL', InvalidJump = 'INVALID_JUMP', OutOfGas = 'OUT_OF_GAS', + NoNetworkId = 'NO_NETWORK_ID', } /** @@ -60,7 +61,7 @@ export interface ExchangeContract extends ContractInstance { fillOrder: { (orderAddresses: OrderAddresses, orderValues: OrderValues, fillTakerTokenAmount: BigNumber.BigNumber, shouldThrowOnInsufficientBalanceOrAllowance: boolean, - v: number, r: string, s: string, txOpts?: TxOpts): Promise; + v: number, r: string, s: string, txOpts?: TxOpts): Promise; estimateGas: (orderAddresses: OrderAddresses, orderValues: OrderValues, fillTakerTokenAmount: BigNumber.BigNumber, shouldThrowOnInsufficientBalanceOrAllowance: boolean, @@ -69,7 +70,7 @@ export interface ExchangeContract extends ContractInstance { batchFillOrders: { (orderAddresses: OrderAddresses[], orderValues: OrderValues[], fillTakerTokenAmounts: BigNumber.BigNumber[], shouldThrowOnInsufficientBalanceOrAllowance: boolean, - v: number[], r: string[], s: string[], txOpts?: TxOpts): Promise; + v: number[], r: string[], s: string[], txOpts?: TxOpts): Promise; estimateGas: (orderAddresses: OrderAddresses[], orderValues: OrderValues[], fillTakerTokenAmounts: BigNumber.BigNumber[], shouldThrowOnInsufficientBalanceOrAllowance: boolean, @@ -78,7 +79,7 @@ export interface ExchangeContract extends ContractInstance { fillOrdersUpTo: { (orderAddresses: OrderAddresses[], orderValues: OrderValues[], fillTakerTokenAmount: BigNumber.BigNumber, shouldThrowOnInsufficientBalanceOrAllowance: boolean, - v: number[], r: string[], s: string[], txOpts?: TxOpts): Promise; + v: number[], r: string[], s: string[], txOpts?: TxOpts): Promise; estimateGas: (orderAddresses: OrderAddresses[], orderValues: OrderValues[], fillTakerTokenAmount: BigNumber.BigNumber, shouldThrowOnInsufficientBalanceOrAllowance: boolean, @@ -86,28 +87,28 @@ export interface ExchangeContract extends ContractInstance { }; cancelOrder: { (orderAddresses: OrderAddresses, orderValues: OrderValues, cancelTakerTokenAmount: BigNumber.BigNumber, - txOpts?: TxOpts): Promise; + txOpts?: TxOpts): Promise; estimateGas: (orderAddresses: OrderAddresses, orderValues: OrderValues, cancelTakerTokenAmount: BigNumber.BigNumber, txOpts?: TxOpts) => Promise; }; batchCancelOrders: { (orderAddresses: OrderAddresses[], orderValues: OrderValues[], cancelTakerTokenAmounts: BigNumber.BigNumber[], - txOpts?: TxOpts): Promise; + txOpts?: TxOpts): Promise; estimateGas: (orderAddresses: OrderAddresses[], orderValues: OrderValues[], cancelTakerTokenAmounts: BigNumber.BigNumber[], txOpts?: TxOpts) => Promise; }; fillOrKillOrder: { (orderAddresses: OrderAddresses, orderValues: OrderValues, fillTakerTokenAmount: BigNumber.BigNumber, - v: number, r: string, s: string, txOpts?: TxOpts): Promise; + v: number, r: string, s: string, txOpts?: TxOpts): Promise; estimateGas: (orderAddresses: OrderAddresses, orderValues: OrderValues, fillTakerTokenAmount: BigNumber.BigNumber, v: number, r: string, s: string, txOpts?: TxOpts) => Promise; }; batchFillOrKillOrders: { (orderAddresses: OrderAddresses[], orderValues: OrderValues[], fillTakerTokenAmounts: BigNumber.BigNumber[], - v: number[], r: string[], s: string[], txOpts: TxOpts): Promise; + v: number[], r: string[], s: string[], txOpts: TxOpts): Promise; estimateGas: (orderAddresses: OrderAddresses[], orderValues: OrderValues[], fillTakerTokenAmounts: BigNumber.BigNumber[], v: number[], r: string[], s: string[], txOpts?: TxOpts) => Promise; @@ -209,6 +210,7 @@ export enum ExchangeContractErrs { InsufficientRemainingFillAmount = 'INSUFFICIENT_REMAINING_FILL_AMOUNT', MultipleTakerTokensInFillUpToDisallowed = 'MULTIPLE_TAKER_TOKENS_IN_FILL_UP_TO_DISALLOWED', BatchOrdersMustHaveSameExchangeAddress = 'BATCH_ORDERS_MUST_HAVE_SAME_EXCHANGE_ADDRESS', + BatchOrdersMustHaveAtLeastOneItem = 'BATCH_ORDERS_MUST_HAVE_AT_LEAST_ONE_ITEM', } export interface ContractResponse { @@ -351,10 +353,7 @@ export type AsyncMethod = (...args: any[]) => Promise; export interface ContractInstance { address: string; -} - -export interface Artifact { - networks: {[networkId: number]: any}; + abi: Web3.ContractAbi; } export interface ContractEventEmitter { @@ -373,14 +372,6 @@ export interface ExchangeContractByAddress { [address: string]: ExchangeContract; } -export interface ContractArtifact { - networks: { - [networkId: number]: { - address: string; - }; - }; -} - export interface JSONRPCPayload { params: any[]; method: string; diff --git a/src/web3_wrapper.ts b/src/web3_wrapper.ts index 61bac45c9..129017e7c 100644 --- a/src/web3_wrapper.ts +++ b/src/web3_wrapper.ts @@ -2,6 +2,8 @@ import * as _ from 'lodash'; import * as Web3 from 'web3'; import * as BigNumber from 'bignumber.js'; import promisify = require('es6-promisify'); +import {ZeroExError} from './types'; +import {Contract} from './contract'; export class Web3Wrapper { private web3: Web3; @@ -41,6 +43,27 @@ export class Web3Wrapper { return undefined; } } + public async getContractInstanceFromArtifactAsync(artifact: Artifact, + address?: string): Promise { + if (_.isUndefined(address)) { + const networkIdIfExists = await this.getNetworkIdIfExistsAsync(); + if (_.isUndefined(networkIdIfExists)) { + throw new Error(ZeroExError.NoNetworkId); + } + if (_.isUndefined(artifact.networks[networkIdIfExists])) { + throw new Error(ZeroExError.ContractNotDeployedOnNetwork); + } + address = artifact.networks[networkIdIfExists].address.toLowerCase(); + } + const doesContractExist = await this.doesContractExistAtAddressAsync(address); + if (!doesContractExist) { + throw new Error(ZeroExError.ContractDoesNotExist); + } + const contractInstance = this.getContractInstance( + artifact.abi, address, + ); + return contractInstance; + } public toWei(ethAmount: BigNumber.BigNumber): BigNumber.BigNumber { const balanceWei = this.web3.toWei(ethAmount, 'ether'); return balanceWei; @@ -68,6 +91,11 @@ export class Web3Wrapper { const addresses: string[] = await promisify(this.web3.eth.getAccounts)(); return addresses; } + private getContractInstance(abi: Web3.ContractAbi, address: string): A { + const web3ContractInstance = this.web3.eth.contract(abi).at(address); + const contractInstance = new Contract(web3ContractInstance) as any as A; + return contractInstance; + } private async getNetworkAsync(): Promise { const networkId = await promisify(this.web3.version.getNetwork)(); return networkId; diff --git a/test/exchange_wrapper_test.ts b/test/exchange_wrapper_test.ts index 90ac37387..15e957ee1 100644 --- a/test/exchange_wrapper_test.ts +++ b/test/exchange_wrapper_test.ts @@ -196,34 +196,6 @@ describe('ExchangeWrapper', () => { expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, takerAddress)) .to.be.bignumber.equal(fillableAmount.minus(partialFillAmount)); }); - it('should return filled amount', async () => { - const signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount, - ); - const partialFillAmount = new BigNumber(3); - const filledAmount = await zeroEx.exchange.fillOrderAsync( - signedOrder, partialFillAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress); - expect(filledAmount).to.be.bignumber.equal(partialFillAmount); - }); - it('should return the partially filled amount \ - if the fill amount specified is greater then the amount available', async () => { - const signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount, - ); - const partialFillAmount = new BigNumber(3); - await zeroEx.exchange.fillOrderAsync( - signedOrder, partialFillAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress); - const missingBalance = new BigNumber(1); - const totalBalance = partialFillAmount.plus(missingBalance); - await zeroEx.token.transferAsync(takerTokenAddress, coinbase, takerAddress, missingBalance); - await zeroEx.token.setProxyAllowanceAsync(takerTokenAddress, takerAddress, totalBalance); - await zeroEx.token.transferAsync(makerTokenAddress, coinbase, makerAddress, missingBalance); - await zeroEx.token.setProxyAllowanceAsync(makerTokenAddress, makerAddress, totalBalance); - const remainingFillAmount = fillableAmount.minus(partialFillAmount); - const filledAmount = await zeroEx.exchange.fillOrderAsync( - signedOrder, partialFillAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress); - expect(filledAmount).to.be.bignumber.equal(remainingFillAmount); - }); it('should fill the valid orders with fees', async () => { const makerFee = new BigNumber(1); const takerFee = new BigNumber(2); @@ -231,8 +203,9 @@ describe('ExchangeWrapper', () => { makerTokenAddress, takerTokenAddress, makerFee, takerFee, makerAddress, takerAddress, fillableAmount, feeRecipient, ); - await zeroEx.exchange.fillOrderAsync( + const txHash = await zeroEx.exchange.fillOrderAsync( signedOrder, fillTakerAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress); + await blockchainLifecycle.waitUntilMinedAsync(txHash); expect(await zeroEx.token.getBalanceAsync(zrxTokenAddress, feeRecipient)) .to.be.bignumber.equal(makerFee.plus(takerFee)); }); @@ -265,13 +238,15 @@ describe('ExchangeWrapper', () => { ]; }); describe('successful batch fills', () => { - it('should no-op for an empty batch', async () => { - await zeroEx.exchange.batchFillOrdersAsync( - [], shouldThrowOnInsufficientBalanceOrAllowance, takerAddress); + it('should throw if a batch is empty', async () => { + return expect(zeroEx.exchange.batchFillOrdersAsync( + [], shouldThrowOnInsufficientBalanceOrAllowance, takerAddress), + ).to.be.rejectedWith(ExchangeContractErrs.BatchOrdersMustHaveAtLeastOneItem); }); it('should successfully fill multiple orders', async () => { - await zeroEx.exchange.batchFillOrdersAsync( + const txHash = await zeroEx.exchange.batchFillOrdersAsync( orderFillBatch, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress); + await blockchainLifecycle.waitUntilMinedAsync(txHash); const filledAmount = await zeroEx.exchange.getFilledTakerAmountAsync(signedOrderHashHex); const anotherFilledAmount = await zeroEx.exchange.getFilledTakerAmountAsync(anotherOrderHashHex); expect(filledAmount).to.be.bignumber.equal(fillTakerAmount); @@ -298,26 +273,22 @@ describe('ExchangeWrapper', () => { signedOrders = [signedOrder, anotherSignedOrder]; }); describe('successful batch fills', () => { - it('should no-op for an empty batch', async () => { - await zeroEx.exchange.fillOrdersUpToAsync( - [], fillUpToAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress); + it('should throw if a batch is empty', async () => { + return expect(zeroEx.exchange.fillOrdersUpToAsync( + [], fillUpToAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress), + ).to.be.rejectedWith(ExchangeContractErrs.BatchOrdersMustHaveAtLeastOneItem); }); it('should successfully fill up to specified amount', async () => { - await zeroEx.exchange.fillOrdersUpToAsync( + const txHash = await zeroEx.exchange.fillOrdersUpToAsync( signedOrders, fillUpToAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress, ); + await blockchainLifecycle.waitUntilMinedAsync(txHash); const filledAmount = await zeroEx.exchange.getFilledTakerAmountAsync(signedOrderHashHex); const anotherFilledAmount = await zeroEx.exchange.getFilledTakerAmountAsync(anotherOrderHashHex); expect(filledAmount).to.be.bignumber.equal(fillableAmount); const remainingFillAmount = fillableAmount.minus(1); expect(anotherFilledAmount).to.be.bignumber.equal(remainingFillAmount); }); - it('should return filled amount', async () => { - const filledTakerTokenAmount = await zeroEx.exchange.fillOrdersUpToAsync( - signedOrders, fillUpToAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress, - ); - expect(filledTakerTokenAmount).to.be.bignumber.equal(fillUpToAmount); - }); }); }); }); @@ -344,14 +315,11 @@ describe('ExchangeWrapper', () => { describe('#cancelOrderAsync', () => { describe('successful cancels', () => { it('should cancel an order', async () => { - await zeroEx.exchange.cancelOrderAsync(signedOrder, cancelAmount); + const txHash = await zeroEx.exchange.cancelOrderAsync(signedOrder, cancelAmount); + await blockchainLifecycle.waitUntilMinedAsync(txHash); const cancelledAmount = await zeroEx.exchange.getCanceledTakerAmountAsync(orderHashHex); expect(cancelledAmount).to.be.bignumber.equal(cancelAmount); }); - it('should return cancelled amount', async () => { - const cancelledAmount = await zeroEx.exchange.cancelOrderAsync(signedOrder, cancelAmount); - expect(cancelledAmount).to.be.bignumber.equal(cancelAmount); - }); }); }); describe('#batchCancelOrdersAsync', () => { diff --git a/test/utils/blockchain_lifecycle.ts b/test/utils/blockchain_lifecycle.ts index 50eb57b95..b4f742e63 100644 --- a/test/utils/blockchain_lifecycle.ts +++ b/test/utils/blockchain_lifecycle.ts @@ -17,4 +17,7 @@ export class BlockchainLifecycle { throw new Error(`Snapshot with id #${this.snapshotId} failed to revert`); } } + public async waitUntilMinedAsync(txHash: string): Promise { + return undefined; + } } diff --git a/yarn.lock b/yarn.lock index ac48a749f..73452ac1f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5671,9 +5671,9 @@ web3-provider-engine@^8.4.0: xhr "^2.2.0" xtend "^4.0.1" -web3-typescript-typings@^0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/web3-typescript-typings/-/web3-typescript-typings-0.3.2.tgz#75f65fe452e35e2b96192908199dbb7a9ab5bcc3" +web3-typescript-typings@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/web3-typescript-typings/-/web3-typescript-typings-0.4.1.tgz#077f5c042c1d2625b4cabedad88b9e9427b38fb3" dependencies: bignumber.js "^4.0.2" -- cgit v1.2.3