feat: Clone with immutable arguments proxy pattern (#10039)

* feat: Clone with immutable arguments proxy pattern

* refactor: rename unverified_proxy_only? to proxy_without_abi?
mf-only-health-webapp
Victor Baranov 6 months ago committed by GitHub
parent 621024c046
commit def8a1aed0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      apps/block_scout_web/lib/block_scout_web/views/api/v2/smart_contract_view.ex
  2. 112
      apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs
  3. 2
      apps/explorer/lib/explorer/chain.ex
  4. 4
      apps/explorer/lib/explorer/chain/smart_contract.ex
  5. 38
      apps/explorer/lib/explorer/chain/smart_contract/proxy.ex
  6. 55
      apps/explorer/lib/explorer/chain/smart_contract/proxy/clone_with_immutable_arguments.ex
  7. 1
      apps/explorer/lib/explorer/chain/smart_contract/proxy/models/implementation.ex
  8. 2
      apps/explorer/lib/explorer/etherscan/contracts.ex
  9. 7
      apps/explorer/priv/repo/migrations/20240501131140_new_proxy_type_clones_with_immutable_arguments.exs

@ -233,7 +233,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractView do
end
@doc """
Returns additional sources of the smart-contract or from bytecode twin or from implementation, if it fits minimal proxy pattern (EIP-1167)
Returns additional sources of the smart-contract or from bytecode twin or from implementation, if it fits minimal proxy pattern (EIP-1167, Clone with immutable arguments)
"""
@spec get_additional_sources(SmartContract.t(), boolean, SmartContract.t() | nil) ::
[SmartContractAdditionalSource.t()] | nil

@ -496,6 +496,118 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
end
end
test "get smart-contract implementation for 'Clones with immutable arguments' pattern", %{conn: conn} do
implementation_contract =
insert(:smart_contract,
external_libraries: [],
constructor_arguments: "",
abi: [
%{
"type" => "constructor",
"inputs" => [
%{"type" => "address", "name" => "_proxyStorage"},
%{"type" => "address", "name" => "_implementationAddress"}
]
},
%{
"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"
}
],
license_type: 9
)
insert(:smart_contract_additional_source,
file_name: "test1",
contract_source_code: "test2",
address_hash: implementation_contract.address_hash
)
implementation_contract_address_hash_string =
Base.encode16(implementation_contract.address_hash.bytes, case: :lower)
proxy_tx_input =
"0x684fbe55000000000000000000000000af1caf51d49b0e63d1ff7e5d4ed6ea26d15f3f9d000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003"
proxy_deployed_bytecode =
"0x3d3d3d3d363d3d3761003f603736393661003f013d73" <>
implementation_contract_address_hash_string <>
"5af43d3d93803e603557fd5bf3af1caf51d49b0e63d1ff7e5d4ed6ea26d15f3f9d0000000000000000000000000000000000000000000000000000000000000001000000000000000203003d"
proxy_address =
insert(:contract_address,
contract_code: proxy_deployed_bytecode
)
insert(:transaction,
created_contract_address_hash: proxy_address.hash,
input: proxy_tx_input
)
|> with_block(status: :ok)
correct_response = %{
"verified_twin_address_hash" => Address.checksum(implementation_contract.address_hash),
"is_verified" => false,
"is_changed_bytecode" => false,
"is_partially_verified" => implementation_contract.partially_verified,
"is_fully_verified" => false,
"is_verified_via_sourcify" => false,
"is_vyper_contract" => implementation_contract.is_vyper_contract,
"has_methods_read" => true,
"has_methods_write" => true,
"has_methods_read_proxy" => true,
"has_methods_write_proxy" => true,
"has_custom_methods_read" => false,
"has_custom_methods_write" => false,
"minimal_proxy_address_hash" => Address.checksum(implementation_contract.address_hash),
"sourcify_repo_url" => nil,
"can_be_visualized_via_sol2uml" => false,
"name" => implementation_contract && implementation_contract.name,
"compiler_version" => implementation_contract.compiler_version,
"optimization_enabled" => implementation_contract.optimization,
"optimization_runs" => implementation_contract.optimization_runs,
"evm_version" => implementation_contract.evm_version,
"verified_at" => implementation_contract.inserted_at |> to_string() |> String.replace(" ", "T"),
"source_code" => implementation_contract.contract_source_code,
"file_path" => implementation_contract.file_path,
"additional_sources" => [
%{"file_path" => "test1", "source_code" => "test2"}
],
"compiler_settings" => implementation_contract.compiler_settings,
"external_libraries" => [],
"constructor_args" => nil,
"decoded_constructor_args" => nil,
"is_self_destructed" => false,
"deployed_bytecode" => proxy_deployed_bytecode,
"creation_bytecode" => proxy_tx_input,
"abi" => implementation_contract.abi,
"is_verified_via_eth_bytecode_db" => implementation_contract.verified_via_eth_bytecode_db,
"is_verified_via_verifier_alliance" => implementation_contract.verified_via_verifier_alliance,
"language" => smart_contract_language(implementation_contract),
"license_type" => "bsd_3_clause",
"certified" => false
}
request = get(conn, "/api/v2/smart-contracts/#{Address.checksum(proxy_address.hash)}")
response = json_response(request, 200)
assert correct_response == response
end
describe "/smart-contracts/{address_hash} <> eth_bytecode_db" do
setup do
old_interval_env = Application.get_env(:explorer, Explorer.Chain.Fetcher.LookUpSmartContractSourcesOnDemand)

@ -1186,7 +1186,7 @@ defmodule Explorer.Chain do
implementation_address_fetched?: false,
refetch_necessity_checked?: false
},
Keyword.put(options, :unverified_proxy_only?, true)
Keyword.put(options, :proxy_without_abi?, true)
)
add_implementation_and_bytecode_twin_to_result(address_result, implementation_address_hashes, hash, options)

@ -584,7 +584,7 @@ defmodule Explorer.Chain.SmartContract do
{implementation_address_hash, _} =
Implementation.get_implementation(
smart_contract,
Keyword.put(options, :unverified_proxy_only?, true)
Keyword.put(options, :proxy_without_abi?, true)
)
implementation_smart_contract =
@ -959,7 +959,7 @@ defmodule Explorer.Chain.SmartContract do
implementation_address_fetched?: false,
refetch_necessity_checked?: false
},
Keyword.put(options, :unverified_proxy_only?, true)
Keyword.put(options, :proxy_without_abi?, true)
)
{implementation_smart_contract, true}

@ -6,7 +6,17 @@ defmodule Explorer.Chain.SmartContract.Proxy do
alias EthereumJSONRPC.Contract
alias Explorer.Chain.{Hash, SmartContract}
alias Explorer.Chain.SmartContract.Proxy
alias Explorer.Chain.SmartContract.Proxy.{Basic, EIP1167, EIP1822, EIP1967, EIP2535, EIP930, MasterCopy}
alias Explorer.Chain.SmartContract.Proxy.{
Basic,
CloneWithImmutableArguments,
EIP1167,
EIP1822,
EIP1967,
EIP2535,
EIP930,
MasterCopy
}
import Explorer.Chain,
only: [
@ -35,7 +45,7 @@ defmodule Explorer.Chain.SmartContract.Proxy do
# aaf10f42 = keccak256(getAddress(bytes32))
@get_address_signature "21f8a721"
@typep options :: [{:api?, true | false}, {:unverified_proxy_only?, true | false}]
@typep options :: [{:api?, true | false}, {:proxy_without_abi?, true | false}]
@doc """
Fetches into DB proxy contract implementation's address and name from different proxy patterns
@ -45,7 +55,7 @@ defmodule Explorer.Chain.SmartContract.Proxy do
def fetch_implementation_address_hash(proxy_address_hash, proxy_abi, options)
when not is_nil(proxy_address_hash) do
%{implementation_address_hash_strings: implementation_address_hash_strings, proxy_type: proxy_type} =
if options[:unverified_proxy_only?] do
if options[:proxy_without_abi?] do
get_implementation_address_hash_string_for_non_verified_proxy(proxy_address_hash)
else
get_implementation_address_hash_string(proxy_address_hash, proxy_abi)
@ -222,6 +232,28 @@ defmodule Explorer.Chain.SmartContract.Proxy do
proxy_abi,
go_to_fallback?
],
:get_implementation_address_hash_string_clones_with_immutable_arguments
)
end
@doc """
Returns implementation address by following "Clone with immutable arguments" pattern or tries next proxy pattern
"""
@spec get_implementation_address_hash_string_clones_with_immutable_arguments(Hash.Address.t(), any(), bool()) ::
%{implementation_address_hash_strings: [String.t()] | :error | nil, proxy_type: atom() | :unknown}
def get_implementation_address_hash_string_clones_with_immutable_arguments(
proxy_address_hash,
proxy_abi,
go_to_fallback? \\ true
) do
get_implementation_address_hash_string_by_module(
CloneWithImmutableArguments,
:clone_with_immutable_arguments,
[
proxy_address_hash,
proxy_abi,
go_to_fallback?
],
:get_implementation_address_hash_string_eip1967
)
end

@ -0,0 +1,55 @@
defmodule Explorer.Chain.SmartContract.Proxy.CloneWithImmutableArguments do
@moduledoc """
Module for fetching proxy implementation from https://github.com/wighawag/clones-with-immutable-args
"""
alias Explorer.Chain
alias Explorer.Chain.{Address, Hash, SmartContract}
alias Explorer.Chain.SmartContract.Proxy
@doc """
Get implementation address following "Clone with immutable arguments" pattern
"""
@spec get_implementation_smart_contract(Hash.Address.t(), Keyword.t()) :: SmartContract.t() | nil
def get_implementation_smart_contract(address_hash, options \\ []) do
address_hash
|> get_implementation_address_hash_string(options)
|> Proxy.implementation_to_smart_contract(options)
end
@doc """
Get implementation address hash string following "Clone with immutable arguments" pattern
"""
@spec get_implementation_address_hash_string(Hash.Address.t(), Keyword.t()) :: String.t() | nil
def get_implementation_address_hash_string(address_hash, options \\ []) do
case Chain.select_repo(options).get(Address, address_hash) do
nil ->
nil
target_address ->
contract_code = target_address.contract_code
case contract_code do
%Chain.Data{bytes: contract_code_bytes} ->
contract_bytecode = Base.encode16(contract_code_bytes, case: :lower)
contract_bytecode |> get_proxy_clone_with_immutable_arguments() |> Proxy.abi_decode_address_output()
_ ->
nil
end
end
end
defp get_proxy_clone_with_immutable_arguments(contract_bytecode) do
case contract_bytecode do
"3d3d3d3d363d3d3761" <>
<<_::binary-size(4)>> <>
"603736393661" <> <<_::binary-size(4)>> <> "013d73" <> <<template_address::binary-size(40)>> <> _ ->
"0x" <> template_address
_ ->
nil
end
end
end

@ -42,6 +42,7 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation do
:basic_get_implementation,
:comptroller,
:eip2535,
:clone_with_immutable_arguments,
:unknown
],
null: true

@ -53,7 +53,7 @@ defmodule Explorer.Etherscan.Contracts do
refetch_necessity_checked?: false
},
[
{:unverified_proxy_only?, true}
{:proxy_without_abi?, true}
]
)

@ -0,0 +1,7 @@
defmodule Explorer.Repo.Migrations.NewProxyTypeClonesWithImmutableArguments do
use Ecto.Migration
def change do
execute("ALTER TYPE proxy_type ADD VALUE 'clone_with_immutable_arguments' BEFORE 'unknown'")
end
end
Loading…
Cancel
Save