Merge pull request #182 from poanetwork/ams-verify-contract

Developer verifies a Smart Contract
pull/269/head
Amanda 7 years ago committed by GitHub
commit c32fcda0e9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 51
      .circleci/config.yml
  2. 5
      apps/explorer/.sobelow-conf
  3. 5
      apps/explorer/config/config.exs
  4. 30
      apps/explorer/lib/explorer/chain.ex
  5. 4
      apps/explorer/lib/explorer/chain/address.ex
  6. 54
      apps/explorer/lib/explorer/chain/smart_contract.ex
  7. 64
      apps/explorer/lib/explorer/smart_contract/publisher.ex
  8. 104
      apps/explorer/lib/explorer/smart_contract/solidity/code_compiler.ex
  9. 47
      apps/explorer/lib/explorer/smart_contract/solidity/compiler_version.ex
  10. 74
      apps/explorer/lib/explorer/smart_contract/verifier.ex
  11. 19
      apps/explorer/priv/repo/migrations/20180518221256_create_smart_contract_verified.exs
  12. 3
      apps/explorer/priv/solc.bash
  13. 80
      apps/explorer/test/explorer/chain_test.exs
  14. 57
      apps/explorer/test/explorer/smart_contract/publisher_test.exs
  15. 73
      apps/explorer/test/explorer/smart_contract/solidity/code_compiler_test.exs
  16. 48
      apps/explorer/test/explorer/smart_contract/solidity/compiler_version_test.exs
  17. 94
      apps/explorer/test/explorer/smart_contract/verifier_test.exs
  18. 5739
      apps/explorer/test/support/fixture/smart_contract/solc_bin.json
  19. 4
      apps/explorer_web/assets/css/_content.scss
  20. 1
      apps/explorer_web/assets/css/app.scss
  21. 14
      apps/explorer_web/assets/css/components/_all.scss
  22. 3
      apps/explorer_web/lib/explorer_web.ex
  23. 34
      apps/explorer_web/lib/explorer_web/controllers/address_contract_verification_controller.ex
  24. 7
      apps/explorer_web/lib/explorer_web/router.ex
  25. 2
      apps/explorer_web/lib/explorer_web/templates/address/overview.html.eex
  26. 57
      apps/explorer_web/lib/explorer_web/templates/address_contract/index.html.eex
  27. 66
      apps/explorer_web/lib/explorer_web/templates/address_contract_verification/new.html.eex
  28. 12
      apps/explorer_web/lib/explorer_web/templates/address_internal_transaction/index.html.eex
  29. 12
      apps/explorer_web/lib/explorer_web/templates/address_transaction/index.html.eex
  30. 3
      apps/explorer_web/lib/explorer_web/views/address_contract_verification_view.ex
  31. 7
      apps/explorer_web/lib/explorer_web/views/address_contract_view.ex
  32. 2
      apps/explorer_web/lib/explorer_web/views/address_internal_transaction_view.ex
  33. 2
      apps/explorer_web/lib/explorer_web/views/address_transaction_view.ex
  34. 6
      apps/explorer_web/lib/explorer_web/views/address_view.ex
  35. 40
      apps/explorer_web/lib/explorer_web/views/error_helpers.ex
  36. 6
      apps/explorer_web/mix.exs
  37. 76
      apps/explorer_web/priv/gettext/default.pot
  38. 76
      apps/explorer_web/priv/gettext/en/LC_MESSAGES/default.po
  39. 2
      apps/explorer_web/test/explorer_web/controllers/address_contract_controller_test.exs
  40. 74
      apps/explorer_web/test/explorer_web/features/address_contract_verification_test.exs
  41. 30
      apps/explorer_web/test/explorer_web/views/error_helpers_test.exs
  42. 5739
      apps/explorer_web/test/support/fixture/smart_contract/solc_bin.json
  43. 2
      mix.lock

@ -21,14 +21,14 @@ jobs:
- restore_cache:
keys:
- v2-mix-deps-get-{{ checksum "mix.lock" }}
- v2-mix-deps-get-{{ checksum "mix.exs" }}
- v2-mix-deps-get
- v3-mix-deps-get-{{ checksum "mix.lock" }}
- v3-mix-deps-get-{{ checksum "mix.exs" }}
- v3-mix-deps-get
- run: mix deps.get
- save_cache:
key: v2-mix-deps-get-{{ checksum "mix.lock" }}
key: v3-mix-deps-get-{{ checksum "mix.lock" }}
paths: "deps"
- save_cache:
key: mix-deps-get-{{ checksum "mix.exs" }}
@ -39,22 +39,22 @@ jobs:
- restore_cache:
keys:
- v2-npm-install-{{ .Branch }}-{{ checksum "apps/explorer_web/assets/package-lock.json" }}
- v2-npm-install-{{ .Branch }}
- v2-npm-install
- v3-npm-install-{{ .Branch }}-{{ checksum "apps/explorer_web/assets/package-lock.json" }}
- v3-npm-install-{{ .Branch }}
- v3-npm-install
- run:
command: npm install
working_directory: "apps/explorer_web/assets"
- save_cache:
key: v2-npm-install-{{ .Branch }}-{{ checksum "apps/explorer_web/assets/package-lock.json" }}
key: v3-npm-install-{{ .Branch }}-{{ checksum "apps/explorer_web/assets/package-lock.json" }}
paths: "apps/explorer_web/assets/node_modules"
- save_cache:
key: v2-npm-install-{{ .Branch }}
key: v3-npm-install-{{ .Branch }}
paths: "apps/explorer_web/assets/node_modules"
- save_cache:
key: v2-npm-install
key: v3-npm-install
paths: "apps/explorer_web/assets/node_modules"
- run:
@ -66,22 +66,22 @@ jobs:
- restore_cache:
keys:
- v2-mix-compile-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}-{{ checksum "mix.lock" }}
- v2-mix-compile-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}-{{ checksum "mix.exs" }}
- v2-mix-compile-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}
- v3-mix-compile-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}-{{ checksum "mix.lock" }}
- v3-mix-compile-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}-{{ checksum "mix.exs" }}
- v3-mix-compile-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}
- run: mix compile
- save_cache:
key: v2-mix-compile-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}-{{ checksum "mix.lock" }}
key: v3-mix-compile-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}-{{ checksum "mix.lock" }}
paths:
- _build
- save_cache:
key: v2-mix-compile-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}-{{ checksum "mix.exs" }}
key: v3-mix-compile-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}-{{ checksum "mix.exs" }}
paths:
- _build
- save_cache:
key: v2-mix-compile-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}
key: v3-mix-compile-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}
paths:
- _build
@ -112,10 +112,11 @@ jobs:
- mix.exs
- mix.lock
- appspec.yml
check_formatted:
docker:
# Ensure .tool-versions matches
- image: circleci/elixir:1.6.4
- image: circleci/elixir:1.6.5
environment:
MIX_ENV: test
@ -177,9 +178,9 @@ jobs:
- restore_cache:
keys:
- v2-mix-dailyzer-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}-{{ checksum "mix.lock" }}
- v2-mix-dialyzer-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}-{{ checksum "mix.exs" }}
- v2-mix-dialyzer-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}
- v3-mix-dailyzer-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}-{{ checksum "mix.lock" }}
- v3-mix-dialyzer-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}-{{ checksum "mix.exs" }}
- v3-mix-dialyzer-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}
- run:
name: Unpack PLT cache
@ -199,7 +200,7 @@ jobs:
cp ~/.mix/dialyxir*.plt plts/
- save_cache:
key: v2-mix-dialyzer-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}-{{ checksum "mix.lock" }}
key: v3-mix-dialyzer-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}-{{ checksum "mix.lock" }}
paths:
- plts
- save_cache:
@ -310,6 +311,14 @@ jobs:
name: Wait for DB
command: dockerize -wait tcp://localhost:5432 -timeout 1m
- run:
name: Install 'solc'
command: |
echo 'deb http://ppa.launchpad.net/ethereum/ethereum/ubuntu trusty main' | sudo tee /etc/apt/sources.list.d/pgdg.list > /dev/null
sudo apt-key adv --recv-keys --keyserver keyserver.ubuntu.com 923F6CA9
sudo apt-get update
sudo apt-get install solc
- run: mix coveralls.circle --umbrella
- store_test_results:

@ -2,9 +2,10 @@
verbose: false,
private: true,
skip: false,
router: "",
exit: "low",
format: "compact",
ignore: ["Config.HTTPS"],
ignore_files: ["lib/giant_address_migrator.ex", "lib/backfill_transaction_receipt_ids.ex"]
ignore_files: [
"lib/explorer/smart_contract/solidity/code_compiler.ex"
]
]

@ -5,6 +5,8 @@
# is restricted to this project.
use Mix.Config
config :ecto, json_library: Jason
config :explorer, :indexer,
block_rate: 5_000,
debug_logs: !!System.get_env("DEBUG_INDEXER")
@ -26,6 +28,9 @@ config :explorer, Explorer.Market.History.Cataloger, enabled: true
config :explorer, Explorer.Repo, migration_timestamps: [type: :utc_datetime]
config :explorer,
solc_bin_api_url: "https://solc-bin.ethereum.org"
# Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
import_config "#{Mix.env()}.exs"

@ -17,7 +17,8 @@ defmodule Explorer.Chain do
InternalTransaction,
Log,
Transaction,
Wei
Wei,
SmartContract
}
alias Explorer.Chain.Block.Reward
@ -490,6 +491,7 @@ defmodule Explorer.Chain do
query =
from(
address in Address,
preload: [:smart_contract],
where: address.hash == ^hash
)
@ -502,13 +504,14 @@ defmodule Explorer.Chain do
end
def find_contract_address(%Hash{byte_count: unquote(Hash.Truncated.byte_count())} = hash) do
address =
Repo.one(
query =
from(
address in Address,
preload: [:smart_contract],
where: address.hash == ^hash and not is_nil(address.contract_code)
)
)
address = Repo.one(query)
if address do
{:ok, address}
@ -1652,6 +1655,25 @@ defmodule Explorer.Chain do
Wei.to(value, unit)
end
def smart_contract_bytecode(address_hash) do
query =
from(
address in Address,
where: address.hash == ^address_hash,
select: address.contract_code
)
query
|> Repo.one()
|> Data.to_string()
end
def create_smart_contract(attrs \\ %{}) do
%SmartContract{}
|> SmartContract.changeset(attrs)
|> Repo.insert()
end
defp address_hash_to_transactions(
%Hash{byte_count: unquote(Hash.Truncated.byte_count())} = address_hash,
named_arguments

@ -5,7 +5,7 @@ defmodule Explorer.Chain.Address do
use Explorer.Schema
alias Explorer.Chain.{Data, Hash, Wei}
alias Explorer.Chain.{Data, Hash, Wei, SmartContract}
@optional_attrs ~w(contract_code)a
@required_attrs ~w(hash)a
@ -39,6 +39,8 @@ defmodule Explorer.Chain.Address do
field(:balance_fetched_at, :utc_datetime)
field(:contract_code, Data)
has_one(:smart_contract, SmartContract)
timestamps()
end

@ -0,0 +1,54 @@
defmodule Explorer.Chain.SmartContract do
@moduledoc """
The representation of a verified Smart Contract.
"A contract in the sense of Solidity is a collection of code (its functions)
and data (its state) that resides at a specific address on the Ethereum
blockchain."
http://solidity.readthedocs.io/en/v0.4.24/introduction-to-smart-contracts.html
"""
alias Explorer.Chain.{Address, Hash}
use Explorer.Schema
@type t :: %Explorer.Chain.SmartContract{
name: String.t(),
compiler_version: String.t(),
optimization: boolean,
contract_source_code: String.t(),
abi: {:array, :map}
}
schema "smart_contracts" do
field(:name, :string)
field(:compiler_version, :string)
field(:optimization, :boolean)
field(:contract_source_code, :string)
field(:abi, {:array, :map})
belongs_to(
:address,
Address,
foreign_key: :address_hash,
references: :hash,
type: Hash.Truncated
)
timestamps()
end
def changeset(%__MODULE__{} = smart_contract, attrs) do
smart_contract
|> cast(attrs, [:name, :compiler_version, :optimization, :contract_source_code, :address_hash, :abi])
|> validate_required([:name, :compiler_version, :optimization, :contract_source_code, :abi, :address_hash])
|> unique_constraint(:address_hash)
end
def invalid_contract_changeset(%__MODULE__{} = smart_contract, attrs) do
smart_contract
|> cast(attrs, [:name, :compiler_version, :optimization, :contract_source_code, :address_hash])
|> validate_required([:name, :compiler_version, :optimization, :address_hash])
|> add_error(:contract_source_code, "there was an error validating your contract, please try again.")
end
end

@ -0,0 +1,64 @@
defmodule Explorer.SmartContract.Publisher do
@moduledoc """
Module responsible to control the contract verification.
"""
alias Explorer.Chain
alias Explorer.Chain.SmartContract
alias Explorer.SmartContract.Verifier
@doc """
Evaluates smart contract authenticity and saves its details.
## Examples
Explorer.SmartContract.Publisher.publish(
"0x0f95fa9bc0383e699325f2658d04e8d96d87b90c",
%{
"compiler" => "0.4.24",
"contract_source_code" => "pragma solidity ^0.4.24; contract SimpleStorage { uint storedData; function set(uint x) public { storedData = x; } function get() public constant returns (uint) { return storedData; } }",
"name" => "SimpleStorage",
"optimization" => false
}
)
#=> {:ok, %Explorer.Chain.SmartContract{}}
"""
def publish(address_hash, params) do
case Verifier.evaluate_authenticity(address_hash, params) do
{:ok, %{abi: abi}} ->
publish_smart_contract(address_hash, params, abi)
{:error, _} ->
{:error, unverified_smart_contract(address_hash, params)}
end
end
defp publish_smart_contract(address_hash, params, abi) do
address_hash
|> attributes(params, abi)
|> Chain.create_smart_contract()
end
defp unverified_smart_contract(address_hash, params) do
attrs = attributes(address_hash, params)
changeset =
SmartContract.invalid_contract_changeset(
%SmartContract{address_hash: address_hash},
attrs
)
%{changeset | action: :insert}
end
defp attributes(address_hash, params, abi \\ %{}) do
%{
address_hash: address_hash,
name: params["name"],
compiler_version: params["compiler"],
optimization: params["optimization"],
contract_source_code: params["contract_source_code"],
abi: abi
}
end
end

@ -0,0 +1,104 @@
defmodule Explorer.SmartContract.Solidity.CodeCompiler do
@moduledoc """
Module responsible to compile the Solidity code of a given Smart Contract.
"""
@doc ~S"""
Compiles a code in the solidity command line.
Returns a `Map`.
## Examples
iex(1)> Explorer.SmartContract.Solidity.CodeCompiler.run("SimpleStorage", "pragma solidity ^0.4.23; contract SimpleStorage {uint storedData; function set(uint x) public {storedData = x; } function get() public constant returns (uint) {return storedData; } }", false)
%{
"contracts" => %{
"SimpleStorage" => %{
"SimpleStorage" => %{
"abi" => [
%{
"constant" => false,
"inputs" => [%{"name" => "x", "type" => "uint256"}],
"name" => "set",
"outputs" => [],
"payable" => false,
"stateMutability" => "nonpayable",
"type" => "function"
},
%{
"constant" => true,
"inputs" => [],
"name" => "get",
"outputs" => [%{"name" => "", "type" => "uint256"}],
"payable" => false,
"stateMutability" => "view",
"type" => "function"
}
],
"evm" => %{
"bytecode" => %{
"linkReferences" => %{},
"object" => "608060405234801561001057600080fd5b5060df8061001f6000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a72305820017172d01c000255d5c74c0efce764adf7c4ae444d7f7e2ed852f6fb9b73df5d0029",
"opcodes" => "PUSH1 0x80 PUSH1 0x40 MSTORE CALLVALUE DUP1 ISZERO PUSH2 0x10 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0xDF DUP1 PUSH2 0x1F PUSH1 0x0 CODECOPY PUSH1 0x0 RETURN STOP PUSH1 0x80 PUSH1 0x40 MSTORE PUSH1 0x4 CALLDATASIZE LT PUSH1 0x49 JUMPI PUSH1 0x0 CALLDATALOAD PUSH29 0x100000000000000000000000000000000000000000000000000000000 SWAP1 DIV PUSH4 0xFFFFFFFF AND DUP1 PUSH4 0x60FE47B1 EQ PUSH1 0x4E JUMPI DUP1 PUSH4 0x6D4CE63C EQ PUSH1 0x78 JUMPI JUMPDEST PUSH1 0x0 DUP1 REVERT JUMPDEST CALLVALUE DUP1 ISZERO PUSH1 0x59 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x76 PUSH1 0x4 DUP1 CALLDATASIZE SUB DUP2 ADD SWAP1 DUP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 POP POP POP PUSH1 0xA0 JUMP JUMPDEST STOP JUMPDEST CALLVALUE DUP1 ISZERO PUSH1 0x83 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x8A PUSH1 0xAA JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST DUP1 PUSH1 0x0 DUP2 SWAP1 SSTORE POP POP JUMP JUMPDEST PUSH1 0x0 DUP1 SLOAD SWAP1 POP SWAP1 JUMP STOP LOG1 PUSH6 0x627A7A723058 KECCAK256 ADD PUSH18 0x72D01C000255D5C74C0EFCE764ADF7C4AE44 0x4d PUSH32 0x7E2ED852F6FB9B73DF5D00290000000000000000000000000000000000000000 ",
"sourceMap" => "25:157:0:-;;;;8:9:-1;5:2;;;30:1;27;20:12;5:2;25:157:0;;;;;;;"
},
"deployedBytecode" => %{
"linkReferences" => %{},
"object" => "6080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a72305820017172d01c000255d5c74c0efce764adf7c4ae444d7f7e2ed852f6fb9b73df5d0029",
"opcodes" => "PUSH1 0x80 PUSH1 0x40 MSTORE PUSH1 0x4 CALLDATASIZE LT PUSH1 0x49 JUMPI PUSH1 0x0 CALLDATALOAD PUSH29 0x100000000000000000000000000000000000000000000000000000000 SWAP1 DIV PUSH4 0xFFFFFFFF AND DUP1 PUSH4 0x60FE47B1 EQ PUSH1 0x4E JUMPI DUP1 PUSH4 0x6D4CE63C EQ PUSH1 0x78 JUMPI JUMPDEST PUSH1 0x0 DUP1 REVERT JUMPDEST CALLVALUE DUP1 ISZERO PUSH1 0x59 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x76 PUSH1 0x4 DUP1 CALLDATASIZE SUB DUP2 ADD SWAP1 DUP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 POP POP POP PUSH1 0xA0 JUMP JUMPDEST STOP JUMPDEST CALLVALUE DUP1 ISZERO PUSH1 0x83 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x8A PUSH1 0xAA JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST DUP1 PUSH1 0x0 DUP2 SWAP1 SSTORE POP POP JUMP JUMPDEST PUSH1 0x0 DUP1 SLOAD SWAP1 POP SWAP1 JUMP STOP LOG1 PUSH6 0x627A7A723058 KECCAK256 ADD PUSH18 0x72D01C000255D5C74C0EFCE764ADF7C4AE44 0x4d PUSH32 0x7E2ED852F6FB9B73DF5D00290000000000000000000000000000000000000000 ",
"sourceMap" => "25:157:0:-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;66:46;;8:9:-1;5:2;;;30:1;27;20:12;5:2;66:46:0;;;;;;;;;;;;;;;;;;;;;;;;;;113:67;;8:9:-1;5:2;;;30:1;27;20:12;5:2;113:67:0;;;;;;;;;;;;;;;;;;;;;;;66:46;108:1;95:10;:14;;;;66:46;:::o;113:67::-;153:4;167:10;;160:17;;113:67;:::o"
},
"gasEstimates" => %{
"creation" => %{
"codeDepositCost" => "44600",
"executionCost" => "93",
"totalCost" => "44693"
},
"external" => %{"get()" => "424", "set(uint256)" => "20205"}
}
},
"metadata" => "{\"compiler\":{\"version\":\"0.4.24+commit.e67f0147\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"constant\":false,\"inputs\":[{\"name\":\"x\",\"type\":\"uint256\"}],\"name\":\"set\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"get\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"}],\"devdoc\":{\"methods\":{}},\"userdoc\":{\"methods\":{}}},\"settings\":{\"compilationTarget\":{\"SimpleStorage\":\"SimpleStorage\"},\"evmVersion\":\"byzantium\",\"libraries\":{},\"optimizer\":{\"enabled\":false,\"runs\":200},\"remappings\":[]},\"sources\":{\"SimpleStorage\":{\"keccak256\":\"0x3f5ecc4c6077dffdfc98d9781295205833bf0558bc2a0c86fc3d5f246808ba34\",\"urls\":[\"bzzr://60d588b13340f26a038f51934f9b8c3cf3928372bc4358848a86960040a3a8e2\"]}},\"version\":1}"
}
}
},
"sources" => %{"SimpleStorage" => %{"id" => 0}}
}
"""
def run(name, code, optimization) do
{response, _status} =
System.cmd(
Application.app_dir(:explorer, "priv/solc.bash"),
[generate_settings(name, code, optimization)]
)
Jason.decode!(response)
end
@doc """
For more output options check the documentation.
https://solidity.readthedocs.io/en/v0.4.24/using-the-compiler.html#compiler-input-and-output-json-description
"""
def generate_settings(name, code, optimization) do
"""
{
"language": "Solidity",
"sources": {
"#{name}":
{
"content": "#{code}"
}
},
"settings": {
"optimizer": {
"enabled": #{optimization}
},
"outputSelection": {
"*": {
"*": [ "evm.bytecode", "evm.deployedBytecode", "evm.gasEstimates", "abi", "metadata" ]
}
}
}
}
"""
end
end

@ -0,0 +1,47 @@
defmodule Explorer.SmartContract.Solidity.CompilerVersion do
@moduledoc """
Adapter for fetching compiler versions from https://solc-bin.ethereum.org/bin/list.json.
"""
@doc """
Fetches a list of compilers from the Ethereum Solidity API.
"""
@spec fetch_versions :: {atom, [map]}
def fetch_versions do
headers = [{"Content-Type", "application/json"}]
case HTTPoison.get(source_url(), headers) do
{:ok, %{status_code: 200, body: body}} ->
{:ok, format_data(body)}
{:ok, %{status_code: _status_code, body: body}} ->
{:error, decode_json(body)["error"]}
{:error, %{reason: reason}} ->
{:error, reason}
end
end
defp format_data(json) do
{:ok, releases} =
json
|> Jason.decode!()
|> Map.fetch("releases")
releases
|> Map.to_list()
|> Enum.map(fn {key, _value} -> {key, key} end)
|> Enum.sort()
|> Enum.reverse()
end
defp decode_json(json) do
Jason.decode!(json)
end
defp source_url do
solc_bin_api_url = Application.get_env(:explorer, :solc_bin_api_url)
"#{solc_bin_api_url}/bin/list.json"
end
end

@ -0,0 +1,74 @@
defmodule Explorer.SmartContract.Verifier do
@moduledoc """
Module responsible to verify the Smart Contract.
Given a contract source code the bytecode will be generated and matched
against the existing Creation Address Bytecode, if it matches the contract is
then Verified.
"""
alias Explorer.Chain
alias Explorer.SmartContract.Solidity.CodeCompiler
def evaluate_authenticity(_, %{"name" => ""}), do: {:error, :name}
def evaluate_authenticity(_, %{"contract_source_code" => ""}), do: {:error, :contract_source_code}
def evaluate_authenticity(address_hash, %{
"name" => name,
"contract_source_code" => contract_source_code,
"optimization" => optimization
}) do
solc_output = CodeCompiler.run(name, contract_source_code, optimization)
case solc_output do
%{
"contracts" => %{
^name => %{
^name => %{
"abi" => abi,
"evm" => %{
"bytecode" => %{"object" => bytecode}
}
}
}
}
} ->
compare_bytecodes(address_hash, abi, bytecode)
_ ->
{:error, :compilation}
end
end
@doc """
In order to discover the bytecode we need to remove the `swarm source` from
the hash.
`64` characters to the left of `0029` are the `swarm source`. The rest on
the left is the `bytecode` to be validated.
"""
def extract_bytecode(code) do
{bytecode, _swarm_source} =
code
|> String.split("0029")
|> List.first()
|> String.split_at(-64)
bytecode
end
defp compare_bytecodes(address_hash, abi, bytecode) do
generated_bytecode = extract_bytecode(bytecode)
"0x" <> blockchain_bytecode =
address_hash
|> Chain.smart_contract_bytecode()
|> extract_bytecode
if generated_bytecode == blockchain_bytecode do
{:ok, %{abi: abi}}
else
{:error, :generated_bytecode}
end
end
end

@ -0,0 +1,19 @@
defmodule Explorer.Repo.Migrations.CreateSmartContractVerified do
use Ecto.Migration
def change do
create table(:smart_contracts) do
add(:name, :string, null: false)
add(:compiler_version, :string, null: false)
add(:optimization, :boolean, null: false)
add(:contract_source_code, :text, null: false)
add(:abi, :jsonb, null: false)
add(:address_hash, references(:addresses, column: :hash, on_delete: :delete_all, type: :bytea), null: false)
timestamps()
end
create(unique_index(:smart_contracts, :address_hash))
end
end

@ -0,0 +1,3 @@
#!/bin/bash
echo "$1" | solc --standard-json

@ -4,7 +4,8 @@ defmodule Explorer.ChainTest do
import Explorer.Factory
alias Explorer.{Chain, Repo, Factory}
alias Explorer.Chain.{Address, Block, InternalTransaction, Log, Transaction, Wei}
alias Explorer.Chain.{Address, Block, InternalTransaction, Log, Transaction, Wei, SmartContract}
doctest Explorer.Chain
@ -821,7 +822,7 @@ defmodule Explorer.ChainTest do
end
test "finds an contract address" do
address = insert(:address, contract_code: Factory.data("contract_code"))
address = insert(:address, contract_code: Factory.data("contract_code"), smart_contract: nil)
response = Chain.find_contract_address(address.hash)
@ -872,4 +873,79 @@ defmodule Explorer.ChainTest do
assert [] == Explorer.Chain.recent_collated_transactions()
end
end
describe "smart_contract_bytecode/1" do
test "fetches the smart contract bytecode" do
smart_contract_bytecode =
"0x608060405234801561001057600080fd5b5060df8061001f6000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a7230582040d82a7379b1ee1632ad4d8a239954fd940277b25628ead95259a85c5eddb2120029"
created_contract_address = insert(:address, contract_code: smart_contract_bytecode)
insert(
:internal_transaction,
index: 0,
created_contract_address_hash: created_contract_address.hash,
created_contract_code: smart_contract_bytecode
)
assert Chain.smart_contract_bytecode(created_contract_address.hash) == smart_contract_bytecode
end
end
describe "create_smart_contract/1" do
test "with valid data creates a smart contract" do
smart_contract_bytecode =
"0x608060405234801561001057600080fd5b5060df8061001f6000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a7230582040d82a7379b1ee1632ad4d8a239954fd940277b25628ead95259a85c5eddb2120029"
created_contract_address =
insert(
:address,
hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c",
contract_code: smart_contract_bytecode
)
insert(
:internal_transaction,
index: 0,
created_contract_address_hash: created_contract_address.hash,
created_contract_code: smart_contract_bytecode
)
valid_attrs = %{
address_hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c",
name: "SimpleStorage",
compiler_version: "0.4.23",
optimization: false,
contract_source_code:
"pragma solidity ^0.4.23; contract SimpleStorage {uint storedData; function set(uint x) public {storedData = x; } function get() public constant returns (uint) {return storedData; } }",
abi: [
%{
"constant" => false,
"inputs" => [%{"name" => "x", "type" => "uint256"}],
"name" => "set",
"outputs" => [],
"payable" => false,
"stateMutability" => "nonpayable",
"type" => "function"
},
%{
"constant" => true,
"inputs" => [],
"name" => "get",
"outputs" => [%{"name" => "", "type" => "uint256"}],
"payable" => false,
"stateMutability" => "view",
"type" => "function"
}
]
}
assert {:ok, %SmartContract{} = smart_contract} = Chain.create_smart_contract(valid_attrs)
assert smart_contract.name == "SimpleStorage"
assert smart_contract.compiler_version == "0.4.23"
assert smart_contract.optimization == false
assert smart_contract.contract_source_code != ""
assert smart_contract.abi != ""
end
end
end

@ -0,0 +1,57 @@
defmodule Explorer.SmartContract.PublisherTest do
use ExUnit.Case, async: true
use Explorer.DataCase
doctest Explorer.SmartContract.Publisher
alias Explorer.Chain.{SmartContract, Hash}
alias Explorer.SmartContract.Publisher
describe "publish/2" do
test "with valid data creates a smart_contract" do
address_hash = "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c"
smart_contract_bytecode =
"0x608060405234801561001057600080fd5b5060df8061001f6000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a7230582040d82a7379b1ee1632ad4d8a239954fd940277b25628ead95259a85c5eddb2120029"
created_contract_address = insert(:address, hash: address_hash, contract_code: smart_contract_bytecode)
insert(
:internal_transaction,
index: 0,
created_contract_address_hash: created_contract_address.hash,
created_contract_code: smart_contract_bytecode
)
valid_attrs = %{
"contract_source_code" =>
"pragma solidity ^0.4.23;\r\n\r\ncontract SimpleStorage {\r\n uint storedData;\r\n\r\n function set(uint x) public {\r\n storedData = x;\r\n }\r\n\r\n function get() public constant returns (uint) {\r\n return storedData;\r\n }\r\n}",
"compiler" => "0.4.24",
"name" => "SimpleStorage",
"optimization" => false
}
assert {:ok, %SmartContract{} = smart_contract} = Publisher.publish(address_hash, valid_attrs)
assert smart_contract.name == valid_attrs["name"]
assert Hash.to_string(smart_contract.address_hash) == address_hash
assert smart_contract.compiler_version == valid_attrs["compiler"]
assert smart_contract.optimization == valid_attrs["optimization"]
assert smart_contract.contract_source_code == valid_attrs["contract_source_code"]
assert smart_contract.abi != nil
end
test "with invalid data returns error changeset" do
address_hash = ""
invalid_attrs = %{
"contract_source_code" => "",
"compiler" => "",
"name" => "",
"optimization" => ""
}
assert {:error, %Ecto.Changeset{}} = Publisher.publish(address_hash, invalid_attrs)
end
end
end

@ -0,0 +1,73 @@
defmodule Explorer.SmartContract.Solidity.CodeCompilerTest do
use ExUnit.Case, async: true
doctest Explorer.SmartContract.Solidity.CodeCompiler
alias Explorer.SmartContract.Solidity.CodeCompiler
describe "run/2" do
test "compiles a smart contract using the solidity command line" do
name = "SimpleStorage"
optimization = false
code = """
pragma solidity ^0.4.24;
contract SimpleStorage {
uint storedData;
function set(uint x) public {
storedData = x;
}
function get() public constant returns (uint) {
return storedData;
}
}
"""
response = CodeCompiler.run(name, code, optimization)
assert %{
"contracts" => %{
^name => %{
^name => %{
"abi" => _,
"evm" => %{
"bytecode" => %{"object" => _}
}
}
}
}
} = response
end
end
describe "generate_settings/2" do
test "creates a json file with the solidity compiler expected settings" do
name = "SimpleStorage"
optimization = false
code = """
pragma solidity ^0.4.24;
contract SimpleStorage {
uint storedData;
function set(uint x) public {
storedData = x;
}
function get() public constant returns (uint) {
return storedData;
}
}
"""
generated = CodeCompiler.generate_settings(name, code, optimization)
assert String.contains?(generated, "contract SimpleStorage") == true
assert String.contains?(generated, "settings") == true
end
end
end

@ -0,0 +1,48 @@
defmodule Explorer.SmartContract.Solidity.CompilerVersionTest do
use ExUnit.Case
doctest Explorer.SmartContract.Solidity.CompilerVersion
alias Explorer.SmartContract.Solidity.CompilerVersion
alias Plug.Conn
describe "fetch_versions" do
setup do
bypass = Bypass.open()
Application.put_env(:explorer, :solc_bin_api_url, "http://localhost:#{bypass.port}")
{:ok, bypass: bypass}
end
test "fetches the list of the solidity compiler versions", %{bypass: bypass} do
Bypass.expect(bypass, fn conn ->
assert "GET" == conn.method
assert "/bin/list.json" == conn.request_path
Conn.resp(conn, 200, solc_bin_versions())
end)
assert {:ok, versions} = CompilerVersion.fetch_versions()
assert Enum.any?(versions, fn item -> item == {"0.4.9", "0.4.9"} end) == true
end
test "returns error when list of versions is not available", %{bypass: bypass} do
Bypass.expect(bypass, fn conn ->
Conn.resp(conn, 400, ~S({"error": "bad request"}))
end)
assert {:error, "bad request"} = CompilerVersion.fetch_versions()
end
test "returns error when there is server error", %{bypass: bypass} do
Bypass.down(bypass)
assert {:error, :econnrefused} = CompilerVersion.fetch_versions()
end
end
def solc_bin_versions() do
File.read!("./test/support/fixture/smart_contract/solc_bin.json")
end
end

@ -0,0 +1,94 @@
defmodule Explorer.SmartContract.VerifierTest do
use ExUnit.Case, async: true
use Explorer.DataCase
doctest Explorer.SmartContract.Verifier
alias Explorer.SmartContract.Verifier
describe "evaluate_authenticity/2" do
test "verifies the generated bytecode against bytecode retrieved from the blockchain" do
address_hash = "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c"
smart_contract_bytecode =
"0x608060405234801561001057600080fd5b5060df8061001f6000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a7230582040d82a7379b1ee1632ad4d8a239954fd940277b25628ead95259a85c5eddb2120029"
created_contract_address = insert(:address, hash: address_hash, contract_code: smart_contract_bytecode)
insert(
:internal_transaction,
index: 0,
created_contract_address_hash: created_contract_address.hash,
created_contract_code: smart_contract_bytecode
)
params = %{
"contract_source_code" =>
"pragma solidity ^0.4.24; contract SimpleStorage { uint storedData; function set(uint x) public { storedData = x; } function get() public constant returns (uint) { return storedData; } }",
"compiler" => "0.4.24",
"name" => "SimpleStorage",
"optimization" => false
}
assert {:ok, %{abi: abi}} = Verifier.evaluate_authenticity(address_hash, params)
assert abi != nil
end
test "returns error when bytecoed doesn't match" do
address_hash = "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c"
wrong_smart_contract_bytecode =
"0x6080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a723058207722b6ddfe522b31e50b878ced2f22d051e8e2cd19be7b4fba9686602b90ba2b0029"
created_contract_address = insert(:address, hash: address_hash, contract_code: wrong_smart_contract_bytecode)
insert(
:internal_transaction,
index: 0,
created_contract_address_hash: created_contract_address.hash,
created_contract_code: wrong_smart_contract_bytecode
)
params = %{
"contract_source_code" =>
"pragma solidity ^0.4.24; contract SimpleStorage { uint storedData; function set(uint x) public { storedData = x; } function get() public constant returns (uint) { return storedData; } }",
"compiler" => "0.4.24",
"name" => "SimpleStorage",
"optimization" => false
}
assert {:error, :generated_bytecode} = Verifier.evaluate_authenticity(address_hash, params)
end
test "returns error when there is a compilation problem" do
address_hash = "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c"
params = %{
"contract_source_code" => "pragma solidity ^0.4.24; contract SimpleStorage { ",
"compiler" => "0.4.24",
"name" => "SimpleStorage",
"optimization" => false
}
assert {:error, :compilation} = Verifier.evaluate_authenticity(address_hash, params)
end
end
describe "extract_bytecode/1" do
test "extracts the bytecode from the hash" do
code =
"0x608060405234801561001057600080fd5b5060df8061001f6000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a723058203c381c1b48b38d050c54d7ef296ecd411040e19420dfec94772b9c49ae106a0b0029"
swarm_source = "3c381c1b48b38d050c54d7ef296ecd411040e19420dfec94772b9c49ae106a0b"
bytecode =
"0x608060405234801561001057600080fd5b5060df8061001f6000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a72305820"
assert bytecode == Verifier.extract_bytecode(code)
assert bytecode != code
assert String.contains?(code, bytecode) == true
assert String.contains?(bytecode, "0029") == false
assert String.contains?(bytecode, swarm_source) == false
end
end
end

@ -68,3 +68,7 @@
}
}
}
.has-error {
color: $red;
}

@ -38,6 +38,7 @@ $fa-font-path: "~@fortawesome/fontawesome-free/webfonts";
@import "node_modules/bootstrap/scss/tables";
@import "node_modules/bootstrap/scss/nav";
@import "node_modules/bootstrap/scss/card";
@import "node_modules/bootstrap/scss/forms";
//Custom theme
@import "theme/theme_ribbon";

@ -0,0 +1,14 @@
@import "section";
@import "dot";
@import "address";
@import "block";
@import "blocks";
@import "chain";
@import "footer";
@import "header";
@import "internal_transaction";
@import "pagination";
@import "transaction";
@import "transaction_log";
@import "transactions";

@ -24,6 +24,7 @@ defmodule ExplorerWeb do
import ExplorerWeb.Controller
import ExplorerWeb.Router.Helpers
import ExplorerWeb.Gettext
import ExplorerWeb.ErrorHelpers
import Plug.Conn
end
end
@ -40,7 +41,7 @@ defmodule ExplorerWeb do
# Use all HTML functionality (forms, tags, etc)
use Phoenix.HTML
import ExplorerWeb.{CurrencyHelpers, Gettext, Router.Helpers, WeiHelpers}
import ExplorerWeb.{CurrencyHelpers, Gettext, Router.Helpers, WeiHelpers, ErrorHelpers}
import Scrivener.HTML
end
end

@ -0,0 +1,34 @@
defmodule ExplorerWeb.AddressContractVerificationController do
use ExplorerWeb, :controller
alias Explorer.Chain.SmartContract
alias Explorer.SmartContract.{Solidity.CompilerVersion, Publisher}
def new(conn, %{"address_id" => address_hash_string}) do
changeset =
SmartContract.changeset(
%SmartContract{address_hash: address_hash_string},
%{}
)
{:ok, compiler_versions} = CompilerVersion.fetch_versions()
render(conn, "new.html", changeset: changeset, compiler_versions: compiler_versions)
end
def create(conn, %{
"address_id" => address_hash_string,
"smart_contract" => smart_contract,
"locale" => locale
}) do
case Publisher.publish(address_hash_string, smart_contract) do
{:ok, _smart_contract} ->
redirect(conn, to: address_contract_path(conn, :index, locale, address_hash_string))
{:error, changeset} ->
{:ok, compiler_versions} = CompilerVersion.fetch_versions()
render(conn, "new.html", changeset: changeset, compiler_versions: compiler_versions)
end
end
end

@ -80,6 +80,13 @@ defmodule ExplorerWeb.Router do
only: [:index],
as: :contract
)
resources(
"/contract_verifications",
AddressContractVerificationController,
only: [:new, :create],
as: :verify_contract
)
end
get("/search", ChainController, :search)

@ -8,7 +8,7 @@
</div>
</div>
<div class="col-sm-10 align-self-center">
<h1><%= gettext "Address" %></h1>
<h1><%= address_title(@address) %></h1>
<p class="mb-0" data-test="address_detail_hash"><%= @address %></p>
</div>
</div>

@ -22,18 +22,63 @@
</li>
<li class="nav-item">
<%= link(
gettext("Contract"),
class: "nav-link active",
to: address_contract_path(@conn, :index, @conn.assigns.locale, @conn.params["address_id"])
) %>
to: address_contract_path(@conn, :index, @conn.assigns.locale, @conn.params["address_id"]),
class: "nav-link active") do %>
<%= gettext("Code") %>
<%= if smart_contract_verified?(@address) do %>
<i class="far fa-check-circle"></i>
<% end %>
<% end %>
</li>
</ul>
</div>
<div class="card-body">
<h2 class="card-title"><%= gettext "Contract bytecode" %></h2>
<%= if !smart_contract_verified?(@address) do %>
<p>
<%= link(
to: address_verify_contract_path(@conn, :new, @conn.assigns.locale, @conn.params["address_id"])
) do %>
<i class="fas fa-info-circle"></i>
<%= gettext("Verify and Publish") %>
<% end %>
</p>
<% end %>
<%= if smart_contract_verified?(@address) do %>
<table class="table">
<tr>
<th>Contract name:</th>
<td><%= @address.smart_contract.name %></td>
</tr>
<tr>
<th>Optimization enabled:</th>
<td><%= format_optimization(@address.smart_contract.optimization) %></td>
</tr>
<tr>
<th>Compiler version:</th>
<td><%= @address.smart_contract.compiler_version %></td>
</tr>
</table>
<h4>Contract source code</h4>
<pre class="p-2 bg-light mb-4">
<code>
<%= text_to_html(@address.smart_contract.contract_source_code, insert_brs: false) %>
</code>
</pre>
<h4>Contract ABI</h4>
<pre class="p-2 bg-light mb-4">
<code>
<%= format_smart_contract_abi(@address.smart_contract.abi) %>
</code>
</pre>
<% end %>
<pre class="pre-wrap p-2 bg-light mb-0">
<h4>Contract creation code</h4>
<pre class="pre-wrap p-2 bg-light mb-4">
<code><%= @address.contract_code %></code>
</pre>
</div>

@ -0,0 +1,66 @@
<section class="container-fluid">
<h1>Smart Contract</h1>
<div class="card">
<div class="card-header">
<ul class="nav nav-tabs card-header-tabs">
<li class="nav-item">
<%= link(
gettext("Contract Source Code"),
class: "nav-link active",
to: '#'
) %>
</li>
</ul>
</div>
<div class="card-body">
<%= form_for @changeset,
address_verify_contract_path(@conn, :create, @conn.assigns.locale, @conn.params["address_id"]),
fn f -> %>
<div class="form-group">
<%= label f, :address_hash, "Contract Address" %>
<%= text_input f, :address_hash, class: "form-control", disabled: true %>
<%= error_tag f, :address_hash %>
</div>
<div class="form-group">
<%= label f, :name, "Contract Name" %>
<%= text_input f, :name, class: "form-control" %>
<%= error_tag f, :name %>
</div>
<div class="form-group">
<%= label f, :compiler, "Compiler" %>
<%= select f, :compiler, @compiler_versions, class: "form-control", selected: "0.4.24" %>
<%= error_tag f, :compiler %>
</div>
<div class="form-group">
<%= label f, "Optimization", class: "d-block" %>
<div class="form-check form-check-inline">
<%= radio_button f, :optimization, false, checked: true, class: "form-check-input" %>
<%= label :smart_contract_optimization, :false, "No", class: "form-check-label" %>
</div>
<div class="form-check form-check-inline">
<%= radio_button f, :optimization, true, class: "form-check-input" %>
<%= label :smart_contract_optimization, :true, "Yes", class: "form-check-label" %>
</div>
<%= error_tag f, :optimization %>
</div>
<div class="form-group">
<%= label f, :contract_source_code, "Enter the Solidity Contract Code below" %>
<%= textarea f, :contract_source_code, class: "form-control", rows: 3 %>
<%= error_tag f, :contract_source_code %>
</div>
<%= submit "Verify and publish", class: "button button--secondary button--sm" %>
<%= reset "Reset", class: "button button--tertiary button--sm" %>
<% end %>
</div>
</div>
</section>

@ -24,10 +24,14 @@
<%= if contract?(@address) do %>
<li class="nav-item">
<%= link(
gettext("Contract"),
class: "nav-link",
to: address_contract_path(@conn, :index, @conn.assigns.locale, @conn.params["address_id"])
) %>
to: address_contract_path(@conn, :index, @conn.assigns.locale, @conn.params["address_id"]),
class: "nav-link") do %>
<%= gettext("Code") %>
<%= if smart_contract_verified?(@address) do %>
<i class="far fa-check-circle"></i>
<% end %>
<% end %>
</li>
<% end %>
</ul>

@ -24,10 +24,14 @@
<%= if contract?(@address) do %>
<li class="nav-item">
<%= link(
gettext("Contract"),
class: "nav-link",
to: address_contract_path(@conn, :index, @conn.assigns.locale, @conn.params["address_id"])
) %>
to: address_contract_path(@conn, :index, @conn.assigns.locale, @conn.params["address_id"]),
class: "nav-link") do %>
<%= gettext("Code") %>
<%= if smart_contract_verified?(@address) do %>
<i class="far fa-check-circle"></i>
<% end %>
<% end %>
</li>
<% end %>
</ul>

@ -0,0 +1,3 @@
defmodule ExplorerWeb.AddressContractVerificationView do
use ExplorerWeb, :view
end

@ -1,3 +1,10 @@
defmodule ExplorerWeb.AddressContractView do
use ExplorerWeb, :view
import ExplorerWeb.AddressView, only: [smart_contract_verified?: 1]
def format_smart_contract_abi(abi), do: Poison.encode!(abi, pretty: true)
def format_optimization(true), do: gettext("true")
def format_optimization(false), do: gettext("false")
end

@ -1,7 +1,7 @@
defmodule ExplorerWeb.AddressInternalTransactionView do
use ExplorerWeb, :view
import ExplorerWeb.AddressView, only: [contract?: 1]
import ExplorerWeb.AddressView, only: [contract?: 1, smart_contract_verified?: 1]
def format_current_filter(filter) do
case filter do

@ -1,7 +1,7 @@
defmodule ExplorerWeb.AddressTransactionView do
use ExplorerWeb, :view
import ExplorerWeb.AddressView, only: [contract?: 1]
import ExplorerWeb.AddressView, only: [contract?: 1, smart_contract_verified?: 1]
alias ExplorerWeb.TransactionView

@ -1,7 +1,8 @@
defmodule ExplorerWeb.AddressView do
use ExplorerWeb, :view
alias Explorer.Chain.{Address, Wei}
alias Explorer.Chain.{Address, Wei, SmartContract}
alias Explorer.ExchangeRates.Token
alias ExplorerWeb.ExchangeRates.USD
@ -51,4 +52,7 @@ defmodule ExplorerWeb.AddressView do
|> QRCode.to_png()
|> Base.encode64()
end
def smart_contract_verified?(%Address{smart_contract: %SmartContract{}}), do: true
def smart_contract_verified?(%Address{smart_contract: nil}), do: false
end

@ -0,0 +1,40 @@
defmodule ExplorerWeb.ErrorHelpers do
@moduledoc """
Conveniences for translating and building error messages.
"""
use Phoenix.HTML
@doc """
Generates tag for inlined form input errors.
"""
def error_tag(form, field, opts \\ []) do
Enum.map(Keyword.get_values(form.errors, field), fn error ->
content_tag(:span, translate_error(error), Keyword.merge([class: "has-error"], opts))
end)
end
@doc """
Translates an error message using gettext.
"""
def translate_error({msg, opts}) do
# Because error messages were defined within Ecto, we must
# call the Gettext module passing our Gettext backend. We
# also use the "errors" domain as translations are placed
# in the errors.po file.
# Ecto will pass the :count keyword if the error message is
# meant to be pluralized.
# On your own code and templates, depending on whether you
# need the message to be pluralized or not, this could be
# written simply as:
#
# dngettext "errors", "1 file", "%{count} files", count
# dgettext "errors", "is invalid"
#
if count = opts[:count] do
Gettext.dngettext(ExplorerWeb.Gettext, "errors", msg, msg, count, opts)
else
Gettext.dgettext(ExplorerWeb.Gettext, "errors", msg, opts)
end
end
end

@ -95,7 +95,11 @@ defmodule ExplorerWeb.Mixfile do
{:timex, "~> 3.1.24"},
{:timex_ecto, "~> 3.2.1"},
{:wallaby, "~> 0.20", only: [:test], runtime: false},
{:qrcode, "~> 0.1.0"}
{:qrcode, "~> 0.1.0"},
{:bypass, "~> 0.8", only: :test},
# Waiting for the Pretty Print to be implemented at the Jason lib
# https://github.com/michalmuskala/jason/issues/15
{:poison, "~> 3.1"}
]
end

@ -1,5 +1,5 @@
#: lib/explorer_web/templates/address_internal_transaction/index.html.eex:81
#: lib/explorer_web/templates/address_transaction/index.html.eex:83
#: lib/explorer_web/templates/address_internal_transaction/index.html.eex:85
#: lib/explorer_web/templates/address_transaction/index.html.eex:87
#: lib/explorer_web/templates/block/index.html.eex:18
#: lib/explorer_web/templates/block_transaction/index.html.eex:141
#: lib/explorer_web/templates/chain/_blocks.html.eex:8
@ -9,8 +9,8 @@
msgid "Age"
msgstr ""
#: lib/explorer_web/templates/address_internal_transaction/index.html.eex:80
#: lib/explorer_web/templates/address_transaction/index.html.eex:82
#: lib/explorer_web/templates/address_internal_transaction/index.html.eex:84
#: lib/explorer_web/templates/address_transaction/index.html.eex:86
#: lib/explorer_web/templates/block_transaction/index.html.eex:140
#: lib/explorer_web/templates/chain/show.html.eex:7
#: lib/explorer_web/templates/transaction/index.html.eex:35
@ -33,7 +33,7 @@ msgstr ""
msgid "Gas Used"
msgstr ""
#: lib/explorer_web/templates/address_transaction/index.html.eex:81
#: lib/explorer_web/templates/address_transaction/index.html.eex:85
#: lib/explorer_web/templates/block_transaction/index.html.eex:30
#: lib/explorer_web/templates/block_transaction/index.html.eex:139
#: lib/explorer_web/templates/pending_transaction/index.html.eex:35
@ -61,8 +61,8 @@ msgstr ""
msgid "Transactions"
msgstr ""
#: lib/explorer_web/templates/address_internal_transaction/index.html.eex:84
#: lib/explorer_web/templates/address_transaction/index.html.eex:87
#: lib/explorer_web/templates/address_internal_transaction/index.html.eex:88
#: lib/explorer_web/templates/address_transaction/index.html.eex:91
#: lib/explorer_web/templates/block_transaction/index.html.eex:145
#: lib/explorer_web/templates/chain/_transactions.html.eex:10
#: lib/explorer_web/templates/pending_transaction/index.html.eex:39
@ -153,16 +153,15 @@ msgstr ""
msgid "%{count} transactions in this block"
msgstr ""
#: lib/explorer_web/templates/address/overview.html.eex:11
#: lib/explorer_web/templates/transaction_log/index.html.eex:29
#: lib/explorer_web/views/address_view.ex:17
#: lib/explorer_web/views/address_view.ex:18
msgid "Address"
msgstr ""
#: lib/explorer_web/templates/address_internal_transaction/index.html.eex:63
#: lib/explorer_web/templates/address_internal_transaction/index.html.eex:82
#: lib/explorer_web/templates/address_transaction/index.html.eex:61
#: lib/explorer_web/templates/address_transaction/index.html.eex:84
#: lib/explorer_web/templates/address_internal_transaction/index.html.eex:67
#: lib/explorer_web/templates/address_internal_transaction/index.html.eex:86
#: lib/explorer_web/templates/address_transaction/index.html.eex:65
#: lib/explorer_web/templates/address_transaction/index.html.eex:88
#: lib/explorer_web/templates/block_transaction/index.html.eex:142
#: lib/explorer_web/templates/chain/_transactions.html.eex:8
#: lib/explorer_web/templates/pending_transaction/index.html.eex:37
@ -183,10 +182,10 @@ msgstr ""
msgid "Success"
msgstr ""
#: lib/explorer_web/templates/address_internal_transaction/index.html.eex:51
#: lib/explorer_web/templates/address_internal_transaction/index.html.eex:83
#: lib/explorer_web/templates/address_transaction/index.html.eex:49
#: lib/explorer_web/templates/address_transaction/index.html.eex:86
#: lib/explorer_web/templates/address_internal_transaction/index.html.eex:55
#: lib/explorer_web/templates/address_internal_transaction/index.html.eex:87
#: lib/explorer_web/templates/address_transaction/index.html.eex:53
#: lib/explorer_web/templates/address_transaction/index.html.eex:90
#: lib/explorer_web/templates/block_transaction/index.html.eex:144
#: lib/explorer_web/templates/chain/_transactions.html.eex:9
#: lib/explorer_web/templates/pending_transaction/index.html.eex:38
@ -313,7 +312,7 @@ msgstr ""
msgid "Out of Gas"
msgstr ""
#: lib/explorer_web/templates/address_transaction/index.html.eex:79
#: lib/explorer_web/templates/address_transaction/index.html.eex:83
#: lib/explorer_web/templates/block_transaction/index.html.eex:137
#: lib/explorer_web/templates/pending_transaction/index.html.eex:33
#: lib/explorer_web/templates/transaction/index.html.eex:33
@ -329,8 +328,8 @@ msgid "Showing #%{number}"
msgstr ""
#: lib/explorer_web/templates/address/overview.html.eex:24
#: lib/explorer_web/templates/address_internal_transaction/index.html.eex:84
#: lib/explorer_web/templates/address_transaction/index.html.eex:87
#: lib/explorer_web/templates/address_internal_transaction/index.html.eex:88
#: lib/explorer_web/templates/address_transaction/index.html.eex:91
#: lib/explorer_web/templates/chain/_transactions.html.eex:10
#: lib/explorer_web/templates/pending_transaction/index.html.eex:39
#: lib/explorer_web/templates/transaction/index.html.eex:39
@ -358,7 +357,7 @@ msgstr ""
msgid "Search by address, transaction hash, or block number"
msgstr ""
#: lib/explorer_web/templates/address_internal_transaction/index.html.eex:110
#: lib/explorer_web/templates/address_internal_transaction/index.html.eex:114
#: lib/explorer_web/templates/transaction_internal_transaction/index.html.eex:53
msgid "There are no Internal Transactions"
msgstr ""
@ -387,14 +386,14 @@ msgstr ""
msgid "Wei"
msgstr ""
#: lib/explorer_web/templates/address_internal_transaction/index.html.eex:45
#: lib/explorer_web/templates/address_transaction/index.html.eex:43
#: lib/explorer_web/templates/address_internal_transaction/index.html.eex:49
#: lib/explorer_web/templates/address_transaction/index.html.eex:47
#: lib/explorer_web/views/address_internal_transaction_view.ex:10
#: lib/explorer_web/views/address_transaction_view.ex:12
msgid "All"
msgstr ""
#: lib/explorer_web/templates/address_transaction/index.html.eex:88
#: lib/explorer_web/templates/address_transaction/index.html.eex:92
msgid "Fee"
msgstr ""
@ -409,7 +408,7 @@ msgstr ""
msgid "Block Details"
msgstr ""
#: lib/explorer_web/templates/address_internal_transaction/index.html.eex:79
#: lib/explorer_web/templates/address_internal_transaction/index.html.eex:83
msgid "Parent Tx Hash"
msgstr ""
@ -490,17 +489,16 @@ msgstr ""
msgid "TX Fee"
msgstr ""
#: lib/explorer_web/templates/address_contract/index.html.eex:25
#: lib/explorer_web/templates/address_internal_transaction/index.html.eex:27
#: lib/explorer_web/templates/address_transaction/index.html.eex:27
msgid "Contract"
msgstr ""
#: lib/explorer_web/views/address_view.ex:15
#: lib/explorer_web/views/address_view.ex:16
msgid "Contract Address"
msgstr ""
#: lib/explorer_web/templates/address_contract/index.html.eex:34
#: lib/explorer_web/templates/address_contract/index.html.eex:39
msgid "Contract bytecode"
msgstr ""
@ -511,3 +509,25 @@ msgstr ""
#: lib/explorer_web/templates/transaction/index.html.eex:78
msgid "Older"
msgstr ""
#: lib/explorer_web/templates/address_contract_verification/new.html.eex:9
msgid "Contract Source Code"
msgstr ""
#: lib/explorer_web/templates/address_contract/index.html.eex:44
msgid "Verify and Publish"
msgstr ""
#: lib/explorer_web/templates/address_contract/index.html.eex:27
#: lib/explorer_web/templates/address_internal_transaction/index.html.eex:29
#: lib/explorer_web/templates/address_transaction/index.html.eex:29
msgid "Code"
msgstr ""
#: lib/explorer_web/views/address_contract_view.ex:9
msgid "false"
msgstr ""
#: lib/explorer_web/views/address_contract_view.ex:8
msgid "true"
msgstr ""

@ -10,8 +10,8 @@ msgid ""
msgstr ""
"Language: en\n"
#: lib/explorer_web/templates/address_internal_transaction/index.html.eex:81
#: lib/explorer_web/templates/address_transaction/index.html.eex:83
#: lib/explorer_web/templates/address_internal_transaction/index.html.eex:85
#: lib/explorer_web/templates/address_transaction/index.html.eex:87
#: lib/explorer_web/templates/block/index.html.eex:18
#: lib/explorer_web/templates/block_transaction/index.html.eex:141
#: lib/explorer_web/templates/chain/_blocks.html.eex:8
@ -21,8 +21,8 @@ msgstr ""
msgid "Age"
msgstr "Age"
#: lib/explorer_web/templates/address_internal_transaction/index.html.eex:80
#: lib/explorer_web/templates/address_transaction/index.html.eex:82
#: lib/explorer_web/templates/address_internal_transaction/index.html.eex:84
#: lib/explorer_web/templates/address_transaction/index.html.eex:86
#: lib/explorer_web/templates/block_transaction/index.html.eex:140
#: lib/explorer_web/templates/chain/show.html.eex:7
#: lib/explorer_web/templates/transaction/index.html.eex:35
@ -45,7 +45,7 @@ msgstr "%{year} POA Network Ltd. All rights reserved"
msgid "Gas Used"
msgstr "Gas Used"
#: lib/explorer_web/templates/address_transaction/index.html.eex:81
#: lib/explorer_web/templates/address_transaction/index.html.eex:85
#: lib/explorer_web/templates/block_transaction/index.html.eex:30
#: lib/explorer_web/templates/block_transaction/index.html.eex:139
#: lib/explorer_web/templates/pending_transaction/index.html.eex:35
@ -73,8 +73,8 @@ msgstr "POA Network Explorer"
msgid "Transactions"
msgstr "Transactions"
#: lib/explorer_web/templates/address_internal_transaction/index.html.eex:84
#: lib/explorer_web/templates/address_transaction/index.html.eex:87
#: lib/explorer_web/templates/address_internal_transaction/index.html.eex:88
#: lib/explorer_web/templates/address_transaction/index.html.eex:91
#: lib/explorer_web/templates/block_transaction/index.html.eex:145
#: lib/explorer_web/templates/chain/_transactions.html.eex:10
#: lib/explorer_web/templates/pending_transaction/index.html.eex:39
@ -165,16 +165,15 @@ msgstr "%{confirmations} block confirmations"
msgid "%{count} transactions in this block"
msgstr "%{count} transactions in this block"
#: lib/explorer_web/templates/address/overview.html.eex:11
#: lib/explorer_web/templates/transaction_log/index.html.eex:29
#: lib/explorer_web/views/address_view.ex:17
#: lib/explorer_web/views/address_view.ex:18
msgid "Address"
msgstr "Address"
#: lib/explorer_web/templates/address_internal_transaction/index.html.eex:63
#: lib/explorer_web/templates/address_internal_transaction/index.html.eex:82
#: lib/explorer_web/templates/address_transaction/index.html.eex:61
#: lib/explorer_web/templates/address_transaction/index.html.eex:84
#: lib/explorer_web/templates/address_internal_transaction/index.html.eex:67
#: lib/explorer_web/templates/address_internal_transaction/index.html.eex:86
#: lib/explorer_web/templates/address_transaction/index.html.eex:65
#: lib/explorer_web/templates/address_transaction/index.html.eex:88
#: lib/explorer_web/templates/block_transaction/index.html.eex:142
#: lib/explorer_web/templates/chain/_transactions.html.eex:8
#: lib/explorer_web/templates/pending_transaction/index.html.eex:37
@ -195,10 +194,10 @@ msgstr "Overview"
msgid "Success"
msgstr "Success"
#: lib/explorer_web/templates/address_internal_transaction/index.html.eex:51
#: lib/explorer_web/templates/address_internal_transaction/index.html.eex:83
#: lib/explorer_web/templates/address_transaction/index.html.eex:49
#: lib/explorer_web/templates/address_transaction/index.html.eex:86
#: lib/explorer_web/templates/address_internal_transaction/index.html.eex:55
#: lib/explorer_web/templates/address_internal_transaction/index.html.eex:87
#: lib/explorer_web/templates/address_transaction/index.html.eex:53
#: lib/explorer_web/templates/address_transaction/index.html.eex:90
#: lib/explorer_web/templates/block_transaction/index.html.eex:144
#: lib/explorer_web/templates/chain/_transactions.html.eex:9
#: lib/explorer_web/templates/pending_transaction/index.html.eex:38
@ -325,7 +324,7 @@ msgstr ""
msgid "Out of Gas"
msgstr ""
#: lib/explorer_web/templates/address_transaction/index.html.eex:79
#: lib/explorer_web/templates/address_transaction/index.html.eex:83
#: lib/explorer_web/templates/block_transaction/index.html.eex:137
#: lib/explorer_web/templates/pending_transaction/index.html.eex:33
#: lib/explorer_web/templates/transaction/index.html.eex:33
@ -341,8 +340,8 @@ msgid "Showing #%{number}"
msgstr ""
#: lib/explorer_web/templates/address/overview.html.eex:24
#: lib/explorer_web/templates/address_internal_transaction/index.html.eex:84
#: lib/explorer_web/templates/address_transaction/index.html.eex:87
#: lib/explorer_web/templates/address_internal_transaction/index.html.eex:88
#: lib/explorer_web/templates/address_transaction/index.html.eex:91
#: lib/explorer_web/templates/chain/_transactions.html.eex:10
#: lib/explorer_web/templates/pending_transaction/index.html.eex:39
#: lib/explorer_web/templates/transaction/index.html.eex:39
@ -370,7 +369,7 @@ msgstr ""
msgid "Search by address, transaction hash, or block number"
msgstr ""
#: lib/explorer_web/templates/address_internal_transaction/index.html.eex:110
#: lib/explorer_web/templates/address_internal_transaction/index.html.eex:114
#: lib/explorer_web/templates/transaction_internal_transaction/index.html.eex:53
msgid "There are no Internal Transactions"
msgstr ""
@ -399,14 +398,14 @@ msgstr ""
msgid "Wei"
msgstr ""
#: lib/explorer_web/templates/address_internal_transaction/index.html.eex:45
#: lib/explorer_web/templates/address_transaction/index.html.eex:43
#: lib/explorer_web/templates/address_internal_transaction/index.html.eex:49
#: lib/explorer_web/templates/address_transaction/index.html.eex:47
#: lib/explorer_web/views/address_internal_transaction_view.ex:10
#: lib/explorer_web/views/address_transaction_view.ex:12
msgid "All"
msgstr ""
#: lib/explorer_web/templates/address_transaction/index.html.eex:88
#: lib/explorer_web/templates/address_transaction/index.html.eex:92
msgid "Fee"
msgstr ""
@ -421,7 +420,7 @@ msgstr ""
msgid "Block Details"
msgstr ""
#: lib/explorer_web/templates/address_internal_transaction/index.html.eex:79
#: lib/explorer_web/templates/address_internal_transaction/index.html.eex:83
msgid "Parent Tx Hash"
msgstr ""
@ -502,17 +501,16 @@ msgstr ""
msgid "TX Fee"
msgstr ""
#: lib/explorer_web/templates/address_contract/index.html.eex:25
#: lib/explorer_web/templates/address_internal_transaction/index.html.eex:27
#: lib/explorer_web/templates/address_transaction/index.html.eex:27
msgid "Contract"
msgstr ""
#: lib/explorer_web/views/address_view.ex:15
#: lib/explorer_web/views/address_view.ex:16
msgid "Contract Address"
msgstr ""
#: lib/explorer_web/templates/address_contract/index.html.eex:34
#: lib/explorer_web/templates/address_contract/index.html.eex:39
msgid "Contract bytecode"
msgstr ""
@ -523,3 +521,25 @@ msgstr ""
#: lib/explorer_web/templates/transaction/index.html.eex:78
msgid "Older"
msgstr ""
#: lib/explorer_web/templates/address_contract_verification/new.html.eex:9
msgid "Contract Source Code"
msgstr "Contract Source Code"
#: lib/explorer_web/templates/address_contract/index.html.eex:44
msgid "Verify and Publish"
msgstr ""
#: lib/explorer_web/templates/address_contract/index.html.eex:27
#: lib/explorer_web/templates/address_internal_transaction/index.html.eex:29
#: lib/explorer_web/templates/address_transaction/index.html.eex:29
msgid "Code"
msgstr ""
#: lib/explorer_web/views/address_contract_view.ex:9
msgid "false"
msgstr "No"
#: lib/explorer_web/views/address_contract_view.ex:8
msgid "true"
msgstr "Yes"

@ -33,7 +33,7 @@ defmodule ExplorerWeb.AddressContractControllerTest do
end
test "suscefully renders the page", %{conn: conn} do
address = insert(:address, contract_code: Factory.data("contract_code"))
address = insert(:address, contract_code: Factory.data("contract_code"), smart_contract: nil)
conn = get(conn, address_contract_path(ExplorerWeb.Endpoint, :index, :en, address))

@ -0,0 +1,74 @@
defmodule ExplorerWeb.AddressContractVerificationTest do
use ExplorerWeb.FeatureCase, async: true
import Wallaby.Query
alias Plug.Conn
setup do
bypass = Bypass.open()
Application.put_env(:explorer, :solc_bin_api_url, "http://localhost:#{bypass.port}")
{:ok, bypass: bypass}
end
test "users validates smart contract", %{session: session, bypass: bypass} do
Bypass.expect(bypass, fn conn -> Conn.resp(conn, 200, solc_bin_versions()) end)
address_hash = "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c"
smart_contract_bytecode =
"0x608060405234801561001057600080fd5b5060df8061001f6000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a7230582040d82a7379b1ee1632ad4d8a239954fd940277b25628ead95259a85c5eddb2120029"
created_contract_address = insert(:address, hash: address_hash, contract_code: smart_contract_bytecode)
insert(
:internal_transaction,
index: 0,
created_contract_address_hash: created_contract_address.hash,
created_contract_code: smart_contract_bytecode
)
code = """
pragma solidity ^0.4.24;
contract SimpleStorage {
uint storedData;
function set(uint x) public {
storedData = x;
}
function get() public constant returns (uint) {
return storedData;
}
}
"""
session
|> visit("/en/addresses/#{address_hash}/contract_verifications/new")
|> fill_in(text_field("Contract Name"), with: "SimpleStorage")
|> click(option("0.4.24"))
|> click(radio_button("No"))
|> fill_in(text_field("Enter the Solidity Contract Code below"), with: code)
|> click(button("Verify and publish"))
assert current_path(session) =~ ~r/\/en\/addresses\/#{address_hash}\/contracts/
end
test "with invalid data shows error messages", %{session: session, bypass: bypass} do
Bypass.expect(bypass, fn conn -> Conn.resp(conn, 200, solc_bin_versions()) end)
session
|> visit("/en/addresses/0x1e0eaa06d02f965be2dfe0bc9ff52b2d82133461/contract_verifications/new")
|> fill_in(text_field("Contract Name"), with: "")
|> fill_in(text_field("Enter the Solidity Contract Code below"), with: "")
|> click(button("Verify and publish"))
|> assert_has(css(".has-error", text: "there was an error validating your contract, please try again."))
end
def solc_bin_versions() do
File.read!("./test/support/fixture/smart_contract/solc_bin.json")
end
end

@ -0,0 +1,30 @@
defmodule ExplorerWeb.ErrorHelpersTest do
use ExplorerWeb.ConnCase, async: true
import Phoenix.HTML.Tag, only: [content_tag: 3]
alias ExplorerWeb.ErrorHelpers
@changeset %{
errors: [
contract_code: {"has already been taken", []}
]
}
test "error_tag/2 renders spans with default options" do
assert ErrorHelpers.error_tag(@changeset, :contract_code) == [
content_tag(:span, "has already been taken", class: "has-error")
]
end
test "error_tag/3 overrides default options" do
assert ErrorHelpers.error_tag(@changeset, :contract_code, class: "something-else") == [
content_tag(:span, "has already been taken", class: "something-else")
]
end
test "error_tag/3 merges given options with default ones" do
assert ErrorHelpers.error_tag(@changeset, :contract_code, data_hidden: true) == [
content_tag(:span, "has already been taken", class: "has-error", data_hidden: true)
]
end
end

@ -73,5 +73,5 @@
"tzdata": {:hex, :tzdata, "0.5.16", "13424d3afc76c68ff607f2df966c0ab4f3258859bbe3c979c9ed1606135e7352", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.3.1", "a1f612a7b512638634a603c8f401892afbf99b8ce93a45041f8aaca99cadb85e", [:rebar3], [], "hexpm"},
"uuid": {:hex, :uuid, "1.1.8", "e22fc04499de0de3ed1116b770c7737779f226ceefa0badb3592e64d5cfb4eb9", [:mix], [], "hexpm"},
"wallaby": {:hex, :wallaby, "0.20.0", "cc6663555ff7b05afbebb2a8b461d18a5b321658b9017f7bc77d494b7063266a", [:mix], [{:httpoison, "~> 0.12", [hex: :httpoison, repo: "hexpm", optional: false]}, {:poison, ">= 1.4.0", [hex: :poison, repo: "hexpm", optional: false]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}], "hexpm"},
"wallaby": {:hex, :wallaby, "0.20.0", "cc6663555ff7b05afbebb2a8b461d18a5b321658b9017f7bc77d494b7063266a", [:mix], [{:httpoison, "~> 0.12", [hex: :httpoison, optional: false]}, {:poison, ">= 1.4.0", [hex: :poison, optional: false]}, {:poolboy, "~> 1.5", [hex: :poolboy, optional: false]}]},
}

Loading…
Cancel
Save