Skip to content

Commit

Permalink
chore: Arbitrum claiming enhancements (blockscout#11552)
Browse files Browse the repository at this point in the history
* Added `completion_transaction_hash` to the `Withdrawal` type

* Fetching ERC20 token properties from L1 chain

* Converting `callvalue` and `token.amount` values to the String.t() for withdrawals list

* Moving ERC20 token fetching into the separated module

* Fix formatting issue

* Refactoring code

* Code formatting issue

* Improve module documentation

* Fix logger issue

* Fixed code review issues

* Fixed test issue & warnings

* Code formatting

* Removing address checksum in EthereumJSONRPC.Arbitrum.value_to_address/1

* Update apps/explorer/lib/explorer/arbitrum/withdraw.ex

Co-authored-by: Victor Baranov <baranov.viktor.27@gmail.com>

* Added TODO marks in `Explorer.Token.MetadataRetriever` and `Explorer.Chain.BridgedToken`

* Added TODO for researching `ERC20.symbol` method

---------

Co-authored-by: Victor Baranov <baranov.viktor.27@gmail.com>
  • Loading branch information
EvgenKor and vbaranov authored Jan 22, 2025
1 parent 7e6c45a commit abdedaf
Show file tree
Hide file tree
Showing 7 changed files with 260 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ defmodule BlockScoutWeb.API.V2.ArbitrumView do
end

@doc """
Function to render GET requests to `/api/v2/arbitrum/messages/from-rollup/:msg_id/proof` endpoint.
Function to render GET requests to `/api/v2/arbitrum/messages/withdrawals/:transaction_hash` endpoint.
"""
def render("arbitrum_withdrawals.json", %{withdrawals: withdrawals}) do
withdrawals_out =
Expand All @@ -93,9 +93,14 @@ defmodule BlockScoutWeb.API.V2.ArbitrumView do
"arb_block_number" => withdraw.arb_block_number,
"eth_block_number" => withdraw.eth_block_number,
"l2_timestamp" => withdraw.l2_timestamp,
"callvalue" => withdraw.callvalue,
"callvalue" => Integer.to_string(withdraw.callvalue),
"data" => withdraw.data,
"token" => withdraw.token
"token" =>
case withdraw.token do
%{} -> Map.update!(withdraw.token, :amount, &Integer.to_string/1)
_ -> nil
end,
"completion_transaction_hash" => withdraw.completion_transaction_hash
}
end)

Expand Down
12 changes: 10 additions & 2 deletions apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/arbitrum.ex
Original file line number Diff line number Diff line change
Expand Up @@ -247,9 +247,17 @@ defmodule EthereumJSONRPC.Arbitrum do
|> TypeDecoder.decode_raw(types)
end

# Casting value into the Ethereum address (hex-string, 0x-prefixed)
@doc """
Casts a value into an Ethereum address (hex-string, 0x-prefixed, not checksummed).
## Parameters
- value: `0x` prefixed hex string or byte array to be cast into an Ethereum address.
## Returns
- A string representing the Ethereum address in hex format, prefixed with '0x'
"""
@spec value_to_address(binary()) :: String.t()
defp value_to_address(value) do
def value_to_address(value) do
hex =
cond do
is_binary(value) and String.starts_with?(value, "0x") -> String.trim_leading(value, "0x")
Expand Down
148 changes: 148 additions & 0 deletions apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/erc20.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
defmodule EthereumJSONRPC.ERC20 do
@moduledoc """
Provides ability to interact with ERC20 token contracts directly.
This module is primarily used in the Arbitrum claiming process to fetch
L1 token information for withdrawal transactions.
Currently supports fetching token properties like name, symbol and decimals
through direct contract calls.
## Examples
iex> EthereumJSONRPC.ERC20.fetch_token_properties(
...> "0xdAC17F958D2ee523a2206206994597C13D831ec7",
...> [:name, :symbol],
...> json_rpc_named_arguments
...> )
%{
name: "Tether USD",
symbol: "USDT"
}
For more information about the ERC20 standard, see:
https://eips.ethereum.org/EIPS/eip-20
"""

require Logger

# decimals()
@selector_decimals "313ce567"
# name()
@selector_name "06fdde03"
# symbol()
@selector_symbol "95d89b41"
@erc20_contract_abi [
%{
"inputs" => [],
"name" => "decimals",
"outputs" => [
%{
"internalType" => "uint8",
"name" => "",
"type" => "uint8"
}
],
"stateMutability" => "view",
"type" => "function"
},
%{
"inputs" => [],
"name" => "name",
"outputs" => [
%{
"internalType" => "string",
"name" => "",
"type" => "string"
}
],
"stateMutability" => "view",
"type" => "function"
},
%{
"inputs" => [],
"name" => "symbol",
"outputs" => [
%{
"internalType" => "string",
"name" => "",
# TODO: Research the compatibility of this ABI
# with tokens that return bytes32 for the symbol method
"type" => "string"
}
],
"stateMutability" => "view",
"type" => "function"
}
]

@doc """
Retrieve minimal ERC20 token properties (name, symbol, decimals) from the contract needed
for display purposes.
## Parameters
- `token_address`: The address of the token's smart contract.
- `properties`: A list of token properties to be requested.
- `json_rpc_named_arguments`: Configuration parameters for the JSON RPC connection.
## Returns
A map with the requested fields containing associated values or nil in case of error.
"""
@spec fetch_token_properties(
EthereumJSONRPC.address(),
[:decimals | :name | :symbol],
EthereumJSONRPC.json_rpc_named_arguments()
) ::
%{
optional(:decimals) => non_neg_integer() | nil,
optional(:name) => binary() | nil,
optional(:symbol) => binary() | nil
}
def fetch_token_properties(
token_address,
properties \\ [:decimals, :name, :symbol],
json_rpc_named_arguments
) do
method_ids =
properties
|> map_properties_to_methods()

method_ids
|> Enum.map(fn method_id ->
%{
contract_address: token_address,
method_id: method_id,
args: []
}
end)
|> EthereumJSONRPC.execute_contract_functions(@erc20_contract_abi, json_rpc_named_arguments)
|> Enum.zip(method_ids)
|> Enum.reduce(%{}, fn
{{:ok, [response]}, method_id}, retval ->
Map.put(retval, atomized_erc20_selector(method_id), response)

{{:error, reason}, method_id}, retval ->
Logger.error("[EthereumJSONRPC.ERC20] Failed to fetch token property! \
token_address: #{inspect(token_address)}, \
method_id: #{inspect(method_id)}, \
error: #{inspect(reason)}")

Map.put(retval, atomized_erc20_selector(method_id), nil)
end)
end

# Maps the token properties to the corresponding method selectors in the ERC20 contract
@spec map_properties_to_methods([:decimals | :name | :symbol]) :: [String.t()]
defp map_properties_to_methods(properties) do
Enum.map(properties, fn
:decimals -> @selector_decimals
:name -> @selector_name
:symbol -> @selector_symbol
end)
end

# Converts the selector to the associated token property atom
@spec atomized_erc20_selector(<<_::64>>) :: atom()
defp atomized_erc20_selector(@selector_decimals), do: :decimals
defp atomized_erc20_selector(@selector_name), do: :name
defp atomized_erc20_selector(@selector_symbol), do: :symbol
end
Loading

0 comments on commit abdedaf

Please sign in to comment.