@ -1,68 +1,116 @@
'use strict'
var $ = require ( 'jquery' )
var yo = require ( 'yo-yo' )
var helper = require ( '../../lib/helper.js' )
var remixLib = require ( 'remix-lib' )
var ethJSUtil = require ( 'ethereumjs-util' )
var csjs = require ( 'csjs-inject' )
var txExecution = remixLib . execution . txExecution
var txFormat = remixLib . execution . txFormat
var txHelper = remixLib . execution . txHelper
var EventManager = remixLib . EventManager
var helper = require ( '../../lib/helper.js' )
var executionContext = require ( '../../execution-context' )
var modalDialogCustom = require ( '../ui/modal-dialog-custom' )
var copyToClipboard = require ( '../ui/copy-to-clipboard' )
var Card = require ( '../ui/card' )
var Recorder = require ( '../../recorder' )
var EventManager = remixLib . EventManager
var addTooltip = require ( '../ui/tooltip' )
var ethJSUtil = require ( 'ethereumjs-util' )
var MultiParamManager = require ( '../../multiParamManager' )
var csjs = require ( 'csjs-inject' )
var css = require ( './styles/run-tab-styles' )
var instanceContainer = yo ` <div class=" ${ css . instanceContainer } "></div> `
var noInstancesText = yo ` <div class=" ${ css . noInstancesText } ">0 contract Instances</div> `
var pendingTxsText = yo ` <span>0 pending transactions</span> `
var MultiParamManager = require ( '../../multiParamManager' )
function runTab ( appAPI = { } , appEvents = { } , opts = { } ) {
var container = yo ` <div class=" ${ css . runTabView } " id="runTabView" ></div> `
/ * - - - - - - - - - - - - - - - - - - - - - - - - -
VARIABLES
-- -- -- -- -- -- -- -- -- -- -- -- -- - * /
var self = this
var event = new EventManager ( )
appEvents . eventManager = event
self . _view = { }
self . data = {
count : 0 ,
text : ` All transactions (deployed contracts and function executions)
in this environment can be saved and replayed in
another environment . i . e . Transactions created in
Javascript VM can be replayed in the Injected Web3 . `
}
self . _view . recorderCount = yo ` <span>0</span> `
self . _view . instanceContainer = yo ` <div class=" ${ css . instanceContainer } "></div> `
self . _view . clearInstanceElement = yo `
< i class = "${css.clearinstance} ${css.icon} fa fa-trash" onclick = $ { ( ) => clearInstanceList ( self ) }
title = "Clear instances list and reset recorder" aria - hidden = "true" >
< / i > `
self . _view . instanceContainerTitle = yo `
< div class = $ { css . instanceContainerTitle }
title = "Autogenerated generic user interfaces for interaction with deployed contracts" >
UI for Deployed Contracts
$ { self . _view . clearInstanceElement }
< / d i v > `
self . _view . noInstancesText = yo `
< div class = "${css.noInstancesText}" >
Currently you have no contract instances to interact with .
< / d i v > `
var clearInstanceElement = yo ` <i class=" ${ css . clearinstance } ${ css . icon } fa fa-trash" title="Clear Instances List" aria-hidden="true"></i> `
clearInstanceElement . addEventListener ( 'click' , ( ) => {
event . trigger ( 'clearInstance' , [ ] )
} )
var recorderInterface = makeRecorder ( event , appAPI , appEvents , opts )
var pendingTxsContainer = yo `
< div class = "${css.pendingTxsContainer}" >
< div class = "${css.pendingTxsText}" >
$ { pendingTxsText }
< span class = "${css.transactionActions}" >
var container = yo ` <div class=" ${ css . runTabView } " id="runTabView" ></div> `
var recorderInterface = makeRecorder ( appAPI , appEvents , opts , self )
self . _view . collapsedView = yo `
< div class = $ { css . recorderCollapsedView } >
< div class = $ { css . recorderCount } > $ { self . _view . recorderCount } < / d i v >
< / d i v > `
self . _view . expandedView = yo `
< div class = $ { css . recorderExpandedView } >
< div class = $ { css . recorderDescription } >
$ { self . data . text }
< / d i v >
< div class = "${css.transactionActions}" >
$ { recorderInterface . recordButton }
$ { recorderInterface . runButton }
$ { clearInstanceElement }
< / s p a n >
< / d i v >
< / d i v >
< / d i v > `
self . recorderOpts = {
title : 'Events recorded:' ,
collapsedView : self . _view . collapsedView
}
var recorderCard = new Card ( { } , { } , self . recorderOpts )
recorderCard . event . register ( 'expandCollapseCard' , ( arrow , body , status ) => {
body . innerHTML = ''
status . innerHTML = ''
if ( arrow === 'up' ) {
status . appendChild ( self . _view . collapsedView )
body . appendChild ( self . _view . expandedView )
} else if ( arrow === 'down' ) {
status . appendChild ( self . _view . collapsedView )
}
} )
/ * - - - - - - - - - - - - - - - - - - - - - - - - -
MAIN HTML ELEMENT
-- -- -- -- -- -- -- -- -- -- -- -- -- - * /
var el = yo `
< div >
$ { settings ( container , appAPI , appEvents , opts ) }
$ { contractDropdown ( event , appAPI , appEvents , opts , instanceContainer ) }
$ { pendingTxsContainer }
$ { instanceContainer }
$ { contractDropdown ( event , appAPI , appEvents , opts , self ) }
$ { recorderCard . render ( ) }
$ { self . _view . instanceContainer }
< / d i v >
`
container . appendChild ( el )
// PENDING transactions
function updatePendingTxs ( container , appAPI ) {
var pendingCount = Object . keys ( opts . udapp . pendingTransactions ( ) ) . length
pendingTxsText . innerText = pendingCount + ' pending transactions'
}
/ * - - - - - - - - - - - - - - - - - - - - - - - - -
HELPER FUNCTIONS
-- -- -- -- -- -- -- -- -- -- -- -- -- - * /
// DROPDOWN
var selectExEnv = el . querySelector ( '#selectExEnvOptions' )
function clearInstanceList ( self ) {
event . trigger ( 'clearInstance' , [ ] )
}
function setFinalContext ( ) {
// set the final context. Cause it is possible that this is not the one we've originaly selected
selectExEnv . value = executionContext . getProvider ( )
@ -87,20 +135,23 @@ function runTab (appAPI = {}, appEvents = {}, opts = {}) {
modalDialogCustom . alert ( alertMsg )
} , setFinalContext )
} )
selectExEnv . value = executionContext . getProvider ( )
executionContext . event . register ( 'contextChanged' , ( context , silent ) => {
setFinalContext ( )
} )
fillAccountsList ( appAPI , opts , el )
setInterval ( ( ) => {
updateAccountBalances ( container , appAPI )
updatePendingTxs ( container , appAPI )
} , 10000 )
event . register ( 'clearInstance' , ( ) => {
var instanceContainer = self . _view . instanceContainer
var instanceContainerTitle = self . _view . instanceContainerTitle
instanceContainer . innerHTML = '' // clear the instances list
noInstancesText . style . display = 'block'
instanceContainer . appendChild ( noInstancesText )
instanceContainer . appendChild ( instanceContainerTitle )
instanceContainer . appendChild ( self . _view . noInstancesText )
} )
return { render ( ) { return container } }
}
@ -135,29 +186,39 @@ function updateAccountBalances (container, appAPI) {
/ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
RECORDER
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- * /
function makeRecorder ( events , appAPI , appEvents , opts ) {
function makeRecorder ( appAPI , appEvents , opts , self ) {
var recorder = new Recorder ( opts . compiler , {
events : {
udapp : appEvents . udapp ,
executioncontext : executionContext . event ,
runtab : events
runtab : appEvents . eventManager
} ,
api : appAPI
} )
recorder . event . register ( 'newTxRecorded' , ( count ) => {
self . data . count = count
self . _view . recorderCount . innerText = count
} )
recorder . event . register ( 'cleared' , ( ) => {
self . data . count = 0
self . _view . recorderCount . innerText = 0
} )
var css2 = csjs `
. container ,
. runTxs ,
. recorder {
}
. container { }
. runTxs { }
. recorder { }
`
var recordButton = yo ` <i class="fa fa-floppy-o savetransaction ${ css2 . recorder } ${ css . icon } " title="Save Transactions" aria-hidden="true"></i> `
var runButton = yo ` <i class="fa fa-play runtransaction ${ css2 . runTxs } ${ css . icon } " title="Run Transactions" aria-hidden="true"></i> `
var recordButton = yo `
< i class = "fa fa-floppy-o savetransaction ${css2.recorder} ${css.icon}"
onclick = $ { triggerRecordButton } title = "Save Transactions" aria - hidden = "true" >
< / i > `
recordButton . onclick = ( ) => {
function triggerRecordButton ( ) {
var txJSON = JSON . stringify ( recorder . getAll ( ) , null , 2 )
var path = appAPI . currentPath ( )
modalDialogCustom . prompt ( null , 'save ran transactions to file (e.g. `scenario.json`). The file is going to be saved under ' + path , 'scenario.json' , input => {
modalDialogCustom . prompt ( null , 'Transactions will be saved in a file under ' + path , 'scenario.json' , input => {
var fileProvider = appAPI . fileProviderOf ( path )
if ( fileProvider ) {
var newFile = path + input
@ -172,7 +233,13 @@ function makeRecorder (events, appAPI, appEvents, opts) {
}
} )
}
runButton . onclick = ( ) => {
/ *
@ TODO
update account address in scenario . json
popup if scenario . json not open - "Open a file with transactions you want to replay and click play again"
* /
var currentFile = appAPI . config . get ( 'currentFile' )
appAPI . fileProviderOf ( currentFile ) . get ( currentFile , ( error , json ) => {
if ( error ) {
@ -183,16 +250,17 @@ function makeRecorder (events, appAPI, appEvents, opts) {
var obj = JSON . parse ( json )
var txArray = obj . transactions || [ ]
var accounts = obj . accounts || [ ]
var options = obj . options
var abis = obj . abis
var options = obj . options || { }
var abis = obj . abis || { }
var linkReferences = obj . linkReferences || { }
} catch ( e ) {
return modalDialogCustom . alert ( 'Invalid Scenario File, please try again' )
}
if ( txArray . length ) {
noInstancesText . style . display = 'none'
var noInstancesText = self . _view . noInstancesText
if ( noInstancesText . parentNode ) { noInstancesText . parentNode . removeChild ( noInstancesText ) }
recorder . run ( txArray , accounts , options , abis , linkReferences , opts . udapp , ( abi , address , contractName ) => {
instanceContainer . appendChild ( opts . udappUI . renderInstanceFromABI ( abi , address , contractName ) )
self . _view . instanceContainer . appendChild ( opts . udappUI . renderInstanceFromABI ( abi , address , contractName ) )
} )
}
} else {
@ -201,14 +269,18 @@ function makeRecorder (events, appAPI, appEvents, opts) {
}
} )
}
return { recordButton , runButton }
}
/ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
section CONTRACT DROPDOWN and BUTTONS
CONTRACT ( deploy or access deployed )
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- * /
function contractDropdown ( events , appAPI , appEvents , opts , instanceContainer ) {
instanceContainer . appendChild ( noInstancesText )
function contractDropdown ( events , appAPI , appEvents , opts , self ) {
var instanceContainer = self . _view . instanceContainer
var instanceContainerTitle = self . _view . instanceContainerTitle
instanceContainer . appendChild ( instanceContainerTitle )
instanceContainer . appendChild ( self . _view . noInstancesText )
var compFails = yo ` <i title="Contract compilation failed. Please check the compile tab for more information." class="fa fa-times-circle ${ css . errorIcon } " ></i> `
appEvents . compiler . register ( 'compilationFinished' , function ( success , data , source ) {
getContractNames ( success , data )
@ -222,7 +294,6 @@ function contractDropdown (events, appAPI, appEvents, opts, instanceContainer) {
} )
var atAddressButtonInput = yo ` <input class=" ${ css . input } ataddressinput" placeholder="Load contract from Address" title="atAddress" /> `
var selectContractNames = yo ` <select class=" ${ css . contractNames } " disabled></select> `
function getSelectedContract ( ) {
@ -236,6 +307,7 @@ function contractDropdown (events, appAPI, appEvents, opts, instanceContainer) {
return null
}
appAPI . getSelectedContract = getSelectedContract
var createPanel = yo ` <div class=" ${ css . button } "></div> `
var el = yo `
@ -247,7 +319,7 @@ function contractDropdown (events, appAPI, appEvents, opts, instanceContainer) {
$ { createPanel }
< div class = "${css.button}" >
$ { atAddressButtonInput }
< div class = "${css.atAddress}" onclick = $ { function ( ) { loadFromAddress ( appAPI ) } } > At Addr ess < / d i v >
< div class = "${css.atAddress}" onclick = $ { function ( ) { loadFromAddress ( appAPI ) } } > Acc ess < / d i v >
< / d i v >
< / d i v >
< / d i v >
@ -269,7 +341,7 @@ function contractDropdown (events, appAPI, appEvents, opts, instanceContainer) {
selectContractNames . addEventListener ( 'change' , setInputParamsPlaceHolder )
// ADD BUTTONS AT ADDRESS AND CREAT E
// DEPLOY INSTANC E
function createInstance ( args ) {
var selectedContract = getSelectedContract ( )
@ -292,7 +364,8 @@ function contractDropdown (events, appAPI, appEvents, opts, instanceContainer) {
return
}
}
noInstancesText . style . display = 'none'
var noInstancesText = self . _view . noInstancesText
if ( noInstancesText . parentNode ) { noInstancesText . parentNode . removeChild ( noInstancesText ) }
var address = isVM ? txResult . result . createdAddress : txResult . result . contractAddress
instanceContainer . appendChild ( opts . udappUI . renderInstance ( selectedContract . contract . object , address , selectContractNames . value ) )
} else {
@ -310,8 +383,10 @@ function contractDropdown (events, appAPI, appEvents, opts, instanceContainer) {
} )
}
// ACCESS DEPLOYED INSTANCE
function loadFromAddress ( appAPI ) {
noInstancesText . style . display = 'none'
var noInstancesText = self . _view . noInstancesText
if ( noInstancesText . parentNode ) { noInstancesText . parentNode . removeChild ( noInstancesText ) }
var contractNames = document . querySelector ( ` . ${ css . contractNames . classNames [ 0 ] } ` )
var address = atAddressButtonInput . value
if ( ! ethJSUtil . isValidAddress ( address ) ) {
@ -353,12 +428,11 @@ function contractDropdown (events, appAPI, appEvents, opts, instanceContainer) {
return el
}
/ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
section SETTINGS : Environment , Account , Gas , Value
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- * /
function settings ( container , appAPI , appEvents , opts ) {
// SETTINGS HTML
// VARIABLES
var net = yo ` <span class= ${ css . network } ></span> `
const updateNetwork = ( ) => {
executionContext . detectNetwork ( ( err , { id , name } = { } ) => {
@ -370,19 +444,7 @@ function settings (container, appAPI, appEvents, opts) {
}
} )
}
setInterval ( updateNetwork , 5000 )
function newAccount ( ) {
opts . udapp . newAccount ( '' , ( error , address ) => {
if ( ! error ) {
container . querySelector ( '#txorigin' ) . appendChild ( yo ` <option value= ${ address } > ${ address } </option> ` )
addTooltip ( ` account ${ address } created ` )
} else {
addTooltip ( 'Cannot create an account: ' + error )
}
} )
}
var el = yo `
< div class = "${css.settings}" >
var environmentEl = yo `
< div class = "${css.crow}" >
< div id = "selectExEnv" class = "${css.col1_1}" >
Environment
@ -392,41 +454,37 @@ function settings (container, appAPI, appEvents, opts) {
< select id = "selectExEnvOptions" onchange = $ { updateNetwork } class = "${css.select}" >
< option id = "vm-mode"
title = "Execution environment does not connect to any node, everything is local and in memory only."
value = "vm"
checked name = "executionContext" >
JavaScript VM
value = "vm" checked name = "executionContext" > JavaScript VM
< / o p t i o n >
< option id = "injected-mode"
title = "Execution environment has been provided by Mist or similar provider."
value = "injected"
checked name = "executionContext" >
Injected Web3
title = "Execution environment has been provided by Metamask or similar provider."
value = "injected" checked name = "executionContext" > Injected Web3
< / o p t i o n >
< option id = "web3-mode"
title = " Execution environment connects to node at localhost ( or via IPC if available ) , transactions will be sent to the network and can cause loss of money or worse !
If this page is served via https and you access your node via http , it might not work . In this case , try cloning the repository and serving it via http . "
value = "web3"
name = "executionContext" >
Web3 Provider
value = "web3" name = "executionContext" > Web3 Provider
< / o p t i o n >
< / s e l e c t >
< a href = "https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md" target = "_blank" > < i class = "${css.icon} fa fa-info" > < / i > < / a >
< / d i v >
< / d i v >
`
var accountEl = yo `
< div class = "${css.crow}" >
< div class = "${css.col1_1}" > Account < / d i v >
< select name = "txorigin" class = "${css.select}" id = "txorigin" > < / s e l e c t >
$ { copyToClipboard ( ( ) => document . querySelector ( '#runTabView #txorigin' ) . value ) }
< i class = "fa fa-plus-square-o ${css.createAccount} ${css.icon}" aria - hidden = "true" onclick = $ { newAccount } title = "Create a new account" > < / i >
< i class = "fa fa-plus-circle ${css.icon}" aria - hidden = "true" onclick = $ { newAccount } title = "Create a new account" > < / i >
< / d i v >
`
var gasPriceEl = yo `
< div class = "${css.crow}" >
< div class = "${css.col1_1}" > Gas limit < / d i v >
< input type = "number" class = "${css.col2}" id = "gasLimit" value = "3000000" >
< / d i v >
< div class = "${css.crow}" style = "display: none" >
< div class = "${css.col1_1}" > Gas Price < / d i v >
< input type = "number" class = "${css.col2}" id = "gasPrice" value = "0" >
< / d i v >
`
var valueEl = yo `
< div class = "${css.crow}" >
< div class = "${css.col1_1}" > Value < / d i v >
< input type = "text" class = "${css.col2_1}" id = "value" value = "0" title = "Enter the value and choose the unit" >
@ -437,15 +495,35 @@ function settings (container, appAPI, appEvents, opts) {
< option data - unit = "ether" > ether < / o p t i o n >
< / s e l e c t >
< / d i v >
`
// DOM ELEMENT
var el = yo `
< div class = "${css.settings}" >
$ { environmentEl }
$ { accountEl }
$ { gasPriceEl }
$ { valueEl }
< / d i v >
`
// EVENTS
// HELPER FUNCTIONS AND EVENTS
appEvents . udapp . register ( 'transactionExecuted' , ( error , from , to , data , lookupOnly , txResult ) => {
if ( error ) return
if ( ! lookupOnly ) el . querySelector ( '#value' ) . value = '0'
updateAccountBalances ( container , appAPI )
} )
setInterval ( updateNetwork , 5000 )
function newAccount ( ) {
appAPI . newAccount ( '' , ( error , address ) => {
if ( ! error ) {
container . querySelector ( '#txorigin' ) . appendChild ( yo ` <option value= ${ address } > ${ address } </option> ` )
addTooltip ( ` account ${ address } created ` )
} else {
addTooltip ( 'Cannot create an account: ' + error )
}
} )
}
return el
}