checking for receive among with fallback

created separate section:
added error report line, disableing should be added
pull/1/head
LianaHus 5 years ago committed by Liana Husikyan
parent ff46e3a18a
commit 9df45242ba
  1. 1096
      package-lock.json
  2. 2
      src/app/tabs/runTab/model/recorder.js
  3. 7
      src/app/ui/multiParamManager.js
  4. 332
      src/app/ui/universal-dapp-ui.js

1096
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -212,6 +212,8 @@ class Recorder {
fnABI = txHelper.getConstructorInterface(abi) fnABI = txHelper.getConstructorInterface(abi)
} else if (tx.record.type === 'fallback') { } else if (tx.record.type === 'fallback') {
fnABI = txHelper.getFallbackInterface(abi) fnABI = txHelper.getFallbackInterface(abi)
} else if (tx.record.type === 'receive') {
fnABI = txHelper.getReceiveInterface(abi)
} else { } else {
fnABI = txHelper.getFunction(abi, record.name + record.inputs) fnABI = txHelper.getFunction(abi, record.name + record.inputs)
} }

@ -113,7 +113,7 @@ class MultiParamManager {
} else if (this.funABI.name) { } else if (this.funABI.name) {
title = this.funABI.name title = this.funABI.name
} else { } else {
title = '(fallback)' title = this.funABI.type === 'receive' ? '(receive)' : '(fallback)'
} }
this.basicInputField = yo`<input></input>` this.basicInputField = yo`<input></input>`
@ -121,7 +121,7 @@ class MultiParamManager {
this.basicInputField.setAttribute('title', this.inputs) this.basicInputField.setAttribute('title', this.inputs)
this.basicInputField.setAttribute('style', 'flex: 4') this.basicInputField.setAttribute('style', 'flex: 4')
var onClick = (domEl) => { var onClick = () => {
this.clickCallBack(this.funABI.inputs, this.basicInputField.value) this.clickCallBack(this.funABI.inputs, this.basicInputField.value)
} }
let funcButton = yo`<button onclick=${() => onClick()} class="${css.instanceButton} btn btn-sm">${title}</button>` let funcButton = yo`<button onclick=${() => onClick()} class="${css.instanceButton} btn btn-sm">${title}</button>`
@ -204,8 +204,9 @@ class MultiParamManager {
if (this.funABI.inputs && this.funABI.inputs.length > 0) { if (this.funABI.inputs && this.funABI.inputs.length > 0) {
contractProperty.classList.add(css.hasArgs) contractProperty.classList.add(css.hasArgs)
} else if (this.funABI.type === 'fallback') { } else if (this.funABI.type === 'fallback' || this.funABI.type === 'receive') {
contractProperty.classList.add(css.hasArgs) contractProperty.classList.add(css.hasArgs)
this.basicInputField.setAttribute('title', `'(${this.funABI.type}')`) // probably should pass name instead
this.contractActionsContainerSingle.querySelector('i').style.visibility = 'hidden' this.contractActionsContainerSingle.querySelector('i').style.visibility = 'hidden'
} else { } else {
this.contractActionsContainerSingle.querySelector('i').style.visibility = 'hidden' this.contractActionsContainerSingle.querySelector('i').style.visibility = 'hidden'

@ -112,17 +112,6 @@ UniversalDAppUI.prototype.renderInstanceFromABI = function (contractABI, address
instance.appendChild(title) instance.appendChild(title)
instance.appendChild(contractActionsWrapper) instance.appendChild(contractActionsWrapper)
// Add the fallback function
var fallback = self.udapp.getFallbackInterface(contractABI)
if (fallback) {
contractActionsWrapper.appendChild(this.getCallButton({
funABI: fallback,
address: address,
contractAbi: contractABI,
contractName: contractName
}))
}
$.each(contractABI, (i, funABI) => { $.each(contractABI, (i, funABI) => {
if (funABI.type !== 'function') { if (funABI.type !== 'function') {
return return
@ -136,6 +125,108 @@ UniversalDAppUI.prototype.renderInstanceFromABI = function (contractABI, address
})) }))
}) })
this.calldataInput = yo`
<input class="m-0" title="Input the amount of Ether to send to receive function.">
`
this.amountInput = yo`
<input class="m-0" title="Input calldata to send to fallback function.">
`
this.llIError = yo`
<label class="text-danger"></label>
`
// constract LLInteractions elements
const lowLevelInteracions = yo`
<div class="d-flex flex-column">
<div class="d-flex flex-row justify-content-between mt-2">
<label class="pt-2 border-top d-flex justify-content-start flex-grow-1">
Low level interactions with contract
</label>
<i aria-hidden="true"
title="A contract can have at most one receive function, declared using receive() external payable { ... } and one fallback function, declared using fallback () external [payable].
These functions cannot have arguments, cannot return anything. Receive is executed on a call to the contract with empty calldata on plain Ether transfers (e.g. via .send() or .transfer()).
If no such function exists, but a payable fallback function exists, the fallback function will be called on a plain Ether transfer. If neither a receive Ether nor a payable fallback function is present, the contract cannot receive Ether through regular transactions and throws an exception.
Fallback is executed on a call to the contract if none of the other functions match the given function signature."
class="fas fa-info text-info my-3 mx-1"></i>
</div>
<div class="d-flex flex-column">
<div class="d-flex justify-content-end m-2 align-items-center">
<label class="mr-2 m-0">Amount</label>
${this.amountInput}
<button class="btn btn-sm btn-secondary" title="Send ether to contract." onclick=${() => sendEther()}>Send</button>
</div>
<div class="d-flex justify-content-end m-2 align-items-center">
<label class="mr-2 m-0">Calldata</label>
${this.calldataInput}
<button class="btn btn-sm btn-secondary" title="Send data to contract." onclick=${() => sendCalldata()}>Send</button>
</div>
</div>
<div>
${this.llIError}
</div>
</div>
`
function setLLIError (text) {
self.llIError.innerText = text
}
function sendCalldata () {
setLLIError('')
const fallback = self.udapp.getFallbackInterface(contractABI)
const args = {
funABI: fallback,
address: address,
contractAbi: contractABI,
contractName: contractName
}
if (!self.calldataInput.value) {
// show error:
setLLIError('Calldata field is empty')
} else {
if (fallback) {
// fallback is defined. call the fallback function
self.clickButton(args)
} else {
// show error
setLLIError("'fallback' function is not defined")
}
}
}
function sendEther () {
setLLIError('')
const fallback = self.udapp.getFallbackInterface(contractABI)
const receive = self.udapp.getReceiveInterface(contractABI)
const argsR = {
funABI: receive,
address: address,
contractAbi: contractABI,
contractName: contractName
}
const argsF = {
funABI: receive,
address: address,
contractAbi: contractABI,
contractName: contractName
}
if (!self.amountInput.value) {
// show error:
setLLIError('Ether amount field is empty')
} else {
if (receive) {
self.clickButton(argsR)
// receive is defined. call the fallback function
} else if (fallback && fallback.stateMutability === 'payable') {
// receive is not defined but there is payable fallback function, call it
self.clickButton(argsF)
} else {
// show error
setLLIError("In order to receive Ether transfer the contract should have either 'receive' or payable 'fallback' function")
}
}
}
contractActionsWrapper.appendChild(lowLevelInteracions)
return instance return instance
} }
@ -145,121 +236,141 @@ UniversalDAppUI.prototype.getCallButton = function (args) {
let self = this let self = this
// args.funABI, args.address [fun only] // args.funABI, args.address [fun only]
// args.contractName [constr only] // args.contractName [constr only]
const lookupOnly = args.funABI.stateMutability === 'view' || args.funABI.stateMutability === 'pure' || args.funABI.constant const lookupOnly = args.funABI.stateMutability === 'view' || args.funABI.stateMutability === 'pure' || !!args.funABI.constant
var outputOverride = yo`<div class=${css.value}></div>` // show return value var outputOverride = yo`<div class=${css.value}></div>` // show return value
function clickButton (valArr, inputsValues) { const multiParamManager = new MultiParamManager(lookupOnly, args.funABI, (valArray, inputsValue) => {
let logMsg this.clickButton(args, valArray, inputsValue)
if (!lookupOnly) { }, self.udapp.getInputs(args.funABI))
logMsg = `call to ${args.contractName}.${(args.funABI.name) ? args.funABI.name : '(fallback)'}`
} else {
logMsg = `transact to ${args.contractName}.${(args.funABI.name) ? args.funABI.name : '(fallback)'}`
}
var value = inputsValues const contractActionsContainer = yo`<div class="${css.contractActionsContainer}" >${multiParamManager.render()}</div>`
contractActionsContainer.appendChild(outputOverride)
const confirmationCb = (network, tx, gasEstimation, continueTxExecution, cancelCb) => { return contractActionsContainer
if (network.name !== 'Main') { }
return continueTxExecution(null)
} UniversalDAppUI.prototype.clickButton = function (args, valArr, inputsValue) {
var amount = Web3.utils.fromWei(typeConversion.toInt(tx.value), 'ether') let self = this
var content = confirmDialog(tx, amount, gasEstimation, self.udapp, // check if it's a special function and add a name in case it is
(gasPrice, cb) => { const fuctionName = args.contractName +
let txFeeText, priceStatus (args.funABI.name ? args.funABI.name : args.funABI.type === 'receive' ? '(receive)' : '(fallback)')
// TODO: this try catch feels like an anti pattern, can/should be
// removed, but for now keeping the original logic const lookupOnly = args.funABI.stateMutability === 'view' || args.funABI.stateMutability === 'pure' || !!args.funABI.constant
const logMsg = lookupOnly ? `transact to ${fuctionName}` : `call to ${fuctionName}`
var value = inputsValue
const confirmationCb = (network, tx, gasEstimation, continueTxExecution, cancelCb) => {
if (network.name !== 'Main') {
return continueTxExecution(null)
}
var amount = Web3.utils.fromWei(typeConversion.toInt(tx.value), 'ether')
var content = confirmDialog(tx, amount, gasEstimation, self.udapp,
(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 = Web3.utils.toBN(tx.gas).mul(Web3.utils.toBN(Web3.utils.toWei(gasPrice.toString(10), 'gwei')))
txFeeText = ' ' + Web3.utils.fromWei(fee.toString(10), 'ether') + ' Ether'
priceStatus = true
} catch (e) {
txFeeText = ' Please fix this issue before sending any transaction. ' + e.message
priceStatus = false
}
cb(txFeeText, priceStatus)
},
(cb) => {
self.executionContext.web3().eth.getGasPrice((error, gasPrice) => {
const warnMessage = ' Please fix this issue before sending any transaction. '
if (error) {
return cb('Unable to retrieve the current network gas price.' + warnMessage + error)
}
try { try {
var fee = Web3.utils.toBN(tx.gas).mul(Web3.utils.toBN(Web3.utils.toWei(gasPrice.toString(10), 'gwei'))) var gasPriceValue = Web3.utils.fromWei(gasPrice.toString(10), 'gwei')
txFeeText = ' ' + Web3.utils.fromWei(fee.toString(10), 'ether') + ' Ether' cb(null, gasPriceValue)
priceStatus = true
} catch (e) { } catch (e) {
txFeeText = ' Please fix this issue before sending any transaction. ' + e.message cb(warnMessage + e.message, null, false)
priceStatus = false }
})
}
)
modalDialog(
'Confirm transaction',
content,
{ label: 'Confirm',
fn: () => {
self.udapp.config.setUnpersistedProperty(
'doNotShowTransactionConfirmationAgain',
content.querySelector('input#confirmsetting').checked
)
// TODO: check if this is check is still valid given the refactor
if (!content.gasPriceStatus) {
cancelCb('Given gas price is not correct')
} else {
var gasPrice = Web3.utils.toWei(content.querySelector('#gasprice').value, 'gwei')
continueTxExecution(gasPrice)
}
}}, {
label: 'Cancel',
fn: () => {
return cancelCb('Transaction canceled by user.')
} }
cb(txFeeText, priceStatus)
},
(cb) => {
self.executionContext.web3().eth.getGasPrice((error, gasPrice) => {
const 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 = Web3.utils.fromWei(gasPrice.toString(10), 'gwei')
cb(null, gasPriceValue)
} catch (e) {
cb(warnMessage + e.message, null, false)
}
})
} }
) )
}
const continueCb = (error, continueTxExecution, cancelCb) => {
if (error) {
const msg = typeof error !== 'string' ? error.message : error
modalDialog( modalDialog(
'Confirm transaction', 'Gas estimation failed',
content, yo`
{ label: 'Confirm', <div>Gas estimation errored with the following message (see below).
fn: () => { The transaction execution will likely fail. Do you want to force sending? <br>${msg}</div>
self.udapp.config.setUnpersistedProperty( `,
'doNotShowTransactionConfirmationAgain', {
content.querySelector('input#confirmsetting').checked label: 'Send Transaction',
) fn: () => continueTxExecution()
// TODO: check if this is check is still valid given the refactor },
if (!content.gasPriceStatus) { {
cancelCb('Given gas price is not correct') label: 'Cancel Transaction',
} else { fn: () => cancelCb()
var gasPrice = Web3.utils.toWei(content.querySelector('#gasprice').value, 'gwei') }
continueTxExecution(gasPrice) )
} } else {
}}, { continueTxExecution()
label: 'Cancel',
fn: () => {
return cancelCb('Transaction canceled by user.')
}
}
)
} }
}
const continueCb = (error, continueTxExecution, cancelCb) => { const outputCb = (decoded) => {
if (error) { outputOverride.innerHTML = ''
const msg = typeof error !== 'string' ? error.message : error outputOverride.appendChild(decoded)
modalDialog( }
'Gas estimation failed',
yo`
<div>Gas estimation errored with the following message (see below).
The transaction execution will likely fail. Do you want to force sending? <br>${msg}</div>
`,
{
label: 'Send Transaction',
fn: () => continueTxExecution()
},
{
label: 'Cancel Transaction',
fn: () => cancelCb()
}
)
} else {
continueTxExecution()
}
}
const outputCb = (decoded) => { const promptCb = (okCb, cancelCb) => {
outputOverride.innerHTML = '' modalCustom.promptPassphrase('Passphrase requested', 'Personal mode is enabled. Please provide passphrase of account', '', okCb, cancelCb)
outputOverride.appendChild(decoded) }
}
const promptCb = (okCb, cancelCb) => { const isSpecialFunction = args.funABI.type === 'fallback' || args.funABI.type === 'receive'
modalCustom.promptPassphrase('Passphrase requested', 'Personal mode is enabled. Please provide passphrase of account', '', okCb, cancelCb)
}
// contractsDetails is used to resolve libraries // contractsDetails is used to resolve libraries
txFormat.buildData(args.contractName, args.contractAbi, {}, false, args.funABI, args.funABI.type !== 'fallback' ? value : '', (error, data) => { txFormat.buildData(
args.contractName,
args.contractAbi,
{},
false,
args.funABI,
!isSpecialFunction ? value : '', // input parameters for the function to call
(error, data) => {
if (!error) { if (!error) {
if (!lookupOnly) { if (!lookupOnly) {
self.logCallback(`${logMsg} pending ... `) self.logCallback(`${logMsg} pending ... `)
} else { } else {
self.logCallback(`${logMsg}`) self.logCallback(`${logMsg}`)
} }
if (args.funABI.type === 'fallback') data.dataHex = value if (isSpecialFunction) data.dataHex = value
self.udapp.callFunction(args.address, data, args.funABI, confirmationCb, continueCb, promptCb, (error, txResult) => { self.udapp.callFunction(args.address, data, args.funABI, confirmationCb, continueCb, promptCb, (error, txResult) => {
if (!error) { if (!error) {
var isVM = self.executionContext.isVM() var isVM = self.executionContext.isVM()
@ -286,17 +397,8 @@ UniversalDAppUI.prototype.getCallButton = function (args) {
}, (data, runTxCallback) => { }, (data, runTxCallback) => {
// called for libraries deployment // called for libraries deployment
self.udapp.runTx(data, confirmationCb, runTxCallback) self.udapp.runTx(data, confirmationCb, runTxCallback)
}) }
} )
const multiParamManager = new MultiParamManager(lookupOnly, args.funABI, (valArray, inputsValues, domEl) => {
clickButton(valArray, inputsValues, domEl)
}, self.udapp.getInputs(args.funABI))
const contractActionsContainer = yo`<div class="${css.contractActionsContainer}" >${multiParamManager.render()}</div>`
contractActionsContainer.appendChild(outputOverride)
return contractActionsContainer
} }
module.exports = UniversalDAppUI module.exports = UniversalDAppUI

Loading…
Cancel
Save