Files
openethereum/js/src/util/tx.js
Jaco Greeff 6d0dfa6dab Backport #6710 & #6714 (#6715)
* [ci skip] js-precompiled 20171011-183908

* Fix estimate gas if from is not provided. (#6714)

* Display vouched overlay on dapps (#6710)

* Remove .only

* Add vouch overlays to dapps

* Cleanup address

* Only run where we have a contentHash

* GitLab kickstart
2017-10-11 22:21:12 +02:00

294 lines
7.9 KiB
JavaScript

// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import BigNumber from 'bignumber.js';
import WalletsUtils from '~/util/wallets';
/**
* The sender is by default (when the UI loads) the
* default dapp address. It can then be modified when
* sending transactions....
*/
let currentSender = '';
let hasCurrentSenderChanged = false;
export function getSender () {
currentSender;
}
export function loadSender (api) {
// If the current sender has been changed
// then don't bother checking changes of the
// default sender
if (hasCurrentSenderChanged) {
return Promise.resolve(currentSender);
}
return api.parity.getNewDappsDefaultAddress()
.then((defaultAccount) => {
currentSender = defaultAccount;
return defaultAccount;
});
}
export function setSender (sender) {
currentSender = sender;
hasCurrentSenderChanged = true;
}
export function trackRequest (api, options, statusCallback) {
const { requestId, transactionHash } = options;
const txHashPromise = transactionHash
? Promise.resolve(transactionHash)
: api.pollMethod('parity_checkRequest', requestId);
return txHashPromise
.then((transactionHash) => {
statusCallback(null, { transactionHash });
return api.pollMethod('eth_getTransactionReceipt', transactionHash, isValidReceipt);
})
.then((transactionReceipt) => {
statusCallback(null, { transactionReceipt });
})
.catch((error) => {
statusCallback(error);
});
}
const isValidReceipt = (receipt) => {
return receipt && receipt.blockNumber && receipt.blockNumber.gt(0);
};
export function getTxOptions (api, func, _options, values = []) {
const options = { ..._options };
const address = options.from;
if (func && func.contract) {
options.to = options.to || func.contract.address;
}
const promise = (!address)
? Promise.resolve(false)
: WalletsUtils.isWallet(api, address);
return promise
.then((isWallet) => {
if (!isWallet) {
return { func, options, values };
}
if (func && func.contract) {
options.data = func.contract.getCallData(func, options, values);
}
if (!options.to) {
return { func, options, values };
}
return WalletsUtils
.getCallArgs(api, options, values)
.then((callArgs) => {
if (!callArgs) {
return { func, options, values };
}
return callArgs;
});
})
.then(({ func, options, values }) => {
if (func) {
options.data = func.contract.getCallData(func, options, values);
}
if (!options.value) {
options.value = new BigNumber(0);
}
return options;
});
}
export function estimateGas (_func, _options, _values = []) {
const { api } = _func.contract;
return getTxOptions(api, _func, _options, _values)
.then((options) => {
return api.eth.estimateGas(options);
});
}
export function postTransaction (_func, _options, _values = []) {
const { api } = _func.contract;
return getTxOptions(api, _func, _options, _values)
.then((options) => {
return api.parity.postTransaction(options);
});
}
export function deployEstimateGas (contract, _options, values) {
const options = { ..._options };
const { api } = contract;
const address = options.from;
return WalletsUtils
.isWallet(api, address)
.then((isWallet) => {
if (!isWallet) {
return contract.deployEstimateGas(options, values);
}
return WalletsUtils
.getDeployArgs(contract, options, values)
.then((callArgs) => {
const { func, options, values } = callArgs;
return func.estimateGas(options, values);
})
.then((gasEst) => {
return [gasEst, gasEst.mul(1.05)];
});
});
}
export function deploy (contract, options, values, skipGasEstimate = false) {
const { api } = contract;
const address = options.from;
const gasEstPromise = skipGasEstimate
? Promise.resolve(null)
: deployEstimateGas(contract, options, values).then(([gasEst, gas]) => gas);
return gasEstPromise
.then((gas) => {
if (gas) {
options.gas = gas.toFixed(0);
}
return WalletsUtils.isWallet(api, address);
})
.then((isWallet) => {
if (!isWallet) {
const encodedOptions = contract._encodeOptions(contract.constructors[0], options, values);
return api.parity.postTransaction(encodedOptions);
}
return WalletsUtils.getDeployArgs(contract, options, values)
.then((callArgs) => {
const { func, options, values } = callArgs;
return func._postTransaction(options, values);
});
});
}
export function parseTransactionReceipt (api, options, receipt) {
if (receipt.gasUsed.eq(options.gas)) {
const error = new Error(`Contract not deployed, gasUsed == ${options.gas.toFixed(0)}`);
return Promise.reject(error);
}
// If regular contract creation, only validate the contract
if (receipt.contractAddress) {
return validateContract(api, receipt.contractAddress);
}
// Otherwise, needs to check for a contract deployment
// from a multisig wallet
const walletResult = WalletsUtils.parseTransactionLogs(api, options, receipt.logs || []);
if (!walletResult) {
const error = new Error('Something went wrong in the contract deployment...');
return Promise.reject(error);
}
if (walletResult.pending) {
return Promise.resolve(null);
}
return validateContract(api, walletResult.contractAddress);
}
function validateContract (api, contractAddress) {
return api.eth
.getCode(contractAddress)
.then((code) => {
if (code === '0x') {
throw new Error('Contract not deployed, getCode returned 0x');
}
return contractAddress;
});
}
export function patchApi (api) {
api.patch = {
...api.patch,
contract: patchContract
};
}
export function patchContract (contract) {
contract._functions.forEach((func) => {
if (!func.constant) {
func._postTransaction = func.postTransaction;
func._estimateGas = func.estimateGas;
func.postTransaction = postTransaction.bind(contract, func);
func.estimateGas = estimateGas.bind(contract, func);
}
});
}
export function checkIfTxFailed (api, tx, gasSent) {
return api.pollMethod('eth_getTransactionReceipt', tx)
.then((receipt) => {
// TODO: Right now, there's no way to tell wether the EVM code crashed.
// Because you usually send a bit more gas than estimated (to make sure
// it gets mined quickly), we transaction probably failed if all the gas
// has been used up.
return receipt.gasUsed.eq(gasSent);
});
}
export function waitForConfirmations (api, tx, confirmations) {
return new Promise((resolve, reject) => {
api.pollMethod('eth_getTransactionReceipt', tx, isValidReceipt)
.then((receipt) => {
let subscription;
api.subscribe('eth_blockNumber', (err, block) => {
if (err) {
reject(err);
} else if (block.minus(confirmations - 1).gte(receipt.blockNumber)) {
if (subscription) {
api.unsubscribe(subscription);
}
resolve();
}
})
.then((_subscription) => {
subscription = _subscription;
})
.catch(reject);
});
});
}