@ -5,26 +5,10 @@ var ethJSUtil = require('ethereumjs-util')
var BN = ethJSUtil . BN
var executionContext = require ( '../../execution-context' )
var modalDialog = require ( '../ui/modaldialog' )
var yo = require ( 'yo-yo' )
var typeConversion = require ( '../../lib/typeConversion' )
var csjs = require ( 'csjs-inject' )
var remixLib = require ( 'remix-lib' )
var styleGuide = remixLib . ui . styleGuide
var styles = styleGuide ( )
var modal = require ( '../ui/modal-dialog-custom' )
var typeConversion = require ( '../../lib/typeConversion' )
var css = csjs `
. txInfoBox {
$ { styles . rightPanel . compileTab . box _CompileContainer } ; // add askToConfirmTXContainer to Remix and then replace this styling
}
. wrapword {
white - space : pre - wrap ; /* Since CSS 2.1 */
white - space : - moz - pre - wrap ; /* Mozilla, since 1999 */
white - space : - pre - wrap ; /* Opera 4-6 */
white - space : - o - pre - wrap ; /* Opera 7 */
word - wrap : break - word ; /* Internet Explorer 5.5+ */
}
`
var confirmDialog = require ( './confirmDialog' )
function TxRunner ( vmaccounts , api ) {
this . _api = api
@ -43,150 +27,179 @@ TxRunner.prototype.rawRun = function (args, cb) {
run ( this , args , Date . now ( ) , cb )
}
TxRunner . prototype . execute = function ( args , callback ) {
var self = this
function execute ( gasPrice ) {
if ( gasPrice ) tx . gasPrice = executionContext . web3 ( ) . toHex ( gasPrice )
function executeTx ( tx , gasPrice , api , callback ) {
if ( gasPrice ) tx . gasPrice = executionContext . web3 ( ) . toHex ( gasPrice )
if ( self . _api . personalMode ( ) ) {
modal . promptPassphrase ( null , 'Personal mode is enabled. Please provide passphrase of account ' + tx . from , '' , ( value ) => {
sendTransaction ( executionContext . web3 ( ) . personal . sendTransaction , tx , value , callback )
} , ( ) => {
return callback ( 'Canceled by user.' )
} )
} else {
sendTransaction ( executionContext . web3 ( ) . eth . sendTransaction , tx , null , callback )
}
if ( api . personalMode ( ) ) {
modal . promptPassphrase ( null , 'Personal mode is enabled. Please provide passphrase of account ' + tx . from , '' , ( value ) => {
sendTransaction ( executionContext . web3 ( ) . personal . sendTransaction , tx , value , callback )
} , ( ) => {
return callback ( 'Canceled by user.' )
} )
} else {
sendTransaction ( executionContext . web3 ( ) . eth . sendTransaction , tx , null , callback )
}
}
TxRunner . prototype . execute = function ( args , callback ) {
var self = this
var from = args . from
var to = args . to
var data = args . data
if ( data . slice ( 0 , 2 ) !== '0x' ) {
data = '0x' + data
}
var value = args . value
var gasLimit = args . gasLimit
var tx
if ( ! executionContext . isVM ( ) ) {
tx = {
from : from ,
to : to ,
data : data ,
value : value
self . runInNode ( args . from , args . to , data , args . value , args . gasLimit , args . useCall , callback )
} else {
try {
self . runInVm ( args . from , args . to , data , args . value , args . gasLimit , args . useCall , callback )
} catch ( e ) {
callback ( e , null )
}
}
}
if ( args . useCall ) {
tx . gas = gasLimit
executionContext . web3 ( ) . eth . call ( tx , function ( error , result ) {
callback ( error , {
result : result ,
transactionHash : result . transactionHash
} )
} )
} else {
executionContext . web3 ( ) . eth . estimateGas ( tx , function ( err , gasEstimation ) {
if ( err ) {
return callback ( err , gasEstimation )
}
var blockGasLimit = executionContext . currentblockGasLimit ( )
// NOTE: estimateGas very likely will return a large limit if execution of the code failed
// we want to be able to run the code in order to debug and find the cause for the failure
TxRunner . prototype . runInVm = function ( from , to , data , value , gasLimit , useCall , callback ) {
const self = this
var account = self . vmaccounts [ from ]
if ( ! account ) {
return callback ( 'Invalid account selected' )
}
var tx = new EthJSTX ( {
nonce : new BN ( account . nonce ++ ) ,
gasPrice : new BN ( 1 ) ,
gasLimit : new BN ( gasLimit , 10 ) ,
to : to ,
value : new BN ( value , 10 ) ,
data : new Buffer ( data . slice ( 2 ) , 'hex' )
} )
tx . sign ( account . privateKey )
const coinbases = [ '0x0e9281e9c6a0808672eaba6bd1220e144c9bb07a' , '0x8945a1288dc78a6d8952a92c77aee6730b414778' , '0x94d76e24f818426ae84aa404140e8d5f60e10e7e' ]
const difficulties = [ new BN ( '69762765929000' , 10 ) , new BN ( '70762765929000' , 10 ) , new BN ( '71762765929000' , 10 ) ]
var block = new EthJSBlock ( {
header : {
timestamp : new Date ( ) . getTime ( ) / 1000 | 0 ,
number : self . blockNumber ,
coinbase : coinbases [ self . blockNumber % coinbases . length ] ,
difficulty : difficulties [ self . blockNumber % difficulties . length ] ,
gasLimit : new BN ( gasLimit , 10 ) . imuln ( 2 )
} ,
transactions : [ ] ,
uncleHeaders : [ ]
} )
if ( ! useCall ) {
++ self . blockNumber
} else {
executionContext . vm ( ) . stateManager . checkpoint ( )
}
var warnEstimation = " An important gas estimation might also be the sign of a problem in the contract code. Please check loops and be sure you did not sent value to a non payable function (that's also the reason of strong gas estimation)."
if ( gasEstimation > gasLimit ) {
return callback ( 'Gas required exceeds limit: ' + gasLimit + '. ' + warnEstimation )
}
if ( gasEstimation > blockGasLimit ) {
return callback ( 'Gas required exceeds block gas limit: ' + gasLimit + '. ' + warnEstimation )
}
executionContext . vm ( ) . runTx ( { block : block , tx : tx , skipBalance : true , skipNonce : true } , function ( err , result ) {
if ( useCall ) {
executionContext . vm ( ) . stateManager . revert ( function ( ) { } )
}
err = err ? err . message : err
result . status = '0x' + result . vm . exception . toString ( 16 )
callback ( err , {
result : result ,
transactionHash : ethJSUtil . bufferToHex ( new Buffer ( tx . hash ( ) ) )
} )
} )
}
tx . gas = gasEstimation
TxRunner . prototype . runInNode = function ( from , to , data , value , gasLimit , useCall , callback ) {
const self = this
var tx = { from : from , to : to , data : data , value : value }
if ( ! self . _api . config . getUnpersistedProperty ( 'doNotShowTransactionConfirmationAgain' ) ) {
self . _api . detectNetwork ( ( err , network ) => {
if ( err ) {
console . log ( err )
} else {
if ( network . name === 'Main' ) {
var content = confirmDialog ( tx , gasEstimation , self )
modalDialog ( 'Confirm transaction' , content ,
{ label : 'Confirm' ,
fn : ( ) => {
self . _api . config . setUnpersistedProperty ( 'doNotShowTransactionConfirmationAgain' , content . querySelector ( 'input#confirmsetting' ) . checked )
if ( ! content . gasPriceStatus ) {
callback ( 'Given gas grice is not correct' )
} else {
var gasPrice = executionContext . web3 ( ) . toWei ( content . querySelector ( '#gasprice' ) . value , 'gwei' )
execute ( gasPrice )
}
} } , {
label : 'Cancel' ,
fn : ( ) => {
return callback ( 'Transaction canceled by user.' )
}
} )
} else {
execute ( )
}
}
} )
} else {
execute ( )
}
if ( useCall ) {
tx . gas = gasLimit
return executionContext . web3 ( ) . eth . call ( tx , function ( error , result ) {
callback ( error , {
result : result ,
transactionHash : result . transactionHash
} )
} )
}
executionContext . web3 ( ) . eth . estimateGas ( tx , function ( err , gasEstimation ) {
if ( err ) {
return callback ( err , gasEstimation )
}
} else {
try {
var account = self . vmaccounts [ from ]
if ( ! account ) {
return callback ( 'Invalid account selected' )
}
tx = new EthJSTX ( {
nonce : new BN ( account . nonce ++ ) ,
gasPrice : new BN ( 1 ) ,
gasLimit : new BN ( gasLimit , 10 ) ,
to : to ,
value : new BN ( value , 10 ) ,
data : new Buffer ( data . slice ( 2 ) , 'hex' )
} )
tx . sign ( account . privateKey )
var blockGasLimit = executionContext . currentblockGasLimit ( )
// NOTE: estimateGas very likely will return a large limit if execution of the code failed
// we want to be able to run the code in order to debug and find the cause for the failure
const coinbases = [ '0x0e9281e9c6a0808672eaba6bd1220e144c9bb07a' , '0x8945a1288dc78a6d8952a92c77aee6730b414778' , '0x94d76e24f818426ae84aa404140e8d5f60e10e7e' ]
const difficulties = [ new BN ( '69762765929000' , 10 ) , new BN ( '70762765929000' , 10 ) , new BN ( '71762765929000' , 10 ) ]
var block = new EthJSBlock ( {
header : {
timestamp : new Date ( ) . getTime ( ) / 1000 | 0 ,
number : self . blockNumber ,
coinbase : coinbases [ self . blockNumber % coinbases . length ] ,
difficulty : difficulties [ self . blockNumber % difficulties . length ] ,
gasLimit : new BN ( gasLimit , 10 ) . imuln ( 2 )
} ,
transactions : [ ] ,
uncleHeaders : [ ]
} )
if ( ! args . useCall ) {
++ self . blockNumber
} else {
executionContext . vm ( ) . stateManager . checkpoint ( )
var warnEstimation = " An important gas estimation might also be the sign of a problem in the contract code. Please check loops and be sure you did not sent value to a non payable function (that's also the reason of strong gas estimation)."
if ( gasEstimation > gasLimit ) {
return callback ( 'Gas required exceeds limit: ' + gasLimit + '. ' + warnEstimation )
}
if ( gasEstimation > blockGasLimit ) {
return callback ( 'Gas required exceeds block gas limit: ' + gasLimit + '. ' + warnEstimation )
}
tx . gas = gasEstimation
if ( self . _api . config . getUnpersistedProperty ( 'doNotShowTransactionConfirmationAgain' ) ) {
return executeTx ( tx , null , self . _api , callback )
}
self . _api . detectNetwork ( ( err , network ) => {
if ( err ) {
console . log ( err )
return
}
if ( network . name !== 'Main' ) {
return executeTx ( tx , null , self . _api , callback )
}
executionContext . vm ( ) . runTx ( { block : block , tx : tx , skipBalance : true , skipNonce : true } , function ( err , result ) {
if ( args . useCall ) {
executionContext . vm ( ) . stateManager . revert ( function ( ) { } )
var amount = executionContext . web3 ( ) . fromWei ( typeConversion . toInt ( tx . value ) , 'ether' )
var content = confirmDialog ( tx , amount , gasEstimation , self ,
( gasPrice , cb ) => {
let txFeeText , priceStatus
// TODO: this try catch feels like an anti pattern, can/should be
// removed, but for now keeping the original logic
try {
var fee = executionContext . web3 ( ) . toBigNumber ( tx . gas ) . mul ( executionContext . web3 ( ) . toBigNumber ( executionContext . web3 ( ) . toWei ( gasPrice . toString ( 10 ) , 'gwei' ) ) )
txFeeText = ' ' + executionContext . web3 ( ) . fromWei ( fee . toString ( 10 ) , 'ether' ) + ' Ether'
priceStatus = true
} catch ( e ) {
txFeeText = ' Please fix this issue before sending any transaction. ' + e . message
priceStatus = false
}
cb ( priceStatus , txFeeText )
} ,
( cb ) => {
executionContext . web3 ( ) . eth . getGasPrice ( ( error , gasPrice ) => {
var warnMessage = ' Please fix this issue before sending any transaction. '
if ( error ) {
return cb ( 'Unable to retrieve the current network gas price.' + warnMessage + error )
}
try {
var gasPriceValue = executionContext . web3 ( ) . fromWei ( gasPrice . toString ( 10 ) , 'gwei' )
cb ( null , gasPriceValue )
} catch ( e ) {
cb ( warnMessage + e . message , null , false )
}
} )
}
err = err ? err . message : err
result . status = '0x' + result . vm . exception . toString ( 16 )
callback ( err , {
result : result ,
transactionHash : ethJSUtil . bufferToHex ( new Buffer ( tx . hash ( ) ) )
} )
} )
} catch ( e ) {
callback ( e , null )
}
}
)
modalDialog ( 'Confirm transaction' , content ,
{ label : 'Confirm' ,
fn : ( ) => {
self . _api . config . setUnpersistedProperty ( 'doNotShowTransactionConfirmationAgain' , content . querySelector ( 'input#confirmsetting' ) . checked )
if ( ! content . gasPriceStatus ) {
return callback ( 'Given gas grice is not correct' )
}
var gasPrice = executionContext . web3 ( ) . toWei ( content . querySelector ( '#gasprice' ) . value , 'gwei' )
executeTx ( tx , gasPrice , self . _api , callback )
} } , {
label : 'Cancel' ,
fn : ( ) => {
return callback ( 'Transaction canceled by user.' )
}
} )
} )
} )
}
function tryTillResponse ( txhash , done ) {
@ -234,57 +247,4 @@ function run (self, tx, stamp, callback) {
}
}
function confirmDialog ( tx , gasEstimation , self ) {
var amount = executionContext . web3 ( ) . fromWei ( typeConversion . toInt ( tx . value ) , 'ether' )
var input = yo ` <input id='confirmsetting' type="checkbox"> `
var el = yo `
< div >
< div > You are creating a transaction on the main network . Click confirm if you are sure to continue . < / d i v >
< div class = $ { css . txInfoBox } >
< div > From : $ { tx . from } < / d i v >
< div > To : $ { tx . to ? tx . to : '(Contract Creation)' } < / d i v >
< div > Amount : $ { amount } Ether < / d i v >
< div > Gas estimation : $ { gasEstimation } < / d i v >
< div > Gas limit : $ { tx . gas } < / d i v >
< div > Gas price : < input id = 'gasprice' oninput = $ { gasPriceChanged } / > Gwei < span > ( visit < a target = '_blank' href = 'https://ethgasstation.info' > ethgasstation . info < / a > t o g e t m o r e i n f o a b o u t g a s p r i c e ) < / s p a n > < / d i v >
< div > Max transaction fee : < span id = 'txfee' > < / s p a n > < / d i v >
< div > Data : < / d i v >
< pre class = $ { css . wrapword } > $ { tx . data } < / p r e >
< / d i v >
< div class = $ { css . checkbox } >
$ { input }
< i class = "fa fa-exclamation-triangle" aria - hidden = "true" > < / i > D o n o t a s k f o r c o n f i r m a t i o n a g a i n . ( t h e s e t t i n g w i l l n o t b e p e r s i s t e d f o r t h e n e x t p a g e r e l o a d )
< / d i v >
< / d i v >
`
var warnMessage = ' Please fix this issue before sending any transaction. '
function gasPriceChanged ( ) {
try {
var gasPrice = el . querySelector ( '#gasprice' ) . value
var fee = executionContext . web3 ( ) . toBigNumber ( tx . gas ) . mul ( executionContext . web3 ( ) . toBigNumber ( executionContext . web3 ( ) . toWei ( gasPrice . toString ( 10 ) , 'gwei' ) ) )
el . querySelector ( '#txfee' ) . innerHTML = ' ' + executionContext . web3 ( ) . fromWei ( fee . toString ( 10 ) , 'ether' ) + ' Ether'
el . gasPriceStatus = true
} catch ( e ) {
el . querySelector ( '#txfee' ) . innerHTML = warnMessage + e . message
el . gasPriceStatus = false
}
}
executionContext . web3 ( ) . eth . getGasPrice ( ( error , gasPrice ) => {
if ( error ) {
el . querySelector ( '#txfee' ) . innerHTML = 'Unable to retrieve the current network gas price.' + warnMessage + error
} else {
try {
el . querySelector ( '#gasprice' ) . value = executionContext . web3 ( ) . fromWei ( gasPrice . toString ( 10 ) , 'gwei' )
gasPriceChanged ( )
} catch ( e ) {
el . querySelector ( '#txfee' ) . innerHTML = warnMessage + e . message
el . gasPriceStatus = false
}
}
} )
return el
}
module . exports = TxRunner