pull/3160/head
Victor Baranov 4 years ago
parent cd60db1830
commit 3877fb170d
  1. 21
      apps/block_scout_web/lib/block_scout_web/views/smart_contract_view.ex
  2. 81
      apps/block_scout_web/test/block_scout_web/controllers/address_write_contract_controller_test.exs
  3. 81
      apps/block_scout_web/test/block_scout_web/controllers/address_write_proxy_controller_test.exs
  4. 19
      apps/block_scout_web/test/block_scout_web/views/address_write_contract_view_test.exs
  5. 19
      apps/block_scout_web/test/block_scout_web/views/address_write_proxy_view_test copy.exs
  6. 177
      apps/block_scout_web/test/block_scout_web/views/tokens/smart_contract_view_test.exs
  7. 344
      apps/explorer/test/explorer/smart_contract/writer_test.exs

@ -5,18 +5,31 @@ defmodule BlockScoutWeb.SmartContractView do
def queryable?(inputs) when is_nil(inputs), do: false
def writeable?(function),
do: payable?(function) || nonpayable?(function) || fallback?(function) || function["constant"] == false
def writeable?(function) when not is_nil(function),
do:
!constructor?(function) && !event?(function) &&
(payable?(function) || nonpayable?(function))
def writeable?(function) when is_nil(function), do: false
def outputs?(outputs) when not is_nil(outputs), do: Enum.any?(outputs)
def outputs?(outputs) when is_nil(outputs), do: false
def fallback?(function), do: function["type"] == "fallback"
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: function["stateMutability"] == "nonpayable"
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"]

@ -0,0 +1,81 @@
defmodule BlockScoutWeb.AddressWriteContractControllerTest do
use BlockScoutWeb.ConnCase, async: true
alias Explorer.ExchangeRates.Token
alias Explorer.Chain.Address
import Mox
describe "GET index/3" do
setup :set_mox_global
setup do
configuration = Application.get_env(:explorer, :checksum_function)
Application.put_env(:explorer, :checksum_function, :eth)
:ok
on_exit(fn ->
Application.put_env(:explorer, :checksum_function, configuration)
end)
end
test "with invalid address hash", %{conn: conn} do
conn = get(conn, address_write_contract_path(BlockScoutWeb.Endpoint, :index, "invalid_address"))
assert html_response(conn, 404)
end
test "with valid address that is not a contract", %{conn: conn} do
address = insert(:address)
conn = get(conn, address_write_contract_path(BlockScoutWeb.Endpoint, :index, Address.checksum(address.hash)))
assert html_response(conn, 404)
end
test "successfully renders the page when the address is a contract", %{conn: conn} do
contract_address = insert(:contract_address)
transaction = insert(:transaction, from_address: contract_address) |> with_block()
insert(
:internal_transaction_create,
index: 0,
transaction: transaction,
created_contract_address: contract_address,
block_hash: transaction.block_hash,
block_index: 0
)
insert(:smart_contract, address_hash: contract_address.hash)
conn =
get(conn, address_write_contract_path(BlockScoutWeb.Endpoint, :index, Address.checksum(contract_address.hash)))
assert html_response(conn, 200)
assert contract_address.hash == conn.assigns.address.hash
assert %Token{} = conn.assigns.exchange_rate
end
test "returns not found for an unverified contract", %{conn: conn} do
contract_address = insert(:contract_address)
transaction = insert(:transaction, from_address: contract_address) |> with_block()
insert(
:internal_transaction_create,
index: 0,
transaction: transaction,
created_contract_address: contract_address,
block_hash: transaction.block_hash,
block_index: 0
)
conn =
get(conn, address_write_contract_path(BlockScoutWeb.Endpoint, :index, Address.checksum(contract_address.hash)))
assert html_response(conn, 404)
end
end
end

@ -0,0 +1,81 @@
defmodule BlockScoutWeb.AddressWriteProxyControllerTest do
use BlockScoutWeb.ConnCase, async: true
alias Explorer.ExchangeRates.Token
alias Explorer.Chain.Address
import Mox
describe "GET index/3" do
setup :set_mox_global
setup do
configuration = Application.get_env(:explorer, :checksum_function)
Application.put_env(:explorer, :checksum_function, :eth)
:ok
on_exit(fn ->
Application.put_env(:explorer, :checksum_function, configuration)
end)
end
test "with invalid address hash", %{conn: conn} do
conn = get(conn, address_write_proxy_path(BlockScoutWeb.Endpoint, :index, "invalid_address"))
assert html_response(conn, 404)
end
test "with valid address that is not a contract", %{conn: conn} do
address = insert(:address)
conn = get(conn, address_write_proxy_path(BlockScoutWeb.Endpoint, :index, Address.checksum(address.hash)))
assert html_response(conn, 404)
end
test "successfully renders the page when the address is a contract", %{conn: conn} do
contract_address = insert(:contract_address)
transaction = insert(:transaction, from_address: contract_address) |> with_block()
insert(
:internal_transaction_create,
index: 0,
transaction: transaction,
created_contract_address: contract_address,
block_hash: transaction.block_hash,
block_index: 0
)
insert(:smart_contract, address_hash: contract_address.hash)
conn =
get(conn, address_write_proxy_path(BlockScoutWeb.Endpoint, :index, Address.checksum(contract_address.hash)))
assert html_response(conn, 200)
assert contract_address.hash == conn.assigns.address.hash
assert %Token{} = conn.assigns.exchange_rate
end
test "returns not found for an unverified contract", %{conn: conn} do
contract_address = insert(:contract_address)
transaction = insert(:transaction, from_address: contract_address) |> with_block()
insert(
:internal_transaction_create,
index: 0,
transaction: transaction,
created_contract_address: contract_address,
block_hash: transaction.block_hash,
block_index: 0
)
conn =
get(conn, address_write_proxy_path(BlockScoutWeb.Endpoint, :index, Address.checksum(contract_address.hash)))
assert html_response(conn, 404)
end
end
end

@ -0,0 +1,19 @@
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

@ -0,0 +1,19 @@
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

@ -17,6 +17,183 @@ defmodule BlockScoutWeb.SmartContractViewTest do
end
end
describe "writeable?" do
test "returns true when there is write function" do
function = %{
"type" => "function",
"stateMutability" => "nonpayable",
"payable" => false,
"outputs" => [],
"name" => "upgradeTo",
"inputs" => [%{"type" => "uint256", "name" => "version"}, %{"type" => "address", "name" => "implementation"}],
"constant" => false
}
assert SmartContractView.writeable?(function)
end
test "returns false when it is not a write function" do
function = %{
"type" => "function",
"stateMutability" => "view",
"payable" => false,
"outputs" => [%{"type" => "uint256", "name" => ""}],
"name" => "version",
"inputs" => [],
"constant" => true
}
refute SmartContractView.writeable?(function)
end
test "returns false when there is no function" do
function = %{}
refute SmartContractView.writeable?(function)
end
test "returns false when there function is nil" do
function = nil
refute SmartContractView.writeable?(function)
end
end
describe "outputs?" do
test "returns true when there are outputs" do
outputs = [%{"name" => "_narcoId", "type" => "uint256"}]
assert SmartContractView.outputs?(outputs)
end
test "returns false when there are no outputs" do
outputs = []
refute SmartContractView.outputs?(outputs)
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)
end
end
describe "address?" do
test "returns true when the type is equal to the string 'address'" do
type = "address"

@ -0,0 +1,344 @@
defmodule Explorer.SmartContract.WriterTest do
use EthereumJSONRPC.Case
use Explorer.DataCase
import Mox
alias Explorer.SmartContract.Writer
@abi [
%{
"type" => "function",
"stateMutability" => "nonpayable",
"payable" => false,
"outputs" => [],
"name" => "upgradeTo",
"inputs" => [%{"type" => "uint256", "name" => "version"}, %{"type" => "address", "name" => "implementation"}],
"constant" => false
},
%{
"type" => "function",
"stateMutability" => "view",
"payable" => false,
"outputs" => [%{"type" => "uint256", "name" => ""}],
"name" => "version",
"inputs" => [],
"constant" => true
},
%{
"type" => "function",
"stateMutability" => "view",
"payable" => false,
"outputs" => [%{"type" => "address", "name" => ""}],
"name" => "implementation",
"inputs" => [],
"constant" => true
},
%{
"type" => "function",
"stateMutability" => "view",
"payable" => false,
"outputs" => [%{"type" => "address", "name" => ""}],
"name" => "upgradeabilityOwner",
"inputs" => [],
"constant" => true
},
%{
"type" => "function",
"stateMutability" => "payable",
"payable" => true,
"outputs" => [],
"name" => "upgradeToAndCall",
"inputs" => [
%{"type" => "uint256", "name" => "version"},
%{"type" => "address", "name" => "implementation"},
%{"type" => "bytes", "name" => "data"}
],
"constant" => false
},
%{
"type" => "function",
"stateMutability" => "nonpayable",
"payable" => false,
"outputs" => [],
"name" => "transferProxyOwnership",
"inputs" => [%{"type" => "address", "name" => "newOwner"}],
"constant" => false
},
%{"type" => "fallback", "stateMutability" => "payable", "payable" => true},
%{
"type" => "event",
"name" => "ProxyOwnershipTransferred",
"inputs" => [
%{"type" => "address", "name" => "previousOwner", "indexed" => false},
%{"type" => "address", "name" => "newOwner", "indexed" => false}
],
"anonymous" => false
},
%{
"type" => "event",
"name" => "Upgraded",
"inputs" => [
%{"type" => "uint256", "name" => "version", "indexed" => false},
%{"type" => "address", "name" => "implementation", "indexed" => true}
],
"anonymous" => false
}
]
@implementation_abi [
%{
"type" => "function",
"stateMutability" => "view",
"payable" => false,
"outputs" => [%{"type" => "uint256", "name" => ""}],
"name" => "extraReceiverAmount",
"inputs" => [%{"type" => "address", "name" => "_receiver"}],
"constant" => true
},
%{
"type" => "function",
"stateMutability" => "view",
"payable" => false,
"outputs" => [%{"type" => "uint256", "name" => ""}],
"name" => "bridgesAllowedLength",
"inputs" => [],
"constant" => true
},
%{
"type" => "function",
"stateMutability" => "pure",
"payable" => false,
"outputs" => [%{"type" => "bytes4", "name" => ""}],
"name" => "blockRewardContractId",
"inputs" => [],
"constant" => true
},
%{
"type" => "function",
"stateMutability" => "view",
"payable" => false,
"outputs" => [%{"type" => "uint256", "name" => ""}],
"name" => "mintedForAccountInBlock",
"inputs" => [%{"type" => "address", "name" => "_account"}, %{"type" => "uint256", "name" => "_blockNumber"}],
"constant" => true
},
%{
"type" => "function",
"stateMutability" => "view",
"payable" => false,
"outputs" => [%{"type" => "uint256", "name" => ""}],
"name" => "mintedForAccount",
"inputs" => [%{"type" => "address", "name" => "_account"}],
"constant" => true
},
%{
"type" => "function",
"stateMutability" => "view",
"payable" => false,
"outputs" => [%{"type" => "uint256", "name" => ""}],
"name" => "mintedInBlock",
"inputs" => [%{"type" => "uint256", "name" => "_blockNumber"}],
"constant" => true
},
%{
"type" => "function",
"stateMutability" => "view",
"payable" => false,
"outputs" => [%{"type" => "uint256", "name" => ""}],
"name" => "mintedTotally",
"inputs" => [],
"constant" => true
},
%{
"type" => "function",
"stateMutability" => "pure",
"payable" => false,
"outputs" => [%{"type" => "address[1]", "name" => ""}],
"name" => "bridgesAllowed",
"inputs" => [],
"constant" => true
},
%{
"type" => "function",
"stateMutability" => "nonpayable",
"payable" => false,
"outputs" => [],
"name" => "addExtraReceiver",
"inputs" => [%{"type" => "uint256", "name" => "_amount"}, %{"type" => "address", "name" => "_receiver"}],
"constant" => false
},
%{
"type" => "function",
"stateMutability" => "view",
"payable" => false,
"outputs" => [%{"type" => "uint256", "name" => ""}],
"name" => "mintedTotallyByBridge",
"inputs" => [%{"type" => "address", "name" => "_bridge"}],
"constant" => true
},
%{
"type" => "function",
"stateMutability" => "view",
"payable" => false,
"outputs" => [%{"type" => "address", "name" => ""}],
"name" => "extraReceiverByIndex",
"inputs" => [%{"type" => "uint256", "name" => "_index"}],
"constant" => true
},
%{
"type" => "function",
"stateMutability" => "view",
"payable" => false,
"outputs" => [%{"type" => "uint256", "name" => ""}],
"name" => "bridgeAmount",
"inputs" => [%{"type" => "address", "name" => "_bridge"}],
"constant" => true
},
%{
"type" => "function",
"stateMutability" => "view",
"payable" => false,
"outputs" => [%{"type" => "uint256", "name" => ""}],
"name" => "extraReceiversLength",
"inputs" => [],
"constant" => true
},
%{
"type" => "function",
"stateMutability" => "nonpayable",
"payable" => false,
"outputs" => [%{"type" => "address[]", "name" => ""}, %{"type" => "uint256[]", "name" => ""}],
"name" => "reward",
"inputs" => [%{"type" => "address[]", "name" => "benefactors"}, %{"type" => "uint16[]", "name" => "kind"}],
"constant" => false
},
%{
"type" => "event",
"name" => "AddedReceiver",
"inputs" => [
%{"type" => "uint256", "name" => "amount", "indexed" => false},
%{"type" => "address", "name" => "receiver", "indexed" => true},
%{"type" => "address", "name" => "bridge", "indexed" => true}
],
"anonymous" => false
}
]
doctest Explorer.SmartContract.Writer
setup :verify_on_exit!
describe "write_functions/1" do
test "fetches the smart contract write functions" do
smart_contract =
insert(
:smart_contract,
abi: @abi
)
response = Writer.write_functions(smart_contract.address_hash)
assert [
%{
"type" => "function",
"stateMutability" => "nonpayable",
"payable" => false,
"outputs" => [],
"name" => "upgradeTo",
"inputs" => [
%{"type" => "uint256", "name" => "version"},
%{"type" => "address", "name" => "implementation"}
],
"constant" => false
},
%{
"type" => "function",
"stateMutability" => "payable",
"payable" => true,
"outputs" => [],
"name" => "upgradeToAndCall",
"inputs" => [
%{"type" => "uint256", "name" => "version"},
%{"type" => "address", "name" => "implementation"},
%{"type" => "bytes", "name" => "data"}
],
"constant" => false
},
%{
"type" => "function",
"stateMutability" => "nonpayable",
"payable" => false,
"outputs" => [],
"name" => "transferProxyOwnership",
"inputs" => [%{"type" => "address", "name" => "newOwner"}],
"constant" => false
},
%{"type" => "fallback", "stateMutability" => "payable", "payable" => true}
] = response
end
end
describe "write_functions_proxy/1" do
test "fetches the smart contract proxy write functions" do
proxy_smart_contract =
insert(:smart_contract,
abi: @abi
)
implementation_contract_address = insert(:contract_address)
insert(:smart_contract,
address_hash: implementation_contract_address.hash,
abi: @implementation_abi
)
implementation_contract_address_hash_string =
Base.encode16(implementation_contract_address.hash.bytes, case: :lower)
expect(
EthereumJSONRPC.Mox,
:json_rpc,
fn [%{id: id, method: _, params: [%{data: _, to: _}, _]}], _options ->
{:ok,
[
%{
id: id,
jsonrpc: "2.0",
result: "0x000000000000000000000000" <> implementation_contract_address_hash_string
}
]}
end
)
response = Writer.write_functions_proxy(proxy_smart_contract.address_hash)
assert [
%{
"type" => "function",
"stateMutability" => "nonpayable",
"payable" => false,
"outputs" => [],
"name" => "addExtraReceiver",
"inputs" => [
%{"type" => "uint256", "name" => "_amount"},
%{"type" => "address", "name" => "_receiver"}
],
"constant" => false
},
%{
"type" => "function",
"stateMutability" => "nonpayable",
"payable" => false,
"outputs" => [%{"type" => "address[]", "name" => ""}, %{"type" => "uint256[]", "name" => ""}],
"name" => "reward",
"inputs" => [
%{"type" => "address[]", "name" => "benefactors"},
%{"type" => "uint16[]", "name" => "kind"}
],
"constant" => false
}
] = response
end
end
end
Loading…
Cancel
Save