UNPKG

10.9 kBJavaScriptView Raw
1/* global */
2'use strict'
3
4var $ = require('jquery')
5var yo = require('yo-yo')
6var ethJSUtil = require('ethereumjs-util')
7var BN = ethJSUtil.BN
8var helper = require('./lib/helper')
9var copyToClipboard = require('./app/ui/copy-to-clipboard')
10var css = require('./universal-dapp-styles')
11var MultiParamManager = require('./multiParamManager')
12var remixLib = require('remix-lib')
13var typeConversion = remixLib.execution.typeConversion
14var txExecution = remixLib.execution.txExecution
15var txFormat = remixLib.execution.txFormat
16
17var executionContext = require('./execution-context')
18
19var confirmDialog = require('./app/execution/confirmDialog')
20var modalCustom = require('./app/ui/modal-dialog-custom')
21var modalDialog = require('./app/ui/modaldialog')
22var TreeView = require('./app/ui/TreeView')
23
24function UniversalDAppUI (udapp, registry) {
25 this.udapp = udapp
26 this.registry = registry
27
28 this.compilerData = {contractsDetails: {}}
29 this._deps = {
30 compilersartefacts: registry.get('compilersartefacts').api
31 }
32}
33
34function decodeResponseToTreeView (response, fnabi) {
35 var treeView = new TreeView({
36 extractData: (item, parent, key) => {
37 var ret = {}
38 if (BN.isBN(item)) {
39 ret.self = item.toString(10)
40 ret.children = []
41 } else {
42 ret = treeView.extractDataDefault(item, parent, key)
43 }
44 return ret
45 }
46 })
47 return treeView.render(txFormat.decodeResponse(response, fnabi))
48}
49
50UniversalDAppUI.prototype.renderInstance = function (contract, address, contractName) {
51 var noInstances = document.querySelector('[class^="noInstancesText"]')
52 if (noInstances) {
53 noInstances.parentNode.removeChild(noInstances)
54 }
55 var abi = this.udapp.getABI(contract)
56 return this.renderInstanceFromABI(abi, address, contractName)
57}
58
59// TODO this function was named before "appendChild".
60// this will render an instance: contract name, contract address, and all the public functions
61// basically this has to be called for the "atAddress" (line 393) and when a contract creation succeed
62// this returns a DOM element
63UniversalDAppUI.prototype.renderInstanceFromABI = function (contractABI, address, contractName) {
64 var self = this
65 address = (address.slice(0, 2) === '0x' ? '' : '0x') + address.toString('hex')
66 var instance = yo`<div class="instance ${css.instance} ${css.hidesub}" id="instance${address}"></div>`
67 var context = self.udapp.context()
68
69 var shortAddress = helper.shortenAddress(address)
70 var title = yo`
71 <div class="${css.title} alert alert-secondary p-2">
72 <button class="btn ${css.titleExpander}" onclick="${(e) => { toggleClass(e) }}">
73 <i class="fas fa-angle-right" aria-hidden="true"></i>
74 </button>
75 <div class="input-group ${css.nameNbuts}">
76 <div class="${css.titleText} input-group-prepend">
77 <span class="input-group-text ${css.spanTitleText}">
78 ${contractName} at ${shortAddress} (${context})
79 </span>
80 </div>
81 <div class="btn-group">
82 <button class="btn p-1 btn-secondary">${copyToClipboard(() => address)}</button>
83 </div>
84 </div>
85 </div>
86 `
87
88 var close = yo`
89 <button
90 class="${css.udappClose} p-1 btn btn-secondary"
91 onclick=${remove}
92 title="Remove from the list"
93 >
94 <i class="${css.closeIcon} fas fa-times" aria-hidden="true"></i>
95 </button>`
96 title.querySelector('.btn-group').appendChild(close)
97
98 var contractActionsWrapper = yo`
99 <div class="${css.cActionsWrapper}">
100 </div>
101 `
102
103 function remove () {
104 instance.remove()
105 // @TODO perhaps add a callack here to warn the caller that the instance has been removed
106 }
107
108 function toggleClass (e) {
109 $(instance).toggleClass(`${css.hidesub}`)
110 // e.currentTarget.querySelector('i')
111 e.currentTarget.querySelector('i').classList.toggle(`fa-angle-right`)
112 e.currentTarget.querySelector('i').classList.toggle(`fa-angle-down`)
113 }
114
115 instance.appendChild(title)
116 instance.appendChild(contractActionsWrapper)
117
118 // Add the fallback function
119 var fallback = self.udapp.getFallbackInterface(contractABI)
120 if (fallback) {
121 contractActionsWrapper.appendChild(this.getCallButton({
122 funABI: fallback,
123 address: address,
124 contractAbi: contractABI,
125 contractName: contractName
126 }))
127 }
128
129 $.each(contractABI, (i, funABI) => {
130 if (funABI.type !== 'function') {
131 return
132 }
133 // @todo getData cannot be used with overloaded functions
134 contractActionsWrapper.appendChild(this.getCallButton({
135 funABI: funABI,
136 address: address,
137 contractAbi: contractABI,
138 contractName: contractName
139 }))
140 })
141
142 return instance
143}
144
145// TODO this is used by renderInstance when a new instance is displayed.
146// this returns a DOM element.
147UniversalDAppUI.prototype.getCallButton = function (args) {
148 var self = this
149 // args.funABI, args.address [fun only]
150 // args.contractName [constr only]
151 var lookupOnly = args.funABI.constant
152
153 var outputOverride = yo`<div class=${css.value}></div>` // show return value
154
155 function clickButton (valArr, inputsValues) {
156 var logMsg
157 if (!args.funABI.constant) {
158 logMsg = `transact to ${args.contractName}.${(args.funABI.name) ? args.funABI.name : '(fallback)'}`
159 } else {
160 logMsg = `call to ${args.contractName}.${(args.funABI.name) ? args.funABI.name : '(fallback)'}`
161 }
162
163 var value = inputsValues
164
165 var confirmationCb = (network, tx, gasEstimation, continueTxExecution, cancelCb) => {
166 if (network.name !== 'Main') {
167 return continueTxExecution(null)
168 }
169 var amount = executionContext.web3().fromWei(typeConversion.toInt(tx.value), 'ether')
170 var content = confirmDialog(tx, amount, gasEstimation, self.udapp,
171 (gasPrice, cb) => {
172 let txFeeText, priceStatus
173 // TODO: this try catch feels like an anti pattern, can/should be
174 // removed, but for now keeping the original logic
175 try {
176 var fee = executionContext.web3().toBigNumber(tx.gas).mul(executionContext.web3().toBigNumber(executionContext.web3().toWei(gasPrice.toString(10), 'gwei')))
177 txFeeText = ' ' + executionContext.web3().fromWei(fee.toString(10), 'ether') + ' Ether'
178 priceStatus = true
179 } catch (e) {
180 txFeeText = ' Please fix this issue before sending any transaction. ' + e.message
181 priceStatus = false
182 }
183 cb(txFeeText, priceStatus)
184 },
185 (cb) => {
186 executionContext.web3().eth.getGasPrice((error, gasPrice) => {
187 var warnMessage = ' Please fix this issue before sending any transaction. '
188 if (error) {
189 return cb('Unable to retrieve the current network gas price.' + warnMessage + error)
190 }
191 try {
192 var gasPriceValue = executionContext.web3().fromWei(gasPrice.toString(10), 'gwei')
193 cb(null, gasPriceValue)
194 } catch (e) {
195 cb(warnMessage + e.message, null, false)
196 }
197 })
198 }
199 )
200 modalDialog('Confirm transaction', content,
201 { label: 'Confirm',
202 fn: () => {
203 self.udapp._deps.config.setUnpersistedProperty('doNotShowTransactionConfirmationAgain', content.querySelector('input#confirmsetting').checked)
204 // TODO: check if this is check is still valid given the refactor
205 if (!content.gasPriceStatus) {
206 cancelCb('Given gas price is not correct')
207 } else {
208 var gasPrice = executionContext.web3().toWei(content.querySelector('#gasprice').value, 'gwei')
209 continueTxExecution(gasPrice)
210 }
211 }}, {
212 label: 'Cancel',
213 fn: () => {
214 return cancelCb('Transaction canceled by user.')
215 }
216 })
217 }
218
219 var continueCb = (error, continueTxExecution, cancelCb) => {
220 if (error) {
221 var msg = typeof error !== 'string' ? error.message : error
222 modalDialog('Gas estimation failed', yo`<div>Gas estimation errored with the following message (see below).
223 The transaction execution will likely fail. Do you want to force sending? <br>
224 ${msg}
225 </div>`,
226 {
227 label: 'Send Transaction',
228 fn: () => {
229 continueTxExecution()
230 }}, {
231 label: 'Cancel Transaction',
232 fn: () => {
233 cancelCb()
234 }
235 })
236 } else {
237 continueTxExecution()
238 }
239 }
240
241 var outputCb = (decoded) => {
242 outputOverride.innerHTML = ''
243 outputOverride.appendChild(decoded)
244 }
245
246 var promptCb = (okCb, cancelCb) => {
247 modalCustom.promptPassphrase('Passphrase requested', 'Personal mode is enabled. Please provide passphrase of account', '', okCb, cancelCb)
248 }
249
250 // contractsDetails is used to resolve libraries
251 txFormat.buildData(args.contractName, args.contractAbi, {}, false, args.funABI, args.funABI.type !== 'fallback' ? value : '', (error, data) => {
252 if (!error) {
253 if (!args.funABI.constant) {
254 self.registry.get('logCallback').api(`${logMsg} pending ... `)
255 } else {
256 self.registry.get('logCallback').api(`${logMsg}`)
257 }
258 if (args.funABI.type === 'fallback') data.dataHex = value
259 self.udapp.callFunction(args.address, data, args.funABI, confirmationCb, continueCb, promptCb, (error, txResult) => {
260 if (!error) {
261 var isVM = executionContext.isVM()
262 if (isVM) {
263 var vmError = txExecution.checkVMError(txResult)
264 if (vmError.error) {
265 self.registry.get('logCallback').api(`${logMsg} errored: ${vmError.message} `)
266 return
267 }
268 }
269 if (lookupOnly) {
270 var decoded = decodeResponseToTreeView(executionContext.isVM() ? txResult.result.vm.return : ethJSUtil.toBuffer(txResult.result), args.funABI)
271 outputCb(decoded)
272 }
273 } else {
274 self.registry.get('logCallback').api(`${logMsg} errored: ${error} `)
275 }
276 })
277 } else {
278 self.registry.get('logCallback').api(`${logMsg} errored: ${error} `)
279 }
280 }, (msg) => {
281 self.registry.get('logCallback').api(msg)
282 }, (data, runTxCallback) => {
283 // called for libraries deployment
284 self.udapp.runTx(data, confirmationCb, runTxCallback)
285 })
286 }
287
288 var multiParamManager = new MultiParamManager(lookupOnly, args.funABI, (valArray, inputsValues, domEl) => {
289 clickButton(valArray, inputsValues, domEl)
290 }, self.udapp.getInputs(args.funABI))
291
292 var contractActionsContainer = yo`<div class="${css.contractActionsContainer}" >${multiParamManager.render()}</div>`
293 contractActionsContainer.appendChild(outputOverride)
294
295 return contractActionsContainer
296}
297
298module.exports = UniversalDAppUI