Newer
Older
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
// This file is part of Parity.
//
// SPDX-License-Identifier: BSD-3-Clause
import Abi from '@parity/abi';
import Token from '@parity/abi/lib/token';
import { eip20 } from '@parity/contracts/lib/abi';
import BigNumber from 'bignumber.js';
import { makeContract } from '@parity/light.js';
import memoize from 'lodash/memoize';
import { toWei } from '@parity/api/lib/util/wei';
import { isNotErc20TokenAddress } from './chain';
import EthereumTx from 'ethereumjs-tx';
const GAS_MULT_FACTOR = 1.25; // Since estimateGas is not always accurate, we add a 25% factor for buffer.
Luke Schoen
committed
export const GAS_LIMIT_DATA = new BigNumber(150000);
export const contractForToken = memoize(tokenAddress =>
/**
* Estimate the amount of gas for our transaction.
*/
export const estimateGas = (tx, token, api) => {
if (!tx || !Object.keys(tx).length) {
if (isNotErc20TokenAddress(token.address)) {
return estimateGasForEth(txForEth(tx), api).then(estimatedGasForEth => {
// do not add any buffer in case of an account to account transaction
return estimatedGasForEth.eq(21000)
? estimatedGasForEth
: addBuffer(estimatedGasForEth);
});
return estimateGasForErc20(txForErc20(tx, token), token).then(addBuffer);
}
};
/**
* Estimate gas to transfer in ERC20 contract. Expensive function, so we
* memoize it.
*/
const estimateGasForErc20 = memoize((txForErc20, token) => {
debug(`Estimating gas for tx on token contract.`, token, txForErc20);
return contractForToken(
token.address
).contractObject.instance.transfer.estimateGas(
txForErc20.options,
txForErc20.args
);
}, JSON.stringify);
* Estimate gas to transfer to an ETH or ETC address. Expensive function, so we
* memoize it. Note that you must only transfer from and ETH to an ETH address,
* or from an ETC to an ETC address.
*/
const estimateGasForEth = memoize((txForEth, api) => {
debug(`Estimating gas for tx.`, txForEth);
return api.eth.estimateGas(txForEth);
}, JSON.stringify);
/**
* Add some extra gas buffer just to be sure user has enough balance.
*
* @param {BigNumber} estimated - The estimated gas price returned by
* estimateGas.
*/
const addBuffer = estimated => {
const withBuffer = estimated.multipliedBy(GAS_MULT_FACTOR).decimalPlaces(0);
debug(`Estimated gas ${+estimated}, with buffer ${+withBuffer}.`);
return withBuffer;
};
/**
* This.tx is a user-friendly tx object. We convert it now as it can be
* passed to makeContract.transfer(...).
*/
export const txForErc20 = (tx, token) => {
new BigNumber(tx.amount).multipliedBy(
new BigNumber(10).pow(token.decimals)
)
data: tx.data,
Luke Schoen
committed
output.options.gas = tx.data ? GAS_LIMIT_DATA : tx.gas;
};
/**
* This.tx is a user-friendly tx object. We convert it now as it can be
* passed to post$(tx).
*/
export const txForEth = tx => {
data: tx.data,
to: tx.to,
value: toWei(tx.amount.toString())
};
// gas field should not be present when the function is called for gas estimation.
if (tx.gas) {
Luke Schoen
committed
output.gas = tx.data ? GAS_LIMIT_DATA : tx.gas;
/**
* This.tx is a user-friendly tx object. This function converts it to an
* EthereumTx object.
*/
const getEthereumTx = tx => {
const {
amount,
chainId,
data,
gas,
gasPrice,
to,
token,
transactionCount
} = tx;
Luke Schoen
committed
// Temporary solution
const gasLimit = data ? GAS_LIMIT_DATA : gas;
const txParams = {
nonce: '0x' + transactionCount.toNumber().toString(16),
Luke Schoen
committed
gasLimit: '0x' + gasLimit.toNumber().toString(16),
gasPrice: toWei(gasPrice, 'shannon').toNumber(),
if (isNotErc20TokenAddress(token.address)) {
txParams.data = data;
txParams.to = to;
txParams.value = parseFloat(amount) * Math.pow(10, 18);
} else {
txParams.to = token.address;
txParams.data = txParams.data
? data
: '0x' +
new Abi(eip20).functions
.find(f => f._name === 'transfer')
.encodeCall([
new Token('address', to),
new Token(
'uint',
'0x' +
new BigNumber(amount)
.multipliedBy(new BigNumber(10).pow(token.decimals))
.toNumber()
.toString(16)
)
]);
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
}
return new EthereumTx(txParams);
};
/*
* Sign the given this.tx with the given signature.
*
* ethereumjs-tx does not support EIP155-compliant signatures (https://git.io/fh3SG)
* This is a workaround from https://git.io/fh3S8
*/
export const signTransactionWithSignature = (thisTx, signature) => {
const tx = getEthereumTx(thisTx);
const sigBuf = Buffer.from(signature.substr(2), 'hex');
// Mimicking the way tx.sign() works
let v = sigBuf[64] + 27;
if (tx._chainId > 0) {
v += tx._chainId * 2 + 8;
}
tx.r = sigBuf.slice(0, 32);
tx.s = sigBuf.slice(32, 64);
tx.v = Buffer.from([v]);
return tx.serialize();
};
/*
* Return the RLP of the given this.tx.
*
* From https://git.io/fh3Sd
*/
export const transactionToRlp = thisTx => {
const tx = getEthereumTx(thisTx);
const { v, r, s } = tx;
// Poor man's serialize without signature.
tx.v = Buffer.from([tx._chainId]);
tx.r = Buffer.from([0]);
tx.s = Buffer.from([0]);
const rlp = '0x' + tx.serialize().toString('hex');
// Restore previous values
tx.v = v;
tx.r = r;
tx.s = s;
return rlp;
};