aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--packages/0x.js/CHANGELOG.md2
-rw-r--r--packages/0x.js/package.json2
-rw-r--r--packages/0x.js/src/contract.ts43
-rw-r--r--packages/0x.js/src/contract_wrappers/ether_token_wrapper.ts18
-rw-r--r--packages/0x.js/src/contract_wrappers/exchange_wrapper.ts153
-rw-r--r--packages/0x.js/src/contract_wrappers/token_wrapper.ts41
-rw-r--r--packages/0x.js/src/types.ts16
-rw-r--r--packages/0x.js/test/ether_token_wrapper_test.ts2
-rw-r--r--packages/0x.js/test/utils/constants.ts1
-rw-r--r--packages/0x.js/test/utils/subproviders/empty_wallet_subprovider.ts (renamed from packages/0x.js/src/subproviders/empty_wallet_subprovider.ts)4
-rw-r--r--packages/0x.js/test/utils/subproviders/fake_gas_estimate_subprovider.ts34
-rw-r--r--packages/0x.js/test/utils/web3_factory.ts6
-rw-r--r--yarn.lock14
13 files changed, 177 insertions, 159 deletions
diff --git a/packages/0x.js/CHANGELOG.md b/packages/0x.js/CHANGELOG.md
index 67b2b89b6..8f9444da6 100644
--- a/packages/0x.js/CHANGELOG.md
+++ b/packages/0x.js/CHANGELOG.md
@@ -8,6 +8,8 @@ vx.x.x
* Remove `ZeroExError.ContractNotFound` and replace it with contract-specific errors (#233)
* Make `DecodedLogEvent<A>` contain `LogWithDecodedArgs<A>` under log key instead of merging it in like web3 does (#234)
* Rename `removed` to `isRemoved` in `DecodedLogEvent<A>` (#234)
+ * Add config allowing to specify gasPrice and gasLimit for every transaction sending method (#235)
+ * All transaction sending methods now call `estimateGas` if no gas amount was supplied (#235)
* Modify order validation methods to validate against the `latest` block, not against the `pending` block (#236)
v0.26.0
diff --git a/packages/0x.js/package.json b/packages/0x.js/package.json
index 024cb0fe9..a8e99bacd 100644
--- a/packages/0x.js/package.json
+++ b/packages/0x.js/package.json
@@ -61,7 +61,7 @@
"copyfiles": "^1.2.0",
"coveralls": "^3.0.0",
"dirty-chai": "^2.0.1",
- "ethereumjs-testrpc": "4.0.1",
+ "ethereumjs-testrpc": "6.0.3",
"json-loader": "^0.5.4",
"mocha": "^4.0.0",
"npm-run-all": "^4.0.2",
diff --git a/packages/0x.js/src/contract.ts b/packages/0x.js/src/contract.ts
index e9c49c9f1..a4ee03910 100644
--- a/packages/0x.js/src/contract.ts
+++ b/packages/0x.js/src/contract.ts
@@ -5,6 +5,10 @@ import * as Web3 from 'web3';
import {AbiType} from './types';
+// HACK: Gas estimates on testrpc don't take into account gas refunds.
+// Our calls can trigger max 8 gas refunds for SSTORE per transaction for 15k gas each which gives 120k.
+const GAS_MARGIN = 120000;
+
export class Contract implements Web3.ContractInstance {
public address: string;
public abi: Web3.ContractAbi;
@@ -34,9 +38,10 @@ export class Contract implements Web3.ContractInstance {
} else {
const cbStyleFunction = this.contract[functionAbi.name];
const cbStyleEstimateGasFunction = this.contract[functionAbi.name].estimateGas;
+ const estimateGasAsync = promisify(cbStyleEstimateGasFunction, this.contract);
this[functionAbi.name] = {
- estimateGasAsync: promisify(cbStyleEstimateGasFunction, this.contract),
- sendTransactionAsync: this.promisifyWithDefaultParams(cbStyleFunction),
+ estimateGasAsync,
+ sendTransactionAsync: this.promisifyWithDefaultParams(cbStyleFunction, estimateGasAsync),
};
}
});
@@ -47,28 +52,40 @@ export class Contract implements Web3.ContractInstance {
this[eventAbi.name] = this.contract[eventAbi.name];
});
}
- private promisifyWithDefaultParams(fn: (...args: any[]) => void): (...args: any[]) => Promise<any> {
+ private promisifyWithDefaultParams(
+ web3CbStyleFunction: (...args: any[]) => void,
+ estimateGasAsync: (...args: any[]) => Promise<number>,
+ ): (...args: any[]) => Promise<any> {
const promisifiedWithDefaultParams = async (...args: any[]) => {
- const promise = new Promise((resolve, reject) => {
+ const promise = new Promise(async (resolve, reject) => {
const lastArg = args[args.length - 1];
let txData: Partial<Web3.TxData> = {};
- if (this.isTxData(lastArg)) {
+ if (!_.isUndefined(lastArg) && this.isTxData(lastArg)) {
txData = args.pop();
}
+ // Gas amount sourced with the following priorities:
+ // 1. Optional param passed in to public method call
+ // 2. Global config passed in at library instantiation
+ // 3. Gas estimate calculation + safety margin
+ const removeUndefinedProperties = _.pickBy;
txData = {
- ...this.defaults,
- ...txData,
+ ...removeUndefinedProperties(this.defaults),
+ ...removeUndefinedProperties(txData),
};
- const callback = (err: Error, data: any) => {
- if (_.isNull(err)) {
- resolve(data);
- } else {
+ if (_.isUndefined(txData.gas)) {
+ try {
+ const estimatedGas = await estimateGasAsync.apply(this.contract, [...args, txData]);
+ const gas = estimatedGas + GAS_MARGIN;
+ txData.gas = gas;
+ } catch (err) {
reject(err);
+ return;
}
- };
+ }
+ const callback = (err: Error, data: any) => _.isNull(err) ? resolve(data) : reject(err);
args.push(txData);
args.push(callback);
- fn.apply(this.contract, args);
+ web3CbStyleFunction.apply(this.contract, args);
});
return promise;
};
diff --git a/packages/0x.js/src/contract_wrappers/ether_token_wrapper.ts b/packages/0x.js/src/contract_wrappers/ether_token_wrapper.ts
index 7a3f2bc52..ede0460bd 100644
--- a/packages/0x.js/src/contract_wrappers/ether_token_wrapper.ts
+++ b/packages/0x.js/src/contract_wrappers/ether_token_wrapper.ts
@@ -2,7 +2,7 @@ import BigNumber from 'bignumber.js';
import * as _ from 'lodash';
import {artifacts} from '../artifacts';
-import {EtherTokenContract, ZeroExError} from '../types';
+import {EtherTokenContract, TransactionOpts, ZeroExError} from '../types';
import {assert} from '../utils/assert';
import {Web3Wrapper} from '../web3_wrapper';
@@ -27,10 +27,13 @@ export class EtherTokenWrapper extends ContractWrapper {
* to the depositor address. These wrapped ETH tokens can be used in 0x trades and are redeemable for 1-to-1
* for ETH.
* @param amountInWei Amount of ETH in Wei the caller wishes to deposit.
- * @param depositor The hex encoded user Ethereum address that would like to make the deposit.
+ * @param depositor The hex encoded user Ethereum address that would like to make the deposit.
+ * @param txOpts Transaction parameters.
* @return Transaction hash.
*/
- public async depositAsync(amountInWei: BigNumber, depositor: string): Promise<string> {
+ public async depositAsync(
+ amountInWei: BigNumber, depositor: string, txOpts: TransactionOpts = {},
+ ): Promise<string> {
assert.isValidBaseUnitAmount('amountInWei', amountInWei);
await assert.isSenderAddressAsync('depositor', depositor, this._web3Wrapper);
@@ -41,6 +44,8 @@ export class EtherTokenWrapper extends ContractWrapper {
const txHash = await wethContract.deposit.sendTransactionAsync({
from: depositor,
value: amountInWei,
+ gas: txOpts.gasLimit,
+ gasPrice: txOpts.gasPrice,
});
return txHash;
}
@@ -49,9 +54,12 @@ export class EtherTokenWrapper extends ContractWrapper {
* equivalent number of wrapped ETH tokens.
* @param amountInWei Amount of ETH in Wei the caller wishes to withdraw.
* @param withdrawer The hex encoded user Ethereum address that would like to make the withdrawl.
+ * @param txOpts Transaction parameters.
* @return Transaction hash.
*/
- public async withdrawAsync(amountInWei: BigNumber, withdrawer: string): Promise<string> {
+ public async withdrawAsync(
+ amountInWei: BigNumber, withdrawer: string, txOpts: TransactionOpts = {},
+ ): Promise<string> {
assert.isValidBaseUnitAmount('amountInWei', amountInWei);
await assert.isSenderAddressAsync('withdrawer', withdrawer, this._web3Wrapper);
@@ -62,6 +70,8 @@ export class EtherTokenWrapper extends ContractWrapper {
const wethContract = await this._getEtherTokenContractAsync();
const txHash = await wethContract.withdraw.sendTransactionAsync(amountInWei, {
from: withdrawer,
+ gas: txOpts.gasLimit,
+ gasPrice: txOpts.gasPrice,
});
return txHash;
}
diff --git a/packages/0x.js/src/contract_wrappers/exchange_wrapper.ts b/packages/0x.js/src/contract_wrappers/exchange_wrapper.ts
index 91b41c4a4..273b348ff 100644
--- a/packages/0x.js/src/contract_wrappers/exchange_wrapper.ts
+++ b/packages/0x.js/src/contract_wrappers/exchange_wrapper.ts
@@ -169,16 +169,16 @@ export class ExchangeWrapper extends ContractWrapper {
public async fillOrderAsync(signedOrder: SignedOrder, fillTakerTokenAmount: BigNumber,
shouldThrowOnInsufficientBalanceOrAllowance: boolean,
takerAddress: string,
- orderTransactionOpts?: OrderTransactionOpts): Promise<string> {
+ orderTransactionOpts: OrderTransactionOpts = {}): Promise<string> {
assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema);
assert.isValidBaseUnitAmount('fillTakerTokenAmount', fillTakerTokenAmount);
assert.isBoolean('shouldThrowOnInsufficientBalanceOrAllowance', shouldThrowOnInsufficientBalanceOrAllowance);
await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper);
const exchangeInstance = await this._getExchangeContractAsync();
- const shouldValidate = _.isUndefined(orderTransactionOpts) ?
- SHOULD_VALIDATE_BY_DEFAULT :
- orderTransactionOpts.shouldValidate;
+ const shouldValidate = _.isUndefined(orderTransactionOpts.shouldValidate) ?
+ SHOULD_VALIDATE_BY_DEFAULT :
+ orderTransactionOpts.shouldValidate;
if (shouldValidate) {
const zrxTokenAddress = this.getZRXTokenAddress();
const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper, BlockParamLiteral.Latest);
@@ -188,18 +188,6 @@ export class ExchangeWrapper extends ContractWrapper {
const [orderAddresses, orderValues] = ExchangeWrapper._getOrderAddressesAndValues(signedOrder);
- const gas = await exchangeInstance.fillOrder.estimateGasAsync(
- orderAddresses,
- orderValues,
- fillTakerTokenAmount,
- shouldThrowOnInsufficientBalanceOrAllowance,
- signedOrder.ecSignature.v,
- signedOrder.ecSignature.r,
- signedOrder.ecSignature.s,
- {
- from: takerAddress,
- },
- );
const txHash: string = await exchangeInstance.fillOrder.sendTransactionAsync(
orderAddresses,
orderValues,
@@ -210,7 +198,8 @@ export class ExchangeWrapper extends ContractWrapper {
signedOrder.ecSignature.s,
{
from: takerAddress,
- gas,
+ gas: orderTransactionOpts.gasLimit,
+ gasPrice: orderTransactionOpts.gasPrice,
},
);
return txHash;
@@ -236,7 +225,7 @@ export class ExchangeWrapper extends ContractWrapper {
public async fillOrdersUpToAsync(signedOrders: SignedOrder[], fillTakerTokenAmount: BigNumber,
shouldThrowOnInsufficientBalanceOrAllowance: boolean,
takerAddress: string,
- orderTransactionOpts?: OrderTransactionOpts): Promise<string> {
+ orderTransactionOpts: OrderTransactionOpts = {}): Promise<string> {
assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema);
const takerTokenAddresses = _.map(signedOrders, signedOrder => signedOrder.takerTokenAddress);
assert.hasAtMostOneUniqueValue(takerTokenAddresses,
@@ -248,9 +237,9 @@ export class ExchangeWrapper extends ContractWrapper {
assert.isBoolean('shouldThrowOnInsufficientBalanceOrAllowance', shouldThrowOnInsufficientBalanceOrAllowance);
await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper);
- const shouldValidate = _.isUndefined(orderTransactionOpts) ?
- SHOULD_VALIDATE_BY_DEFAULT :
- orderTransactionOpts.shouldValidate;
+ const shouldValidate = _.isUndefined(orderTransactionOpts.shouldValidate) ?
+ SHOULD_VALIDATE_BY_DEFAULT :
+ orderTransactionOpts.shouldValidate;
if (shouldValidate) {
const zrxTokenAddress = this.getZRXTokenAddress();
const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper, BlockParamLiteral.Latest);
@@ -278,18 +267,6 @@ export class ExchangeWrapper extends ContractWrapper {
);
const exchangeInstance = await this._getExchangeContractAsync();
- const gas = await exchangeInstance.fillOrdersUpTo.estimateGasAsync(
- orderAddressesArray,
- orderValuesArray,
- fillTakerTokenAmount,
- shouldThrowOnInsufficientBalanceOrAllowance,
- vArray,
- rArray,
- sArray,
- {
- from: takerAddress,
- },
- );
const txHash = await exchangeInstance.fillOrdersUpTo.sendTransactionAsync(
orderAddressesArray,
orderValuesArray,
@@ -300,7 +277,8 @@ export class ExchangeWrapper extends ContractWrapper {
sArray,
{
from: takerAddress,
- gas,
+ gas: orderTransactionOpts.gasLimit,
+ gasPrice: orderTransactionOpts.gasPrice,
},
);
return txHash;
@@ -328,7 +306,7 @@ export class ExchangeWrapper extends ContractWrapper {
public async batchFillOrdersAsync(orderFillRequests: OrderFillRequest[],
shouldThrowOnInsufficientBalanceOrAllowance: boolean,
takerAddress: string,
- orderTransactionOpts?: OrderTransactionOpts): Promise<string> {
+ orderTransactionOpts: OrderTransactionOpts = {}): Promise<string> {
assert.doesConformToSchema('orderFillRequests', orderFillRequests, schemas.orderFillRequestsSchema);
const exchangeContractAddresses = _.map(
orderFillRequests,
@@ -338,9 +316,9 @@ export class ExchangeWrapper extends ContractWrapper {
ExchangeContractErrs.BatchOrdersMustHaveSameExchangeAddress);
assert.isBoolean('shouldThrowOnInsufficientBalanceOrAllowance', shouldThrowOnInsufficientBalanceOrAllowance);
await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper);
- const shouldValidate = _.isUndefined(orderTransactionOpts) ?
- SHOULD_VALIDATE_BY_DEFAULT :
- orderTransactionOpts.shouldValidate;
+ const shouldValidate = _.isUndefined(orderTransactionOpts.shouldValidate) ?
+ SHOULD_VALIDATE_BY_DEFAULT :
+ orderTransactionOpts.shouldValidate;
if (shouldValidate) {
const zrxTokenAddress = this.getZRXTokenAddress();
const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper, BlockParamLiteral.Latest);
@@ -370,18 +348,6 @@ export class ExchangeWrapper extends ContractWrapper {
);
const exchangeInstance = await this._getExchangeContractAsync();
- const gas = await exchangeInstance.batchFillOrders.estimateGasAsync(
- orderAddressesArray,
- orderValuesArray,
- fillTakerTokenAmounts,
- shouldThrowOnInsufficientBalanceOrAllowance,
- vArray,
- rArray,
- sArray,
- {
- from: takerAddress,
- },
- );
const txHash = await exchangeInstance.batchFillOrders.sendTransactionAsync(
orderAddressesArray,
orderValuesArray,
@@ -392,7 +358,8 @@ export class ExchangeWrapper extends ContractWrapper {
sArray,
{
from: takerAddress,
- gas,
+ gas: orderTransactionOpts.gasLimit,
+ gasPrice: orderTransactionOpts.gasPrice,
},
);
return txHash;
@@ -411,16 +378,16 @@ export class ExchangeWrapper extends ContractWrapper {
@decorators.contractCallErrorHandler
public async fillOrKillOrderAsync(signedOrder: SignedOrder, fillTakerTokenAmount: BigNumber,
takerAddress: string,
- orderTransactionOpts?: OrderTransactionOpts): Promise<string> {
+ orderTransactionOpts: OrderTransactionOpts = {}): Promise<string> {
assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema);
assert.isValidBaseUnitAmount('fillTakerTokenAmount', fillTakerTokenAmount);
await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper);
const exchangeInstance = await this._getExchangeContractAsync();
- const shouldValidate = _.isUndefined(orderTransactionOpts) ?
- SHOULD_VALIDATE_BY_DEFAULT :
- orderTransactionOpts.shouldValidate;
+ const shouldValidate = _.isUndefined(orderTransactionOpts.shouldValidate) ?
+ SHOULD_VALIDATE_BY_DEFAULT :
+ orderTransactionOpts.shouldValidate;
if (shouldValidate) {
const zrxTokenAddress = this.getZRXTokenAddress();
const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper, BlockParamLiteral.Latest);
@@ -429,18 +396,6 @@ export class ExchangeWrapper extends ContractWrapper {
}
const [orderAddresses, orderValues] = ExchangeWrapper._getOrderAddressesAndValues(signedOrder);
-
- const gas = await exchangeInstance.fillOrKillOrder.estimateGasAsync(
- orderAddresses,
- orderValues,
- fillTakerTokenAmount,
- signedOrder.ecSignature.v,
- signedOrder.ecSignature.r,
- signedOrder.ecSignature.s,
- {
- from: takerAddress,
- },
- );
const txHash = await exchangeInstance.fillOrKillOrder.sendTransactionAsync(
orderAddresses,
orderValues,
@@ -450,7 +405,8 @@ export class ExchangeWrapper extends ContractWrapper {
signedOrder.ecSignature.s,
{
from: takerAddress,
- gas,
+ gas: orderTransactionOpts.gasLimit,
+ gasPrice: orderTransactionOpts.gasPrice,
},
);
return txHash;
@@ -467,7 +423,7 @@ export class ExchangeWrapper extends ContractWrapper {
@decorators.contractCallErrorHandler
public async batchFillOrKillAsync(orderFillRequests: OrderFillRequest[],
takerAddress: string,
- orderTransactionOpts?: OrderTransactionOpts): Promise<string> {
+ orderTransactionOpts: OrderTransactionOpts = {}): Promise<string> {
assert.doesConformToSchema('orderFillRequests', orderFillRequests,
schemas.orderFillRequestsSchema);
const exchangeContractAddresses = _.map(
@@ -482,9 +438,9 @@ export class ExchangeWrapper extends ContractWrapper {
}
const exchangeInstance = await this._getExchangeContractAsync();
- const shouldValidate = _.isUndefined(orderTransactionOpts) ?
- SHOULD_VALIDATE_BY_DEFAULT :
- orderTransactionOpts.shouldValidate;
+ const shouldValidate = _.isUndefined(orderTransactionOpts.shouldValidate) ?
+ SHOULD_VALIDATE_BY_DEFAULT :
+ orderTransactionOpts.shouldValidate;
if (shouldValidate) {
const zrxTokenAddress = this.getZRXTokenAddress();
const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper, BlockParamLiteral.Latest);
@@ -509,18 +465,6 @@ export class ExchangeWrapper extends ContractWrapper {
// We use _.unzip<any> because _.unzip doesn't type check if values have different types :'(
const [orderAddresses, orderValues, fillTakerTokenAmounts, vParams, rParams, sParams] =
_.unzip<any>(orderAddressesValuesAndTakerTokenFillAmounts);
-
- const gas = await exchangeInstance.batchFillOrKillOrders.estimateGasAsync(
- orderAddresses,
- orderValues,
- fillTakerTokenAmounts,
- vParams,
- rParams,
- sParams,
- {
- from: takerAddress,
- },
- );
const txHash = await exchangeInstance.batchFillOrKillOrders.sendTransactionAsync(
orderAddresses,
orderValues,
@@ -530,7 +474,8 @@ export class ExchangeWrapper extends ContractWrapper {
sParams,
{
from: takerAddress,
- gas,
+ gas: orderTransactionOpts.gasLimit,
+ gasPrice: orderTransactionOpts.gasPrice,
},
);
return txHash;
@@ -546,16 +491,16 @@ export class ExchangeWrapper extends ContractWrapper {
@decorators.contractCallErrorHandler
public async cancelOrderAsync(order: Order|SignedOrder,
cancelTakerTokenAmount: BigNumber,
- orderTransactionOpts?: OrderTransactionOpts): Promise<string> {
+ orderTransactionOpts: OrderTransactionOpts = {}): Promise<string> {
assert.doesConformToSchema('order', order, schemas.orderSchema);
assert.isValidBaseUnitAmount('takerTokenCancelAmount', cancelTakerTokenAmount);
await assert.isSenderAddressAsync('order.maker', order.maker, this._web3Wrapper);
const exchangeInstance = await this._getExchangeContractAsync();
- const shouldValidate = _.isUndefined(orderTransactionOpts) ?
- SHOULD_VALIDATE_BY_DEFAULT :
- orderTransactionOpts.shouldValidate;
+ const shouldValidate = _.isUndefined(orderTransactionOpts.shouldValidate) ?
+ SHOULD_VALIDATE_BY_DEFAULT :
+ orderTransactionOpts.shouldValidate;
if (shouldValidate) {
const orderHash = utils.getOrderHashHex(order);
const unavailableTakerTokenAmount = await this.getUnavailableTakerAmountAsync(orderHash);
@@ -564,21 +509,14 @@ export class ExchangeWrapper extends ContractWrapper {
}
const [orderAddresses, orderValues] = ExchangeWrapper._getOrderAddressesAndValues(order);
- const gas = await exchangeInstance.cancelOrder.estimateGasAsync(
- orderAddresses,
- orderValues,
- cancelTakerTokenAmount,
- {
- from: order.maker,
- },
- );
const txHash = await exchangeInstance.cancelOrder.sendTransactionAsync(
orderAddresses,
orderValues,
cancelTakerTokenAmount,
{
from: order.maker,
- gas,
+ gas: orderTransactionOpts.gasLimit,
+ gasPrice: orderTransactionOpts.gasPrice,
},
);
return txHash;
@@ -593,7 +531,7 @@ export class ExchangeWrapper extends ContractWrapper {
*/
@decorators.contractCallErrorHandler
public async batchCancelOrdersAsync(orderCancellationRequests: OrderCancellationRequest[],
- orderTransactionOpts?: OrderTransactionOpts): Promise<string> {
+ orderTransactionOpts: OrderTransactionOpts = {}): Promise<string> {
assert.doesConformToSchema('orderCancellationRequests', orderCancellationRequests,
schemas.orderCancellationRequestsSchema);
const exchangeContractAddresses = _.map(
@@ -606,9 +544,9 @@ export class ExchangeWrapper extends ContractWrapper {
assert.hasAtMostOneUniqueValue(makers, ExchangeContractErrs.MultipleMakersInSingleCancelBatchDisallowed);
const maker = makers[0];
await assert.isSenderAddressAsync('maker', maker, this._web3Wrapper);
- const shouldValidate = _.isUndefined(orderTransactionOpts) ?
- SHOULD_VALIDATE_BY_DEFAULT :
- orderTransactionOpts.shouldValidate;
+ const shouldValidate = _.isUndefined(orderTransactionOpts.shouldValidate) ?
+ SHOULD_VALIDATE_BY_DEFAULT :
+ orderTransactionOpts.shouldValidate;
if (shouldValidate) {
for (const orderCancellationRequest of orderCancellationRequests) {
const orderHash = utils.getOrderHashHex(orderCancellationRequest.order);
@@ -633,21 +571,14 @@ export class ExchangeWrapper extends ContractWrapper {
// We use _.unzip<any> because _.unzip doesn't type check if values have different types :'(
const [orderAddresses, orderValues, cancelTakerTokenAmounts] =
_.unzip<any>(orderAddressesValuesAndTakerTokenCancelAmounts);
- const gas = await exchangeInstance.batchCancelOrders.estimateGasAsync(
- orderAddresses,
- orderValues,
- cancelTakerTokenAmounts,
- {
- from: maker,
- },
- );
const txHash = await exchangeInstance.batchCancelOrders.sendTransactionAsync(
orderAddresses,
orderValues,
cancelTakerTokenAmounts,
{
from: maker,
- gas,
+ gas: orderTransactionOpts.gasLimit,
+ gasPrice: orderTransactionOpts.gasPrice,
},
);
return txHash;
diff --git a/packages/0x.js/src/contract_wrappers/token_wrapper.ts b/packages/0x.js/src/contract_wrappers/token_wrapper.ts
index 5c6cfeaed..4a1dfcf8d 100644
--- a/packages/0x.js/src/contract_wrappers/token_wrapper.ts
+++ b/packages/0x.js/src/contract_wrappers/token_wrapper.ts
@@ -12,6 +12,7 @@ import {
TokenContract,
TokenContractEventArgs,
TokenEvents,
+ TransactionOpts,
ZeroExError,
} from '../types';
import {AbiDecoder} from '../utils/abi_decoder';
@@ -66,24 +67,21 @@ export class TokenWrapper extends ContractWrapper {
* for spenderAddress.
* @param spenderAddress The hex encoded user Ethereum address who will be able to spend the set allowance.
* @param amountInBaseUnits The allowance amount you would like to set.
+ * @param txOpts Transaction parameters.
* @return Transaction hash.
*/
public async setAllowanceAsync(tokenAddress: string, ownerAddress: string, spenderAddress: string,
- amountInBaseUnits: BigNumber): Promise<string> {
+ amountInBaseUnits: BigNumber, txOpts: TransactionOpts = {}): Promise<string> {
await assert.isSenderAddressAsync('ownerAddress', ownerAddress, this._web3Wrapper);
assert.isETHAddressHex('spenderAddress', spenderAddress);
assert.isETHAddressHex('tokenAddress', tokenAddress);
assert.isValidBaseUnitAmount('amountInBaseUnits', amountInBaseUnits);
const tokenContract = await this._getTokenContractAsync(tokenAddress);
- // Hack: for some reason default estimated gas amount causes `base fee exceeds gas limit` exception
- // on testrpc. Probably related to https://github.com/ethereumjs/testrpc/issues/294
- // TODO: Debug issue in testrpc and submit a PR, then remove this hack
- const networkId = this._web3Wrapper.getNetworkId();
- const gas = networkId === constants.TESTRPC_NETWORK_ID ? ALLOWANCE_TO_ZERO_GAS_AMOUNT : undefined;
const txHash = await tokenContract.approve.sendTransactionAsync(spenderAddress, amountInBaseUnits, {
from: ownerAddress,
- gas,
+ gas: txOpts.gasLimit,
+ gasPrice: txOpts.gasPrice,
});
return txHash;
}
@@ -96,12 +94,13 @@ export class TokenWrapper extends ContractWrapper {
* @param ownerAddress The hex encoded user Ethereum address who would like to set an allowance
* for spenderAddress.
* @param spenderAddress The hex encoded user Ethereum address who will be able to spend the set allowance.
+ * @param txOpts Transaction parameters.
* @return Transaction hash.
*/
public async setUnlimitedAllowanceAsync(tokenAddress: string, ownerAddress: string,
- spenderAddress: string): Promise<string> {
+ spenderAddress: string, txOpts: TransactionOpts = {}): Promise<string> {
const txHash = await this.setAllowanceAsync(
- tokenAddress, ownerAddress, spenderAddress, this.UNLIMITED_ALLOWANCE_IN_BASE_UNITS,
+ tokenAddress, ownerAddress, spenderAddress, this.UNLIMITED_ALLOWANCE_IN_BASE_UNITS, txOpts,
);
return txHash;
}
@@ -147,16 +146,19 @@ export class TokenWrapper extends ContractWrapper {
* @param ownerAddress The hex encoded user Ethereum address who is setting an allowance
* for the Proxy contract.
* @param amountInBaseUnits The allowance amount specified in baseUnits.
+ * @param txOpts Transaction parameters.
* @return Transaction hash.
*/
public async setProxyAllowanceAsync(tokenAddress: string, ownerAddress: string,
- amountInBaseUnits: BigNumber): Promise<string> {
+ amountInBaseUnits: BigNumber, txOpts: TransactionOpts = {}): Promise<string> {
assert.isETHAddressHex('ownerAddress', ownerAddress);
assert.isETHAddressHex('tokenAddress', tokenAddress);
assert.isValidBaseUnitAmount('amountInBaseUnits', amountInBaseUnits);
const proxyAddress = this._tokenTransferProxyWrapper.getContractAddress();
- const txHash = await this.setAllowanceAsync(tokenAddress, ownerAddress, proxyAddress, amountInBaseUnits);
+ const txHash = await this.setAllowanceAsync(
+ tokenAddress, ownerAddress, proxyAddress, amountInBaseUnits, txOpts,
+ );
return txHash;
}
/**
@@ -167,11 +169,14 @@ export class TokenWrapper extends ContractWrapper {
* @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed.
* @param ownerAddress The hex encoded user Ethereum address who is setting an allowance
* for the Proxy contract.
+ * @param txOpts Transaction parameters.
* @return Transaction hash.
*/
- public async setUnlimitedProxyAllowanceAsync(tokenAddress: string, ownerAddress: string): Promise<string> {
+ public async setUnlimitedProxyAllowanceAsync(
+ tokenAddress: string, ownerAddress: string, txOpts: TransactionOpts = {},
+ ): Promise<string> {
const txHash = await this.setProxyAllowanceAsync(
- tokenAddress, ownerAddress, this.UNLIMITED_ALLOWANCE_IN_BASE_UNITS,
+ tokenAddress, ownerAddress, this.UNLIMITED_ALLOWANCE_IN_BASE_UNITS, txOpts,
);
return txHash;
}
@@ -181,10 +186,11 @@ export class TokenWrapper extends ContractWrapper {
* @param fromAddress The hex encoded user Ethereum address that will send the funds.
* @param toAddress The hex encoded user Ethereum address that will receive the funds.
* @param amountInBaseUnits The amount (specified in baseUnits) of the token to transfer.
+ * @param txOpts Transaction parameters.
* @return Transaction hash.
*/
public async transferAsync(tokenAddress: string, fromAddress: string, toAddress: string,
- amountInBaseUnits: BigNumber): Promise<string> {
+ amountInBaseUnits: BigNumber, txOpts: TransactionOpts = {}): Promise<string> {
assert.isETHAddressHex('tokenAddress', tokenAddress);
await assert.isSenderAddressAsync('fromAddress', fromAddress, this._web3Wrapper);
assert.isETHAddressHex('toAddress', toAddress);
@@ -199,6 +205,8 @@ export class TokenWrapper extends ContractWrapper {
const txHash = await tokenContract.transfer.sendTransactionAsync(toAddress, amountInBaseUnits, {
from: fromAddress,
+ gas: txOpts.gasLimit,
+ gasPrice: txOpts.gasPrice,
});
return txHash;
}
@@ -213,10 +221,11 @@ export class TokenWrapper extends ContractWrapper {
* `fromAddress` must have set an allowance to the `senderAddress`
* before this call.
* @param amountInBaseUnits The amount (specified in baseUnits) of the token to transfer.
+ * @param txOpts Transaction parameters.
* @return Transaction hash.
*/
public async transferFromAsync(tokenAddress: string, fromAddress: string, toAddress: string,
- senderAddress: string, amountInBaseUnits: BigNumber):
+ senderAddress: string, amountInBaseUnits: BigNumber, txOpts: TransactionOpts = {}):
Promise<string> {
assert.isETHAddressHex('tokenAddress', tokenAddress);
assert.isETHAddressHex('fromAddress', fromAddress);
@@ -240,6 +249,8 @@ export class TokenWrapper extends ContractWrapper {
fromAddress, toAddress, amountInBaseUnits,
{
from: senderAddress,
+ gas: txOpts.gasLimit,
+ gasPrice: txOpts.gasPrice,
},
);
return txHash;
diff --git a/packages/0x.js/src/types.ts b/packages/0x.js/src/types.ts
index 91a5978ea..5363b02ff 100644
--- a/packages/0x.js/src/types.ts
+++ b/packages/0x.js/src/types.ts
@@ -332,6 +332,7 @@ export interface TxOpts {
from: string;
gas?: number;
value?: BigNumber;
+ gasPrice?: BigNumber;
}
export interface TokenAddressBySymbol {
@@ -484,11 +485,20 @@ export interface MethodOpts {
}
/*
+ * gasPrice: Gas price in Wei to use for a transaction
+ * gasLimit: The amount of gas to send with a transaction
+ */
+export interface TransactionOpts {
+ gasPrice?: BigNumber;
+ gasLimit?: number;
+}
+
+/*
* shouldValidate: Flag indicating whether the library should make attempts to validate a transaction before
- * broadcasting it. For example, order has a valid signature, maker has sufficient funds, etc.
+ * broadcasting it. For example, order has a valid signature, maker has sufficient funds, etc. Default: true
*/
-export interface OrderTransactionOpts {
- shouldValidate: boolean;
+export interface OrderTransactionOpts extends TransactionOpts {
+ shouldValidate?: boolean;
}
export type FilterObject = Web3.FilterObject;
diff --git a/packages/0x.js/test/ether_token_wrapper_test.ts b/packages/0x.js/test/ether_token_wrapper_test.ts
index 5b5e4c656..d3e4439ee 100644
--- a/packages/0x.js/test/ether_token_wrapper_test.ts
+++ b/packages/0x.js/test/ether_token_wrapper_test.ts
@@ -18,7 +18,7 @@ const blockchainLifecycle = new BlockchainLifecycle();
// a small amount of ETH will be used to pay this gas cost. We therefore check that the difference between
// the expected balance and actual balance (given the amount of ETH deposited), only deviates by the amount
// required to pay gas costs.
-const MAX_REASONABLE_GAS_COST_IN_WEI = 62237;
+const MAX_REASONABLE_GAS_COST_IN_WEI = 62517;
describe('EtherTokenWrapper', () => {
let web3: Web3;
diff --git a/packages/0x.js/test/utils/constants.ts b/packages/0x.js/test/utils/constants.ts
index 212abf4d6..75fdf49c9 100644
--- a/packages/0x.js/test/utils/constants.ts
+++ b/packages/0x.js/test/utils/constants.ts
@@ -8,4 +8,5 @@ export const constants = {
KOVAN_RPC_URL: 'https://kovan.infura.io',
ROPSTEN_RPC_URL: 'https://ropsten.infura.io',
ZRX_DECIMALS: 18,
+ GAS_ESTIMATE: 500000,
};
diff --git a/packages/0x.js/src/subproviders/empty_wallet_subprovider.ts b/packages/0x.js/test/utils/subproviders/empty_wallet_subprovider.ts
index 2993bc801..e5e279873 100644
--- a/packages/0x.js/src/subproviders/empty_wallet_subprovider.ts
+++ b/packages/0x.js/test/utils/subproviders/empty_wallet_subprovider.ts
@@ -1,11 +1,11 @@
-import {JSONRPCPayload} from '../types';
+import {JSONRPCPayload} from '../../../src/types';
/*
* This class implements the web3-provider-engine subprovider interface and returns
* that the provider has no addresses when queried.
* Source: https://github.com/MetaMask/provider-engine/blob/master/subproviders/subprovider.js
*/
-export class EmptyWalletSubProvider {
+export class EmptyWalletSubprovider {
// This method needs to be here to satisfy the interface but linter wants it to be static.
// tslint:disable-next-line:prefer-function-over-method
public handleRequest(payload: JSONRPCPayload, next: () => void, end: (err: Error|null, result: any) => void) {
diff --git a/packages/0x.js/test/utils/subproviders/fake_gas_estimate_subprovider.ts b/packages/0x.js/test/utils/subproviders/fake_gas_estimate_subprovider.ts
new file mode 100644
index 000000000..059163f2e
--- /dev/null
+++ b/packages/0x.js/test/utils/subproviders/fake_gas_estimate_subprovider.ts
@@ -0,0 +1,34 @@
+import {JSONRPCPayload} from '../../../src/types';
+
+/*
+ * This class implements the web3-provider-engine subprovider interface and returns
+ * the constant gas estimate when queried.
+ * HACK: We need this so that our tests don't use testrpc gas estimation which sometimes kills the node.
+ * Source: https://github.com/trufflesuite/ganache-cli/issues/417
+ * Source: https://github.com/trufflesuite/ganache-cli/issues/437
+ * Source: https://github.com/MetaMask/provider-engine/blob/master/subproviders/subprovider.js
+ */
+export class FakeGasEstimateSubprovider {
+ private constantGasAmount: number;
+ constructor(constantGasAmount: number) {
+ this.constantGasAmount = constantGasAmount;
+ }
+ // This method needs to be here to satisfy the interface but linter wants it to be static.
+ // tslint:disable-next-line:prefer-function-over-method
+ public handleRequest(payload: JSONRPCPayload, next: () => void, end: (err: Error|null, result: any) => void) {
+ switch (payload.method) {
+ case 'eth_estimateGas':
+ end(null, this.constantGasAmount);
+ return;
+
+ default:
+ next();
+ return;
+ }
+ }
+ // Required to implement this method despite not needing it for this subprovider
+ // tslint:disable-next-line:prefer-function-over-method
+ public setEngine(engine: any) {
+ // noop
+ }
+}
diff --git a/packages/0x.js/test/utils/web3_factory.ts b/packages/0x.js/test/utils/web3_factory.ts
index b4bf1acd3..da4828943 100644
--- a/packages/0x.js/test/utils/web3_factory.ts
+++ b/packages/0x.js/test/utils/web3_factory.ts
@@ -7,7 +7,8 @@ import * as Web3 from 'web3';
import ProviderEngine = require('web3-provider-engine');
import RpcSubprovider = require('web3-provider-engine/subproviders/rpc');
-import {EmptyWalletSubProvider} from '../../src/subproviders/empty_wallet_subprovider';
+import {EmptyWalletSubprovider} from './subproviders/empty_wallet_subprovider';
+import {FakeGasEstimateSubprovider} from './subproviders/fake_gas_estimate_subprovider';
import {constants} from './constants';
@@ -22,8 +23,9 @@ export const web3Factory = {
const provider = new ProviderEngine();
const rpcUrl = `http://${constants.RPC_HOST}:${constants.RPC_PORT}`;
if (!hasAddresses) {
- provider.addProvider(new EmptyWalletSubProvider());
+ provider.addProvider(new EmptyWalletSubprovider());
}
+ provider.addProvider(new FakeGasEstimateSubprovider(constants.GAS_ESTIMATE));
provider.addProvider(new RpcSubprovider({
rpcUrl,
}));
diff --git a/yarn.lock b/yarn.lock
index 482e133e8..f7a51e1e6 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -6,6 +6,10 @@
version "0.4.1"
resolved "https://registry.yarnpkg.com/@types/accounting/-/accounting-0.4.1.tgz#865d9f5694fd7c438fba34eb4bc82eec6f34cdd5"
+"@types/bintrees@^1.0.2":
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/@types/bintrees/-/bintrees-1.0.2.tgz#0dfdce4eeebdf90427bd35b0e79dc248b3d157a6"
+
"@types/dateformat@^1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@types/dateformat/-/dateformat-1.0.1.tgz#2e5b235c8c55652c4fec284506d2a36fe65fe87e"
@@ -18,10 +22,6 @@
version "5.12.2"
resolved "https://registry.yarnpkg.com/@types/fetch-mock/-/fetch-mock-5.12.2.tgz#8c96517ff74303031c65c5da2d99858e34c844d2"
-"@types/bintrees@^1.0.2":
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/@types/bintrees/-/bintrees-1.0.2.tgz#0dfdce4eeebdf90427bd35b0e79dc248b3d157a6"
-
"@types/fs-extra@^4.0.0":
version "4.0.5"
resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-4.0.5.tgz#8aa6033c0e87c653b09a6711686916864b48ec9e"
@@ -2888,9 +2888,9 @@ ethereumjs-blockstream@^2.0.6:
source-map-support "0.4.14"
uuid "3.0.1"
-ethereumjs-testrpc@4.0.1:
- version "4.0.1"
- resolved "https://registry.yarnpkg.com/ethereumjs-testrpc/-/ethereumjs-testrpc-4.0.1.tgz#af23babff4c36008418bc6de4c80f81606896cad"
+ethereumjs-testrpc@^6.0.3:
+ version "6.0.3"
+ resolved "https://registry.yarnpkg.com/ethereumjs-testrpc/-/ethereumjs-testrpc-6.0.3.tgz#7a0b87bf3670f92f607f98fa6a78801d9741b124"
dependencies:
webpack "^3.0.0"