diff --git a/CHANGELOG.md b/CHANGELOG.md index b349ed60de73..d60943c096f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ - [#9572](https://github.com/blockscout/blockscout/pull/9572) - Fix Shibarium L1 fetcher - [#9563](https://github.com/blockscout/blockscout/pull/9563) - Fix timestamp handler for unfinalized zkEVM batches - [#9560](https://github.com/blockscout/blockscout/pull/9560) - Fix fetch pending transaction for hyperledger besu client +- [#9555](https://github.com/blockscout/blockscout/pull/9555) - Fix EIP-1967 beacon proxy pattern detection - [#9514](https://github.com/blockscout/blockscout/pull/9514) - Fix missing `0x` prefix for `blockNumber`, `logIndex`, `transactionIndex` and remove `transactionLogIndex` in `eth_getLogs` response. - [#9512](https://github.com/blockscout/blockscout/pull/9512) - Docker-compose 2.24.6 compatibility - [#9262](https://github.com/blockscout/blockscout/pull/9262) - Fix withdrawal status diff --git a/apps/explorer/lib/explorer/chain/smart_contract.ex b/apps/explorer/lib/explorer/chain/smart_contract.ex index 7d5ba48d0c96..cb9ac24ea5a6 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract.ex @@ -41,9 +41,7 @@ defmodule Explorer.Chain.SmartContract do @burn_address_hash_string "0x0000000000000000000000000000000000000000" @burn_address_hash_string_32 "0x0000000000000000000000000000000000000000000000000000000000000000" - defguard is_burn_signature(term) when term in ["0x", "0x0", @burn_address_hash_string_32] - defguard is_burn_signature_or_nil(term) when is_burn_signature(term) or term == nil - defguard is_burn_signature_extended(term) when is_burn_signature(term) or term == @burn_address_hash_string + defguard is_burn_signature(term) when term in ["0x", "0x0", @burn_address_hash_string, @burn_address_hash_string_32] @doc """ Returns burn address hash @@ -594,7 +592,7 @@ defmodule Explorer.Chain.SmartContract do def save_implementation_data(nil, _, _, _), do: {nil, nil} def save_implementation_data(empty_address_hash_string, proxy_address_hash, metadata_from_verified_twin, options) - when is_burn_signature_extended(empty_address_hash_string) do + when is_burn_signature(empty_address_hash_string) do if is_nil(metadata_from_verified_twin) or !metadata_from_verified_twin do proxy_address_hash |> address_hash_to_smart_contract_without_twin(options) diff --git a/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex b/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex index 845f8f6e08e8..672b17ddd65b 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex @@ -13,7 +13,7 @@ defmodule Explorer.Chain.SmartContract.Proxy do string_to_address_hash: 1 ] - import Explorer.Chain.SmartContract, only: [burn_address_hash_string: 0, is_burn_signature_or_nil: 1] + import Explorer.Chain.SmartContract, only: [burn_address_hash_string: 0, is_burn_signature: 1] # supported signatures: # 5c60da1b = keccak256(implementation()) @@ -146,11 +146,11 @@ defmodule Explorer.Chain.SmartContract.Proxy do json_rpc_named_arguments ) do {:ok, empty_address_hash_string} - when is_burn_signature_or_nil(empty_address_hash_string) -> + when is_burn_signature(empty_address_hash_string) -> nil - {:ok, implementation_logic_address_hash_string} -> - implementation_logic_address_hash_string + {:ok, "0x" <> storage_value} -> + extract_address_hex_from_storage_pointer(storage_value) _ -> nil @@ -289,4 +289,14 @@ defmodule Explorer.Chain.SmartContract.Proxy do Map.get(input, "name") == name end) end + + @doc """ + Decodes 20 bytes address hex from smart-contract storage pointer value + """ + @spec extract_address_hex_from_storage_pointer(binary) :: binary + def extract_address_hex_from_storage_pointer(storage_value) when is_binary(storage_value) do + address_hex = storage_value |> String.slice(-40, 40) |> String.pad_leading(40, ["0"]) + + "0x" <> address_hex + end end diff --git a/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1967.ex b/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1967.ex index 5cc06c75ed9e..24549c96a132 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1967.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1967.ex @@ -7,7 +7,7 @@ defmodule Explorer.Chain.SmartContract.Proxy.EIP1967 do alias Explorer.Chain.SmartContract.Proxy alias Explorer.Chain.SmartContract.Proxy.Basic - import Explorer.Chain.SmartContract, only: [is_burn_signature_or_nil: 1] + import Explorer.Chain.SmartContract, only: [is_burn_signature: 1] # supported signatures: # 5c60da1b = keccak256(implementation()) @@ -65,31 +65,38 @@ defmodule Explorer.Chain.SmartContract.Proxy.EIP1967 do } ] - case Contract.eth_get_storage_at_request( - proxy_address_hash, - storage_slot_beacon_contract_address, - nil, - json_rpc_named_arguments - ) do - {:ok, empty_address} - when is_burn_signature_or_nil(empty_address) -> - nil + beacon_contract_address = + case Contract.eth_get_storage_at_request( + proxy_address_hash, + storage_slot_beacon_contract_address, + nil, + json_rpc_named_arguments + ) do + {:ok, empty_address} + when is_burn_signature(empty_address) -> + nil - {:ok, beacon_contract_address} -> - case @implementation_signature - |> Basic.get_implementation_address_hash_string( - beacon_contract_address, - implementation_method_abi - ) do - <> -> - implementation_address + {:ok, "0x" <> storage_value} -> + Proxy.extract_address_hex_from_storage_pointer(storage_value) - _ -> - nil - end + _ -> + nil + end + + if beacon_contract_address do + case @implementation_signature + |> Basic.get_implementation_address_hash_string( + beacon_contract_address, + implementation_method_abi + ) do + <> -> + implementation_address - _ -> - nil + _ -> + nil + end + else + nil end end end diff --git a/apps/explorer/lib/explorer/chain/smart_contract/proxy/master_copy.ex b/apps/explorer/lib/explorer/chain/smart_contract/proxy/master_copy.ex index 97b927981392..ce5a7aed49aa 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract/proxy/master_copy.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract/proxy/master_copy.ex @@ -29,7 +29,8 @@ defmodule Explorer.Chain.SmartContract.Proxy.MasterCopy do when is_burn_signature(empty_address) -> {:ok, "0x"} - {:ok, logic_contract_address} -> + {:ok, "0x" <> storage_value} -> + logic_contract_address = Proxy.extract_address_hex_from_storage_pointer(storage_value) {:ok, logic_contract_address} _ -> diff --git a/apps/explorer/test/explorer/chain/smart_contract/proxy_test.exs b/apps/explorer/test/explorer/chain/smart_contract/proxy_test.exs index f5fd83b562a2..c61b10c795a4 100644 --- a/apps/explorer/test/explorer/chain/smart_contract/proxy_test.exs +++ b/apps/explorer/test/explorer/chain/smart_contract/proxy_test.exs @@ -404,6 +404,108 @@ defmodule Explorer.Chain.SmartContract.ProxyTest do implementation_contract_address_hash_string = Base.encode16(implementation_contract_address.hash.bytes, case: :lower) + eip_1967_beacon_proxy_mock_requests( + beacon_contract_address_hash_string, + implementation_contract_address_hash_string, + :full_32 + ) + + implementation_abi = Proxy.get_implementation_abi_from_proxy(smart_contract, []) + verify!(EthereumJSONRPC.Mox) + + assert implementation_abi == @implementation_abi + end + + test "get_implementation_abi_from_proxy/2 returns implementation abi in case of EIP-1967 proxy pattern (beacon contract) when eth_getStorageAt returns 20 bytes address" do + proxy_contract_address = insert(:contract_address) + + smart_contract = + insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: [], contract_code_md5: "123") + + beacon_contract_address = insert(:contract_address) + + insert(:smart_contract, + address_hash: beacon_contract_address.hash, + abi: @beacon_abi, + contract_code_md5: "123" + ) + + beacon_contract_address_hash_string = Base.encode16(beacon_contract_address.hash.bytes, case: :lower) + + implementation_contract_address = insert(:contract_address) + + insert(:smart_contract, + address_hash: implementation_contract_address.hash, + abi: @implementation_abi, + contract_code_md5: "123" + ) + + implementation_contract_address_hash_string = + Base.encode16(implementation_contract_address.hash.bytes, case: :lower) + + eip_1967_beacon_proxy_mock_requests( + beacon_contract_address_hash_string, + implementation_contract_address_hash_string, + :exact_20 + ) + + implementation_abi = Proxy.get_implementation_abi_from_proxy(smart_contract, []) + verify!(EthereumJSONRPC.Mox) + + assert implementation_abi == @implementation_abi + end + + test "get_implementation_abi_from_proxy/2 returns implementation abi in case of EIP-1967 proxy pattern (beacon contract) when eth_getStorageAt returns less 20 bytes address" do + proxy_contract_address = insert(:contract_address) + + smart_contract = + insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: [], contract_code_md5: "123") + + beacon_contract_address = insert(:contract_address) + + insert(:smart_contract, + address_hash: beacon_contract_address.hash, + abi: @beacon_abi, + contract_code_md5: "123" + ) + + beacon_contract_address_hash_string = Base.encode16(beacon_contract_address.hash.bytes, case: :lower) + + implementation_contract_address = insert(:contract_address) + + insert(:smart_contract, + address_hash: implementation_contract_address.hash, + abi: @implementation_abi, + contract_code_md5: "123" + ) + + implementation_contract_address_hash_string = + Base.encode16(implementation_contract_address.hash.bytes, case: :lower) + + eip_1967_beacon_proxy_mock_requests( + beacon_contract_address_hash_string, + implementation_contract_address_hash_string, + :short + ) + + implementation_abi = Proxy.get_implementation_abi_from_proxy(smart_contract, []) + verify!(EthereumJSONRPC.Mox) + + assert implementation_abi == @implementation_abi + end + + defp eip_1967_beacon_proxy_mock_requests( + beacon_contract_address_hash_string, + implementation_contract_address_hash_string, + mode + ) do + response = + case mode do + :full_32 -> "0x000000000000000000000000" <> beacon_contract_address_hash_string + :exact_20 -> "0x" <> beacon_contract_address_hash_string + :short -> "0x" <> String.slice(beacon_contract_address_hash_string, 10..-1) + end + EthereumJSONRPC.Mox |> expect( :json_rpc, @@ -432,7 +534,7 @@ defmodule Explorer.Chain.SmartContract.ProxyTest do ] }, _options -> - {:ok, "0x000000000000000000000000" <> beacon_contract_address_hash_string} + {:ok, response} end ) |> expect( @@ -442,7 +544,7 @@ defmodule Explorer.Chain.SmartContract.ProxyTest do id: _id, method: "eth_call", params: [ - %{data: "0x5c60da1b", to: "0x000000000000000000000000" <> ^beacon_contract_address_hash_string}, + %{data: "0x5c60da1b", to: "0x" <> ^beacon_contract_address_hash_string}, "latest" ] } @@ -460,10 +562,5 @@ defmodule Explorer.Chain.SmartContract.ProxyTest do } end ) - - implementation_abi = Proxy.get_implementation_abi_from_proxy(smart_contract, []) - verify!(EthereumJSONRPC.Mox) - - assert implementation_abi == @implementation_abi end end