Skip to content

Commit

Permalink
Merge pull request blockscout#9555 from blockscout/vb-fix-1967-beacon…
Browse files Browse the repository at this point in the history
…-pattern-detection

Fix EIP-1967 beacon proxy pattern detection
  • Loading branch information
vbaranov authored Mar 10, 2024
2 parents 7a7a424 + 843a9e3 commit f8092ac
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 39 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 2 additions & 4 deletions apps/explorer/lib/explorer/chain/smart_contract.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
18 changes: 14 additions & 4 deletions apps/explorer/lib/explorer/chain/smart_contract/proxy.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
53 changes: 30 additions & 23 deletions apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1967.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down Expand Up @@ -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::binary-size(42)>> ->
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::binary-size(42)>> ->
implementation_address

_ ->
nil
_ ->
nil
end
else
nil
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -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}

_ ->
Expand Down
111 changes: 104 additions & 7 deletions apps/explorer/test/explorer/chain/smart_contract/proxy_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -432,7 +534,7 @@ defmodule Explorer.Chain.SmartContract.ProxyTest do
]
},
_options ->
{:ok, "0x000000000000000000000000" <> beacon_contract_address_hash_string}
{:ok, response}
end
)
|> expect(
Expand All @@ -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"
]
}
Expand All @@ -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

0 comments on commit f8092ac

Please sign in to comment.