Merge pull request #3736 from blockscout/vb-contract-interaction-refactoring
Contract writer: Fix sending a transaction with tuple input typepull/3739/head
commit
db10ef9eea
@ -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 |
||||
} |
||||
} |
@ -0,0 +1,13 @@ |
||||
<div connect-to class="connect-container"> |
||||
<span style="margin-top: -2px;" class="mr-2"> |
||||
<%= render BlockScoutWeb.IconsView, "_inactive_icon.html" %> |
||||
</span> |
||||
<h2 style="margin-top: -2px;">Disconnected</h2> |
||||
<button connect-metamask class="button btn-line ml-4">Connect to Metamask</button> |
||||
</div> |
||||
<div connected-to class="connect-container hidden"> |
||||
<span style="margin-top: -2px;" class="mr-2"> |
||||
<%= render BlockScoutWeb.IconsView, "_active_icon.html" %> |
||||
</span> |
||||
<h2 style="margin-top: -2px;">Connected to</h2><h3 connected-to-address class="ml-2"></h3> |
||||
</div> |
@ -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 |
||||
|
@ -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 |
||||
|
@ -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 |
||||
|
@ -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 |
||||
|
@ -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 |
@ -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 |
@ -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 |
@ -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 |
@ -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 |
@ -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 |
Loading…
Reference in new issue