diff --git a/.dialyzer-ignore b/.dialyzer-ignore index a5d8c8f6c1..899009bca9 100644 --- a/.dialyzer-ignore +++ b/.dialyzer-ignore @@ -18,7 +18,7 @@ lib/block_scout_web/views/layout_view.ex:145: The call 'Elixir.Poison.Parser':'p lib/block_scout_web/views/layout_view.ex:237: The call 'Elixir.Poison.Parser':'parse!' lib/block_scout_web/controllers/api/rpc/transaction_controller.ex:21 lib/block_scout_web/controllers/api/rpc/transaction_controller.ex:22 -lib/explorer/smart_contract/reader.ex:347 +lib/explorer/smart_contract/reader.ex:348 lib/indexer/fetcher/token_total_supply_on_demand.ex:16 lib/explorer/exchange_rates/source.ex:110 lib/explorer/exchange_rates/source.ex:113 diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e450d4fc8..adddd59e46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ - [#3577](https://github.com/poanetwork/blockscout/pull/3577) - Eliminate GraphiQL page XSS attack ### Chore +- [#3736](https://github.com/blockscout/blockscout/pull/3736) - Contract writer: Fix sending a transaction with tuple input type - [#3719](https://github.com/poanetwork/blockscout/pull/3719) - Rename ethprice API endpoint - [#3717](https://github.com/poanetwork/blockscout/pull/3717) - Update alpine-elixir-phoenix 1.11.3 - [#3714](https://github.com/poanetwork/blockscout/pull/3714) - Application announcements management: whole explorer, staking dapp diff --git a/apps/block_scout_web/assets/js/lib/smart_contract/common_helpers.js b/apps/block_scout_web/assets/js/lib/smart_contract/common_helpers.js new file mode 100644 index 0000000000..57de5b7477 --- /dev/null +++ b/apps/block_scout_web/assets/js/lib/smart_contract/common_helpers.js @@ -0,0 +1,87 @@ +import $ from 'jquery' + +export function getContractABI ($form) { + const implementationAbi = $form.data('implementation-abi') + const parentAbi = $form.data('contract-abi') + const $parent = $('[data-smart-contract-functions]') + const contractType = $parent.data('type') + const contractAbi = contractType === 'proxy' ? implementationAbi : parentAbi + return contractAbi +} + +export function getMethodInputs (contractAbi, functionName) { + const functionAbi = contractAbi.find(abi => + abi.name === functionName + ) + return functionAbi && functionAbi.inputs +} + +export function prepareMethodArgs ($functionInputs, inputs) { + return $.map($functionInputs, (element, ind) => { + const inputValue = $(element).val() + const inputType = inputs[ind] && inputs[ind].type + let sanitizedInputValue + sanitizedInputValue = replaceSpaces(inputValue, inputType) + sanitizedInputValue = replaceDoubleQuotes(sanitizedInputValue, inputType) + + if (isArrayInputType(inputType) || isTupleInputType(inputType)) { + if (sanitizedInputValue === '') { + return [[]] + } else { + if (sanitizedInputValue.startsWith('[') && sanitizedInputValue.endsWith(']')) { + sanitizedInputValue = sanitizedInputValue.substring(1, sanitizedInputValue.length - 1) + } + const inputValueElements = sanitizedInputValue.split(',') + const sanitizedInputValueElements = inputValueElements.map(elementValue => { + const elementInputType = inputType.split('[')[0] + return replaceDoubleQuotes(elementValue, elementInputType) + }) + return [sanitizedInputValueElements] + } + } else { return sanitizedInputValue } + }) +} + +function isArrayInputType (inputType) { + return inputType && inputType.includes('[') && inputType.includes(']') +} + +function isTupleInputType (inputType) { + return inputType.includes('tuple') && !isArrayInputType(inputType) +} + +function isAddressInputType (inputType) { + return inputType.includes('address') && !isArrayInputType(inputType) +} + +function isUintInputType (inputType) { + return inputType.includes('int') && !isArrayInputType(inputType) +} + +function isStringInputType (inputType) { + return inputType.includes('string') && !isArrayInputType(inputType) +} + +function isNonSpaceInputType (inputType) { + return isAddressInputType(inputType) || inputType.includes('int') || inputType.includes('bool') +} + +function replaceSpaces (value, type) { + if (isNonSpaceInputType(type)) { + return value.replace(/\s/g, '') + } else { + return value + } +} + +function replaceDoubleQuotes (value, type) { + if (isAddressInputType(type) || isUintInputType(type) || isStringInputType(type)) { + if (typeof value.replaceAll === 'function') { + return value.replaceAll('"', '') + } else { + return value.replace(/"/g, '') + } + } else { + return value + } +} diff --git a/apps/block_scout_web/assets/js/lib/smart_contract/functions.js b/apps/block_scout_web/assets/js/lib/smart_contract/functions.js index 6d96023b49..55312c1429 100644 --- a/apps/block_scout_web/assets/js/lib/smart_contract/functions.js +++ b/apps/block_scout_web/assets/js/lib/smart_contract/functions.js @@ -1,7 +1,6 @@ import $ from 'jquery' -import { props } from 'eth-net-props' -import { walletEnabled, connectToWallet, getCurrentAccount, shouldHideConnectButton } from './write.js' -import { openErrorModal, openWarningModal, openSuccessModal, openModalWithMessage } from '../modals.js' +import { getContractABI, getMethodInputs, prepareMethodArgs } from './common_helpers' +import { walletEnabled, connectToWallet, shouldHideConnectButton, callMethod } from './write' import '../../pages/address' const loadFunctions = (element) => { @@ -137,181 +136,6 @@ const readWriteFunction = (element) => { }) } -function callMethod (isWalletEnabled, $functionInputs, explorerChainId, $form, functionName, $element) { - if (!isWalletEnabled) { - const warningMsg = 'You haven\'t approved the reading of account list from your MetaMask or MetaMask/Nifty wallet is locked or is not installed.' - return openWarningModal('Unauthorized', warningMsg) - } - const contractAbi = getContractABI($form) - const inputs = getMethodInputs(contractAbi, functionName) - - const $functionInputsExceptTxValue = $functionInputs.filter(':not([tx-value])') - const args = prepareMethodArgs($functionInputsExceptTxValue, inputs) - - const txValue = getTxValue($functionInputs) - const contractAddress = $form.data('contract-address') - - const { chainId: walletChainIdHex } = window.ethereum - compareChainIDs(explorerChainId, walletChainIdHex) - .then(currentAccount => { - if (functionName) { - const TargetContract = new window.web3.eth.Contract(contractAbi, contractAddress) - const sendParams = { from: currentAccount, value: txValue || 0 } - const methodToCall = TargetContract.methods[functionName](...args).send(sendParams) - methodToCall - .on('error', function (error) { - openErrorModal(`Error in sending transaction for method "${functionName}"`, formatError(error), false) - }) - .on('transactionHash', function (txHash) { - onTransactionHash(txHash, $element, functionName) - }) - } else { - const txParams = { - from: currentAccount, - to: contractAddress, - value: txValue || 0 - } - window.ethereum.request({ - method: 'eth_sendTransaction', - params: [txParams] - }) - .then(function (txHash) { - onTransactionHash(txHash, $element, functionName) - }) - .catch(function (error) { - openErrorModal('Error in sending transaction for fallback method', formatError(error), false) - }) - } - }) - .catch(error => { - openWarningModal('Unauthorized', formatError(error)) - }) -} - -function getMethodInputs (contractAbi, functionName) { - const functionAbi = contractAbi.find(abi => - abi.name === functionName - ) - return functionAbi && functionAbi.inputs -} - -function prepareMethodArgs ($functionInputs, inputs) { - return $.map($functionInputs, (element, ind) => { - const inputValue = $(element).val() - const inputType = inputs[ind] && inputs[ind].type - let sanitizedInputValue - sanitizedInputValue = replaceSpaces(inputValue, inputType) - sanitizedInputValue = replaceDoubleQuotes(sanitizedInputValue, inputType) - if (isArrayInputType(inputType)) { - if (sanitizedInputValue === '') { - return [[]] - } else { - if (sanitizedInputValue.startsWith('[') && sanitizedInputValue.endsWith(']')) { - sanitizedInputValue = sanitizedInputValue.substring(1, sanitizedInputValue.length - 1) - } - const inputValueElements = sanitizedInputValue.split(',') - const sanitizedInputValueElements = inputValueElements.map(elementValue => { - const elementInputType = inputType.split('[')[0] - return replaceDoubleQuotes(elementValue, elementInputType) - }) - return [sanitizedInputValueElements] - } - } else { return sanitizedInputValue } - }) -} - -function isArrayInputType (inputType) { - return inputType && inputType.includes('[') && inputType.includes(']') -} - -function isAddressInputType (inputType) { - return inputType.includes('address') -} - -function isUintInputType (inputType) { - return inputType.includes('int') && !inputType.includes('[') -} - -function isStringInputType (inputType) { - return inputType.includes('string') && !inputType.includes('[') -} - -function isNonSpaceInputType (inputType) { - return isAddressInputType(inputType) || inputType.includes('int') || inputType.includes('bool') -} - -function getTxValue ($functionInputs) { - const WEI_MULTIPLIER = 10 ** 18 - const $txValue = $functionInputs.filter('[tx-value]:first') - const txValue = $txValue && $txValue.val() && parseFloat($txValue.val()) * WEI_MULTIPLIER - const txValueStr = txValue && txValue.toString(16) - return txValueStr -} - -function getContractABI ($form) { - const implementationAbi = $form.data('implementation-abi') - const parentAbi = $form.data('contract-abi') - const $parent = $('[data-smart-contract-functions]') - const contractType = $parent.data('type') - const contractAbi = contractType === 'proxy' ? implementationAbi : parentAbi - return contractAbi -} - -function compareChainIDs (explorerChainId, walletChainIdHex) { - if (explorerChainId !== parseInt(walletChainIdHex)) { - const networkDisplayNameFromWallet = props.getNetworkDisplayName(walletChainIdHex) - const networkDisplayName = props.getNetworkDisplayName(explorerChainId) - const errorMsg = `You connected to ${networkDisplayNameFromWallet} chain in the wallet, but the current instance of Blockscout is for ${networkDisplayName} chain` - return Promise.reject(new Error(errorMsg)) - } else { - return getCurrentAccount() - } -} - -function onTransactionHash (txHash, $element, functionName) { - openModalWithMessage($element.find('#pending-contract-write'), true, txHash) - const getTxReceipt = (txHash) => { - window.ethereum.request({ - method: 'eth_getTransactionReceipt', - params: [txHash] - }) - .then(txReceipt => { - if (txReceipt) { - const successMsg = `Successfully sent transaction for method "${functionName}"` - openSuccessModal('Success', successMsg) - clearInterval(txReceiptPollingIntervalId) - } - }) - } - const txReceiptPollingIntervalId = setInterval(() => { getTxReceipt(txHash) }, 5 * 1000) -} - -function replaceSpaces (value, type) { - if (isNonSpaceInputType(type)) { - return value.replace(/\s/g, '') - } else { - return value - } -} - -function replaceDoubleQuotes (value, type) { - if (isAddressInputType(type) || isUintInputType(type) || isStringInputType(type)) { - if (typeof value.replaceAll === 'function') { - return value.replaceAll('"', '') - } else { - return value.replace(/"/g, '') - } - } else { - return value - } -} - -const formatError = (error) => { - let { message } = error - message = message && message.split('Error: ').length > 1 ? message.split('Error: ')[1] : message - return message -} - const container = $('[data-smart-contract-functions]') if (container.length) { diff --git a/apps/block_scout_web/assets/js/lib/smart_contract/write.js b/apps/block_scout_web/assets/js/lib/smart_contract/write.js index 73e50a6f2e..14ada95f25 100644 --- a/apps/block_scout_web/assets/js/lib/smart_contract/write.js +++ b/apps/block_scout_web/assets/js/lib/smart_contract/write.js @@ -1,4 +1,7 @@ import Web3 from 'web3' +import { props } from 'eth-net-props' +import { openErrorModal, openWarningModal, openSuccessModal, openModalWithMessage } from '../modals' +import { getContractABI, getMethodInputs, prepareMethodArgs } from './common_helpers' export const walletEnabled = () => { return new Promise((resolve) => { @@ -80,3 +83,97 @@ export const shouldHideConnectButton = () => { } }) } + +export function callMethod (isWalletEnabled, $functionInputs, explorerChainId, $form, functionName, $element) { + if (!isWalletEnabled) { + const warningMsg = 'You haven\'t approved the reading of account list from your MetaMask or MetaMask/Nifty wallet is locked or is not installed.' + return openWarningModal('Unauthorized', warningMsg) + } + const contractAbi = getContractABI($form) + const inputs = getMethodInputs(contractAbi, functionName) + + const $functionInputsExceptTxValue = $functionInputs.filter(':not([tx-value])') + const args = prepareMethodArgs($functionInputsExceptTxValue, inputs) + + const txValue = getTxValue($functionInputs) + const contractAddress = $form.data('contract-address') + + const { chainId: walletChainIdHex } = window.ethereum + compareChainIDs(explorerChainId, walletChainIdHex) + .then(currentAccount => { + if (functionName) { + const TargetContract = new window.web3.eth.Contract(contractAbi, contractAddress) + const sendParams = { from: currentAccount, value: txValue || 0 } + const methodToCall = TargetContract.methods[functionName](...args).send(sendParams) + methodToCall + .on('error', function (error) { + openErrorModal(`Error in sending transaction for method "${functionName}"`, formatError(error), false) + }) + .on('transactionHash', function (txHash) { + onTransactionHash(txHash, $element, functionName) + }) + } else { + const txParams = { + from: currentAccount, + to: contractAddress, + value: txValue || 0 + } + window.ethereum.request({ + method: 'eth_sendTransaction', + params: [txParams] + }) + .then(function (txHash) { + onTransactionHash(txHash, $element, functionName) + }) + .catch(function (error) { + openErrorModal('Error in sending transaction for fallback method', formatError(error), false) + }) + } + }) + .catch(error => { + openWarningModal('Unauthorized', formatError(error)) + }) +} + +function onTransactionHash (txHash, $element, functionName) { + openModalWithMessage($element.find('#pending-contract-write'), true, txHash) + const getTxReceipt = (txHash) => { + window.ethereum.request({ + method: 'eth_getTransactionReceipt', + params: [txHash] + }) + .then(txReceipt => { + if (txReceipt) { + const successMsg = `Successfully sent transaction for method "${functionName}"` + openSuccessModal('Success', successMsg) + clearInterval(txReceiptPollingIntervalId) + } + }) + } + const txReceiptPollingIntervalId = setInterval(() => { getTxReceipt(txHash) }, 5 * 1000) +} + +const formatError = (error) => { + let { message } = error + message = message && message.split('Error: ').length > 1 ? message.split('Error: ')[1] : message + return message +} + +function getTxValue ($functionInputs) { + const WEI_MULTIPLIER = 10 ** 18 + const $txValue = $functionInputs.filter('[tx-value]:first') + const txValue = $txValue && $txValue.val() && parseFloat($txValue.val()) * WEI_MULTIPLIER + const txValueStr = txValue && txValue.toString(16) + return txValueStr +} + +function compareChainIDs (explorerChainId, walletChainIdHex) { + if (explorerChainId !== parseInt(walletChainIdHex)) { + const networkDisplayNameFromWallet = props.getNetworkDisplayName(walletChainIdHex) + const networkDisplayName = props.getNetworkDisplayName(explorerChainId) + const errorMsg = `You connected to ${networkDisplayNameFromWallet} chain in the wallet, but the current instance of Blockscout is for ${networkDisplayName} chain` + return Promise.reject(new Error(errorMsg)) + } else { + return getCurrentAccount() + } +} diff --git a/apps/block_scout_web/assets/webpack.config.js b/apps/block_scout_web/assets/webpack.config.js index 0f52fa95bb..d0d8414d43 100644 --- a/apps/block_scout_web/assets/webpack.config.js +++ b/apps/block_scout_web/assets/webpack.config.js @@ -90,7 +90,7 @@ const appJs = 'admin-tasks': './js/pages/admin/tasks.js', 'read-token-contract': './js/pages/read_token_contract.js', 'smart-contract-helpers': './js/lib/smart_contract/index.js', - 'write_contract': './js/pages/write_contract.js', + 'write-contract': './js/pages/write_contract.js', 'token-transfers-toggle': './js/lib/token_transfers_toggle.js', 'try-api': './js/lib/try_api.js', 'try-eth-api': './js/lib/try_eth_api.js', diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_write_contract/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_write_contract/index.html.eex index 34c5dcffcf..0c7874b6e9 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_write_contract/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_write_contract/index.html.eex @@ -16,5 +16,5 @@ - + diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_write_proxy/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_write_proxy/index.html.eex index 34c5dcffcf..0c7874b6e9 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_write_proxy/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_write_proxy/index.html.eex @@ -16,5 +16,5 @@ - + diff --git a/apps/block_scout_web/lib/block_scout_web/templates/smart_contract/_connect_container.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/smart_contract/_connect_container.html.eex new file mode 100644 index 0000000000..d2ad941524 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/smart_contract/_connect_container.html.eex @@ -0,0 +1,13 @@ +
+ + <%= render BlockScoutWeb.IconsView, "_inactive_icon.html" %> + +

Disconnected

+ +
+ \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/smart_contract/_functions.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/smart_contract/_functions.html.eex index 75d023ca91..79bd7cbe44 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/smart_contract/_functions.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/smart_contract/_functions.html.eex @@ -12,19 +12,7 @@ to: address_contract_path(@conn, :index, metadata_for_verification.address_hash) <% end %> <% end %> <%= if @action== "write" do %> -
- - <%= render BlockScoutWeb.IconsView, "_inactive_icon.html" %> - -

Disconnected

- -
- + <%= render BlockScoutWeb.SmartContractView, "_connect_container.html" %> <% end %> <%= if @contract_type == "proxy" do %>
@@ -52,7 +40,7 @@ to: address_contract_path(@conn, :index, metadata_for_verification.address_hash) end %> <%= render BlockScoutWeb.SmartContractView, "_pending_contract_write.html" %> -
+ @@ -80,7 +68,7 @@ to: address_contract_path(@conn, :index, metadata_for_verification.address_hash) <% end %> <% end %> - <%= if payable?(function) do %> + <%= if Helper.payable?(function) do %>
diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_read_contract_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_read_contract_view.ex index 1930c4b8d7..559af200bc 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/address_read_contract_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/address_read_contract_view.ex @@ -1,13 +1,3 @@ defmodule BlockScoutWeb.AddressReadContractView do use BlockScoutWeb, :view - - def queryable?(inputs) when not is_nil(inputs), do: Enum.any?(inputs) - - def queryable?(inputs) when is_nil(inputs), do: false - - def outputs?(outputs) when not is_nil(outputs), do: Enum.any?(outputs) - - def outputs?(outputs) when is_nil(outputs), do: false - - def address?(type), do: type == "address" end diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_read_proxy_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_read_proxy_view.ex index 178e900fe0..e51247fdd8 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/address_read_proxy_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/address_read_proxy_view.ex @@ -1,13 +1,3 @@ defmodule BlockScoutWeb.AddressReadProxyView do use BlockScoutWeb, :view - - def queryable?(inputs) when not is_nil(inputs), do: Enum.any?(inputs) - - def queryable?(inputs) when is_nil(inputs), do: false - - def outputs?(outputs) when not is_nil(outputs), do: Enum.any?(outputs) - - def outputs?(outputs) when is_nil(outputs), do: false - - def address?(type), do: type == "address" end diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_view.ex index db37658d12..f0dc1ed29d 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/address_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/address_view.ex @@ -8,7 +8,7 @@ defmodule BlockScoutWeb.AddressView do alias Explorer.Chain.{Address, Hash, InternalTransaction, SmartContract, Token, TokenTransfer, Transaction, Wei} alias Explorer.Chain.Block.Reward alias Explorer.ExchangeRates.Token, as: TokenExchangeRate - alias Explorer.SmartContract.Writer + alias Explorer.SmartContract.{Helper, Writer} @dialyzer :no_match @@ -229,7 +229,7 @@ defmodule BlockScoutWeb.AddressView do def smart_contract_verified?(%Address{smart_contract: nil}), do: false def smart_contract_with_read_only_functions?(%Address{smart_contract: %SmartContract{}} = address) do - Enum.any?(address.smart_contract.abi, &(&1["constant"] || &1["stateMutability"] == "view")) + Enum.any?(address.smart_contract.abi, &Helper.queriable_method?(&1)) end def smart_contract_with_read_only_functions?(%Address{smart_contract: nil}), do: false diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_write_contract_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_write_contract_view.ex index 37b82f72cc..c21e1f51c9 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/address_write_contract_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/address_write_contract_view.ex @@ -1,13 +1,3 @@ defmodule BlockScoutWeb.AddressWriteContractView do use BlockScoutWeb, :view - - def queryable?(inputs) when not is_nil(inputs), do: Enum.any?(inputs) - - def queryable?(inputs) when is_nil(inputs), do: false - - def outputs?(outputs) when not is_nil(outputs), do: Enum.any?(outputs) - - def outputs?(outputs) when is_nil(outputs), do: false - - def address?(type), do: type == "address" end diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_write_proxy_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_write_proxy_view.ex index 9825477c3d..17634e9fb1 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/address_write_proxy_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/address_write_proxy_view.ex @@ -1,13 +1,3 @@ defmodule BlockScoutWeb.AddressWriteProxyView do use BlockScoutWeb, :view - - def queryable?(inputs) when not is_nil(inputs), do: Enum.any?(inputs) - - def queryable?(inputs) when is_nil(inputs), do: false - - def outputs?(outputs) when not is_nil(outputs), do: Enum.any?(outputs) - - def outputs?(outputs) when is_nil(outputs), do: false - - def address?(type), do: type == "address" end diff --git a/apps/block_scout_web/lib/block_scout_web/views/smart_contract_view.ex b/apps/block_scout_web/lib/block_scout_web/views/smart_contract_view.ex index 98d4438dc1..d9ab656ce9 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/smart_contract_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/smart_contract_view.ex @@ -2,7 +2,9 @@ defmodule BlockScoutWeb.SmartContractView do use BlockScoutWeb, :view alias Explorer.Chain - alias Explorer.Chain.Hash.Address + alias Explorer.Chain.Address + alias Explorer.Chain.Hash.Address, as: HashAddress + alias Explorer.SmartContract.Helper def queryable?(inputs) when not is_nil(inputs), do: Enum.any?(inputs) @@ -10,8 +12,8 @@ defmodule BlockScoutWeb.SmartContractView do def writable?(function) when not is_nil(function), do: - !constructor?(function) && !event?(function) && - (payable?(function) || nonpayable?(function)) + !Helper.constructor?(function) && !Helper.event?(function) && + (Helper.payable?(function) || Helper.nonpayable?(function)) def writable?(function) when is_nil(function), do: false @@ -19,21 +21,6 @@ defmodule BlockScoutWeb.SmartContractView do def outputs?(outputs) when is_nil(outputs), do: false - defp event?(function), do: function["type"] == "event" - - defp constructor?(function), do: function["type"] == "constructor" - - def payable?(function), do: function["stateMutability"] == "payable" || function["payable"] - - def nonpayable?(function) do - if function["type"] do - function["stateMutability"] == "nonpayable" || - (!function["payable"] && !function["constant"] && !function["stateMutability"]) - else - false - end - end - def address?(type), do: type in ["address", "address payable"] def int?(type), do: String.contains?(type, "int") && !String.contains?(type, "[") @@ -94,7 +81,7 @@ defmodule BlockScoutWeb.SmartContractView do end def values_with_type(value, type, _components) when type in [:address, "address", "address payable"] do - case Address.cast(value) do + case HashAddress.cast(value) do {:ok, address} -> render_type_value("address", to_string(address)) @@ -167,7 +154,7 @@ defmodule BlockScoutWeb.SmartContractView do end def values_only(value, type, _components) when type in [:address, "address", "address payable"] do - {:ok, address} = Address.cast(value) + {:ok, address} = HashAddress.cast(value) to_string(address) end diff --git a/apps/block_scout_web/lib/block_scout_web/views/tokens/instance/overview_view.ex b/apps/block_scout_web/lib/block_scout_web/views/tokens/instance/overview_view.ex index 0b410254ed..d15da258bd 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/tokens/instance/overview_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/tokens/instance/overview_view.ex @@ -3,6 +3,7 @@ defmodule BlockScoutWeb.Tokens.Instance.OverviewView do alias BlockScoutWeb.CurrencyHelpers alias Explorer.Chain.{Address, SmartContract, Token} + alias Explorer.SmartContract.Helper import BlockScoutWeb.APIDocsView, only: [blockscout_url: 1, blockscout_url: 2] @@ -68,7 +69,7 @@ defmodule BlockScoutWeb.Tokens.Instance.OverviewView do def smart_contract_with_read_only_functions?( %Token{contract_address: %Address{smart_contract: %SmartContract{}}} = token ) do - Enum.any?(token.contract_address.smart_contract.abi, &(&1["constant"] || &1["stateMutability"] == "view")) + Enum.any?(token.contract_address.smart_contract.abi, &Helper.queriable_method?(&1)) end def smart_contract_with_read_only_functions?(%Token{contract_address: %Address{smart_contract: nil}}), do: false diff --git a/apps/block_scout_web/lib/block_scout_web/views/tokens/overview_view.ex b/apps/block_scout_web/lib/block_scout_web/views/tokens/overview_view.ex index c5dccab223..f18b68ee74 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/tokens/overview_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/tokens/overview_view.ex @@ -3,6 +3,7 @@ defmodule BlockScoutWeb.Tokens.OverviewView do alias Explorer.{Chain, CustomContractsHelpers} alias Explorer.Chain.{Address, SmartContract, Token} + alias Explorer.SmartContract.Helper alias BlockScoutWeb.{AccessHelpers, CurrencyHelpers, LayoutView} @@ -48,7 +49,7 @@ defmodule BlockScoutWeb.Tokens.OverviewView do def smart_contract_with_read_only_functions?( %Token{contract_address: %Address{smart_contract: %SmartContract{}}} = token ) do - Enum.any?(token.contract_address.smart_contract.abi, &(&1["constant"] || &1["stateMutability"] == "view")) + Enum.any?(token.contract_address.smart_contract.abi, &Helper.queriable_method?(&1)) end def smart_contract_with_read_only_functions?(%Token{contract_address: %Address{smart_contract: nil}}), do: false diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot index 14fd7d16a7..fad4e3e0bb 100644 --- a/apps/block_scout_web/priv/gettext/default.pot +++ b/apps/block_scout_web/priv/gettext/default.pot @@ -555,8 +555,8 @@ msgid "ERC-721 " msgstr "" #, elixir-format -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:85 -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:120 +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:73 +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:108 msgid "ETH" msgstr "" @@ -868,7 +868,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/tokens/holder/index.html.eex:15 #: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:9 -#: lib/block_scout_web/views/tokens/overview_view.ex:41 +#: lib/block_scout_web/views/tokens/overview_view.ex:42 msgid "Token Holders" msgstr "" @@ -894,8 +894,8 @@ msgstr "" #: lib/block_scout_web/templates/transaction/_tabs.html.eex:4 #: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:7 #: lib/block_scout_web/views/address_view.ex:346 -#: lib/block_scout_web/views/tokens/instance/overview_view.ex:124 -#: lib/block_scout_web/views/tokens/overview_view.ex:40 +#: lib/block_scout_web/views/tokens/instance/overview_view.ex:125 +#: lib/block_scout_web/views/tokens/overview_view.ex:41 #: lib/block_scout_web/views/transaction_view.ex:405 msgid "Token Transfers" msgstr "" @@ -944,7 +944,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/tokens/inventory/index.html.eex:15 #: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:17 -#: lib/block_scout_web/views/tokens/overview_view.ex:43 +#: lib/block_scout_web/views/tokens/overview_view.ex:44 msgid "Inventory" msgstr "" @@ -1187,7 +1187,7 @@ msgid "QR Code" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:89 +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:77 msgid "Query" msgstr "" @@ -1640,7 +1640,7 @@ msgid "View transaction %{transaction} on %{subnetwork}" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:119 +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:107 msgid "WEI" msgstr "" @@ -1775,7 +1775,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/tokens/instance/metadata/index.html.eex:18 #: lib/block_scout_web/templates/tokens/instance/overview/_tabs.html.eex:10 -#: lib/block_scout_web/views/tokens/instance/overview_view.ex:125 +#: lib/block_scout_web/views/tokens/instance/overview_view.ex:126 msgid "Metadata" msgstr "" @@ -1852,7 +1852,7 @@ msgstr "" #: lib/block_scout_web/templates/address/_tabs.html.eex:81 #: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:25 #: lib/block_scout_web/views/address_view.ex:349 -#: lib/block_scout_web/views/tokens/overview_view.ex:42 +#: lib/block_scout_web/views/tokens/overview_view.ex:43 msgid "Read Contract" msgstr "" @@ -1931,7 +1931,7 @@ msgid "Waiting for transaction's confirmation..." msgstr "" #, elixir-format -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:89 +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:77 msgid "Write" msgstr "" diff --git a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po index ad8a22f21b..fad4e3e0bb 100644 --- a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po +++ b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po @@ -555,8 +555,8 @@ msgid "ERC-721 " msgstr "" #, elixir-format -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:85 -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:120 +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:73 +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:108 msgid "ETH" msgstr "" @@ -868,7 +868,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/tokens/holder/index.html.eex:15 #: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:9 -#: lib/block_scout_web/views/tokens/overview_view.ex:41 +#: lib/block_scout_web/views/tokens/overview_view.ex:42 msgid "Token Holders" msgstr "" @@ -894,8 +894,8 @@ msgstr "" #: lib/block_scout_web/templates/transaction/_tabs.html.eex:4 #: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:7 #: lib/block_scout_web/views/address_view.ex:346 -#: lib/block_scout_web/views/tokens/instance/overview_view.ex:124 -#: lib/block_scout_web/views/tokens/overview_view.ex:40 +#: lib/block_scout_web/views/tokens/instance/overview_view.ex:125 +#: lib/block_scout_web/views/tokens/overview_view.ex:41 #: lib/block_scout_web/views/transaction_view.ex:405 msgid "Token Transfers" msgstr "" @@ -944,7 +944,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/tokens/inventory/index.html.eex:15 #: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:17 -#: lib/block_scout_web/views/tokens/overview_view.ex:43 +#: lib/block_scout_web/views/tokens/overview_view.ex:44 msgid "Inventory" msgstr "" @@ -1187,7 +1187,7 @@ msgid "QR Code" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:89 +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:77 msgid "Query" msgstr "" @@ -1640,7 +1640,7 @@ msgid "View transaction %{transaction} on %{subnetwork}" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:119 +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:107 msgid "WEI" msgstr "" @@ -1775,7 +1775,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/tokens/instance/metadata/index.html.eex:18 #: lib/block_scout_web/templates/tokens/instance/overview/_tabs.html.eex:10 -#: lib/block_scout_web/views/tokens/instance/overview_view.ex:125 +#: lib/block_scout_web/views/tokens/instance/overview_view.ex:126 msgid "Metadata" msgstr "" @@ -1852,7 +1852,7 @@ msgstr "" #: lib/block_scout_web/templates/address/_tabs.html.eex:81 #: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:25 #: lib/block_scout_web/views/address_view.ex:349 -#: lib/block_scout_web/views/tokens/overview_view.ex:42 +#: lib/block_scout_web/views/tokens/overview_view.ex:43 msgid "Read Contract" msgstr "" @@ -1931,7 +1931,7 @@ msgid "Waiting for transaction's confirmation..." msgstr "" #, elixir-format -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:89 +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:77 msgid "Write" msgstr "" @@ -2702,11 +2702,6 @@ msgstr "" msgid "Swap STAKE on Honeyswap" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/stakes/_stakes_top.html.eex:34 -msgid "Bridge to Ethereum" -msgstr "" - #, elixir-format #: lib/block_scout_web/templates/stakes/_stakes_top.html.eex:52 msgid "Trade STAKE on BitMax" diff --git a/apps/block_scout_web/test/block_scout_web/views/address_read_contract_view_test.exs b/apps/block_scout_web/test/block_scout_web/views/address_read_contract_view_test.exs deleted file mode 100644 index 1f98e26e5a..0000000000 --- a/apps/block_scout_web/test/block_scout_web/views/address_read_contract_view_test.exs +++ /dev/null @@ -1,19 +0,0 @@ -defmodule BlockScoutWeb.AddressReadContractViewTest do - use BlockScoutWeb.ConnCase, async: true - - alias BlockScoutWeb.AddressReadContractView - - describe "queryable?/1" do - test "returns true if list of inputs is not empty" do - assert AddressReadContractView.queryable?([%{"name" => "argument_name", "type" => "uint256"}]) == true - assert AddressReadContractView.queryable?([]) == false - end - end - - describe "address?/1" do - test "returns true if type equals `address`" do - assert AddressReadContractView.address?("address") == true - assert AddressReadContractView.address?("uint256") == false - end - end -end diff --git a/apps/block_scout_web/test/block_scout_web/views/address_read_proxy_view_test.exs b/apps/block_scout_web/test/block_scout_web/views/address_read_proxy_view_test.exs deleted file mode 100644 index cf1b6fd8e9..0000000000 --- a/apps/block_scout_web/test/block_scout_web/views/address_read_proxy_view_test.exs +++ /dev/null @@ -1,19 +0,0 @@ -defmodule BlockScoutWeb.AddressReadProxyViewTest do - use BlockScoutWeb.ConnCase, async: true - - alias BlockScoutWeb.AddressReadProxyView - - describe "queryable?/1" do - test "returns true if list of inputs is not empty" do - assert AddressReadProxyView.queryable?([%{"name" => "argument_name", "type" => "uint256"}]) == true - assert AddressReadProxyView.queryable?([]) == false - end - end - - describe "address?/1" do - test "returns true if type equals `address`" do - assert AddressReadProxyView.address?("address") == true - assert AddressReadProxyView.address?("uint256") == false - end - end -end diff --git a/apps/block_scout_web/test/block_scout_web/views/address_write_contract_view_test.exs b/apps/block_scout_web/test/block_scout_web/views/address_write_contract_view_test.exs deleted file mode 100644 index 55360f1229..0000000000 --- a/apps/block_scout_web/test/block_scout_web/views/address_write_contract_view_test.exs +++ /dev/null @@ -1,19 +0,0 @@ -defmodule BlockScoutWeb.AddressWriteContractViewTest do - use BlockScoutWeb.ConnCase, async: true - - alias BlockScoutWeb.AddressWriteContractView - - describe "queryable?/1" do - test "returns true if list of inputs is not empty" do - assert AddressWriteContractView.queryable?([%{"name" => "argument_name", "type" => "uint256"}]) == true - assert AddressWriteContractView.queryable?([]) == false - end - end - - describe "address?/1" do - test "returns true if type equals `address`" do - assert AddressWriteContractView.address?("address") == true - assert AddressWriteContractView.address?("uint256") == false - end - end -end diff --git a/apps/block_scout_web/test/block_scout_web/views/address_write_proxy_view_test copy.exs b/apps/block_scout_web/test/block_scout_web/views/address_write_proxy_view_test copy.exs deleted file mode 100644 index e3db0d7003..0000000000 --- a/apps/block_scout_web/test/block_scout_web/views/address_write_proxy_view_test copy.exs +++ /dev/null @@ -1,19 +0,0 @@ -defmodule BlockScoutWeb.AddressWriteProxyViewTest do - use BlockScoutWeb.ConnCase, async: true - - alias BlockScoutWeb.AddressWriteProxyView - - describe "queryable?/1" do - test "returns true if list of inputs is not empty" do - assert AddressWriteProxyView.queryable?([%{"name" => "argument_name", "type" => "uint256"}]) == true - assert AddressWriteProxyView.queryable?([]) == false - end - end - - describe "address?/1" do - test "returns true if type equals `address`" do - assert AddressWriteProxyView.address?("address") == true - assert AddressWriteProxyView.address?("uint256") == false - end - end -end diff --git a/apps/block_scout_web/test/block_scout_web/views/tokens/smart_contract_view_test.exs b/apps/block_scout_web/test/block_scout_web/views/tokens/smart_contract_view_test.exs index 27b9ab00ba..7e43ce4e10 100644 --- a/apps/block_scout_web/test/block_scout_web/views/tokens/smart_contract_view_test.exs +++ b/apps/block_scout_web/test/block_scout_web/views/tokens/smart_contract_view_test.exs @@ -15,6 +15,11 @@ defmodule BlockScoutWeb.SmartContractViewTest do refute SmartContractView.queryable?(inputs) end + + test "returns true if list of inputs is not empty" do + assert SmartContractView.queryable?([%{"name" => "argument_name", "type" => "uint256"}]) == true + assert SmartContractView.queryable?([]) == false + end end describe "writable?" do @@ -73,128 +78,12 @@ defmodule BlockScoutWeb.SmartContractViewTest do end end - describe "payable?" do - test "returns true when there is payable function" do - function = %{ - "type" => "function", - "stateMutability" => "payable", - "payable" => true, - "outputs" => [], - "name" => "upgradeToAndCall", - "inputs" => [ - %{"type" => "uint256", "name" => "version"}, - %{"type" => "address", "name" => "implementation"}, - %{"type" => "bytes", "name" => "data"} - ], - "constant" => false - } - - assert SmartContractView.payable?(function) - end - - test "returns true when there is old-style payable function" do - function = %{ - "type" => "function", - "payable" => true, - "outputs" => [], - "name" => "upgradeToAndCall", - "inputs" => [ - %{"type" => "uint256", "name" => "version"}, - %{"type" => "address", "name" => "implementation"}, - %{"type" => "bytes", "name" => "data"} - ], - "constant" => false - } - - assert SmartContractView.payable?(function) - end - - test "returns false when it is nonpayable function" do - function = %{ - "type" => "function", - "stateMutability" => "nonpayable", - "payable" => false, - "outputs" => [], - "name" => "transferProxyOwnership", - "inputs" => [%{"type" => "address", "name" => "newOwner"}], - "constant" => false - } - - refute SmartContractView.payable?(function) - end - - test "returns false when there is no function" do - function = %{} - - refute SmartContractView.payable?(function) - end - - test "returns false when function is nil" do - function = nil - - refute SmartContractView.payable?(function) - end - end - - describe "nonpayable?" do - test "returns true when there is nonpayable function" do - function = %{ - "type" => "function", - "stateMutability" => "nonpayable", - "payable" => false, - "outputs" => [], - "name" => "transferProxyOwnership", - "inputs" => [%{"type" => "address", "name" => "newOwner"}], - "constant" => false - } - - assert SmartContractView.nonpayable?(function) - end - - test "returns true when there is old-style nonpayable function" do - function = %{ - "type" => "function", - "outputs" => [], - "name" => "test", - "inputs" => [%{"type" => "address", "name" => "newOwner"}], - "constant" => false - } - - assert SmartContractView.nonpayable?(function) - end - - test "returns false when it is payable function" do - function = %{ - "type" => "function", - "stateMutability" => "payable", - "payable" => true, - "outputs" => [], - "name" => "upgradeToAndCall", - "inputs" => [ - %{"type" => "uint256", "name" => "version"}, - %{"type" => "address", "name" => "implementation"}, - %{"type" => "bytes", "name" => "data"} - ], - "constant" => false - } - - refute SmartContractView.nonpayable?(function) - end - - test "returns true when there is no function" do - function = %{} - - refute SmartContractView.nonpayable?(function) - end - - test "returns false when function is nil" do - function = nil - - refute SmartContractView.nonpayable?(function) + describe "address?" do + test "returns true if type equals `address`" do + assert SmartContractView.address?("address") == true + assert SmartContractView.address?("uint256") == false end - end - describe "address?" do test "returns true when the type is equal to the string 'address'" do type = "address" diff --git a/apps/explorer/lib/explorer/smart_contract/helper.ex b/apps/explorer/lib/explorer/smart_contract/helper.ex new file mode 100644 index 0000000000..083258ac60 --- /dev/null +++ b/apps/explorer/lib/explorer/smart_contract/helper.ex @@ -0,0 +1,24 @@ +defmodule Explorer.SmartContract.Helper do + @moduledoc """ + SmartContract helper functions + """ + + def queriable_method?(method) do + method["constant"] || method["stateMutability"] == "view" + end + + def constructor?(function), do: function["type"] == "constructor" + + def event?(function), do: function["type"] == "event" + + def payable?(function), do: function["stateMutability"] == "payable" || function["payable"] + + def nonpayable?(function) do + if function["type"] do + function["stateMutability"] == "nonpayable" || + (!function["payable"] && !function["constant"] && !function["stateMutability"]) + else + false + end + end +end diff --git a/apps/explorer/lib/explorer/smart_contract/reader.ex b/apps/explorer/lib/explorer/smart_contract/reader.ex index e51c35e9b5..1cfff9183f 100644 --- a/apps/explorer/lib/explorer/smart_contract/reader.ex +++ b/apps/explorer/lib/explorer/smart_contract/reader.ex @@ -9,6 +9,7 @@ defmodule Explorer.SmartContract.Reader do alias EthereumJSONRPC.Contract alias Explorer.Chain alias Explorer.Chain.{Hash, SmartContract} + alias Explorer.SmartContract.Helper @typedoc """ Map of functions to call with the values for the function to be called with. @@ -203,7 +204,7 @@ defmodule Explorer.SmartContract.Reader do abi_with_method_id = get_abi_with_method_id(abi) abi_with_method_id - |> Enum.filter(&(&1["constant"] || &1["stateMutability"] == "view")) + |> Enum.filter(&Helper.queriable_method?(&1)) |> Enum.map(&fetch_current_value_from_blockchain(&1, abi_with_method_id, contract_address_hash)) end end @@ -219,7 +220,7 @@ defmodule Explorer.SmartContract.Reader do implementation_abi_with_method_id = get_abi_with_method_id(implementation_abi) implementation_abi_with_method_id - |> Enum.filter(&(&1["constant"] || &1["stateMutability"] == "view")) + |> Enum.filter(&Helper.queriable_method?(&1)) |> Enum.map(&fetch_current_value_from_blockchain(&1, implementation_abi_with_method_id, contract_address_hash)) end end diff --git a/apps/explorer/lib/explorer/smart_contract/writer.ex b/apps/explorer/lib/explorer/smart_contract/writer.ex index 872742fe73..334366b8bf 100644 --- a/apps/explorer/lib/explorer/smart_contract/writer.ex +++ b/apps/explorer/lib/explorer/smart_contract/writer.ex @@ -4,6 +4,7 @@ defmodule Explorer.SmartContract.Writer do """ alias Explorer.Chain + alias Explorer.SmartContract.Helper @spec write_functions(Hash.t()) :: [%{}] def write_functions(contract_address_hash) do @@ -37,21 +38,12 @@ defmodule Explorer.SmartContract.Writer do end def write_function?(function) do - !event?(function) && !constructor?(function) && - (payable?(function) || nonpayable?(function)) + !Helper.event?(function) && !Helper.constructor?(function) && + (Helper.payable?(function) || Helper.nonpayable?(function)) end defp filter_write_functions(abi) do abi |> Enum.filter(&write_function?(&1)) end - - defp event?(function), do: function["type"] == "event" - defp constructor?(function), do: function["type"] == "constructor" - defp payable?(function), do: function["stateMutability"] == "payable" || function["payable"] - - defp nonpayable?(function), - do: - function["stateMutability"] == "nonpayable" || - (!function["payable"] && !function["constant"] && !function["stateMutability"]) end diff --git a/apps/explorer/test/explorer/smart_contract/helper_test.exs b/apps/explorer/test/explorer/smart_contract/helper_test.exs new file mode 100644 index 0000000000..4cd3832434 --- /dev/null +++ b/apps/explorer/test/explorer/smart_contract/helper_test.exs @@ -0,0 +1,127 @@ +defmodule Explorer.SmartContract.HelperTest do + use ExUnit.Case, async: true + + use Explorer.DataCase + alias Explorer.SmartContract.Helper + + describe "payable?" do + test "returns true when there is payable function" do + function = %{ + "type" => "function", + "stateMutability" => "payable", + "payable" => true, + "outputs" => [], + "name" => "upgradeToAndCall", + "inputs" => [ + %{"type" => "uint256", "name" => "version"}, + %{"type" => "address", "name" => "implementation"}, + %{"type" => "bytes", "name" => "data"} + ], + "constant" => false + } + + assert Helper.payable?(function) + end + + test "returns true when there is old-style payable function" do + function = %{ + "type" => "function", + "payable" => true, + "outputs" => [], + "name" => "upgradeToAndCall", + "inputs" => [ + %{"type" => "uint256", "name" => "version"}, + %{"type" => "address", "name" => "implementation"}, + %{"type" => "bytes", "name" => "data"} + ], + "constant" => false + } + + assert Helper.payable?(function) + end + + test "returns false when it is nonpayable function" do + function = %{ + "type" => "function", + "stateMutability" => "nonpayable", + "payable" => false, + "outputs" => [], + "name" => "transferProxyOwnership", + "inputs" => [%{"type" => "address", "name" => "newOwner"}], + "constant" => false + } + + refute Helper.payable?(function) + end + + test "returns false when there is no function" do + function = %{} + + refute Helper.payable?(function) + end + + test "returns false when function is nil" do + function = nil + + refute Helper.payable?(function) + end + end + + describe "nonpayable?" do + test "returns true when there is nonpayable function" do + function = %{ + "type" => "function", + "stateMutability" => "nonpayable", + "payable" => false, + "outputs" => [], + "name" => "transferProxyOwnership", + "inputs" => [%{"type" => "address", "name" => "newOwner"}], + "constant" => false + } + + assert Helper.nonpayable?(function) + end + + test "returns true when there is old-style nonpayable function" do + function = %{ + "type" => "function", + "outputs" => [], + "name" => "test", + "inputs" => [%{"type" => "address", "name" => "newOwner"}], + "constant" => false + } + + assert Helper.nonpayable?(function) + end + + test "returns false when it is payable function" do + function = %{ + "type" => "function", + "stateMutability" => "payable", + "payable" => true, + "outputs" => [], + "name" => "upgradeToAndCall", + "inputs" => [ + %{"type" => "uint256", "name" => "version"}, + %{"type" => "address", "name" => "implementation"}, + %{"type" => "bytes", "name" => "data"} + ], + "constant" => false + } + + refute Helper.nonpayable?(function) + end + + test "returns true when there is no function" do + function = %{} + + refute Helper.nonpayable?(function) + end + + test "returns false when function is nil" do + function = nil + + refute Helper.nonpayable?(function) + end + end +end