Current section
Files
Jump to
Current section
Files
src/gleeth@ethereum@abi@decode.erl
-module(gleeth@ethereum@abi@decode).-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).-define(FILEPATH, "src/gleeth/ethereum/abi/decode.gleam").-export([decode/2, decode_single/2, decode_calldata/2, decode_function_input/2, decode_function_output/2, decode_revert_with_abi/2, decode_revert/1]).-export_type([decoded_calldata/0, decoded_revert/0]).-if(?OTP_RELEASE >= 27).-define(MODULEDOC(Str), -moduledoc(Str)).-define(DOC(Str), -doc(Str)).-else.-define(MODULEDOC(Str), -compile([])).-define(DOC(Str), -compile([])).-endif.-type decoded_calldata() :: {decoded_calldata, binary(), list(gleeth@ethereum@abi@types:abi_value())}.-type decoded_revert() :: {revert_string, binary()} | {revert_panic, integer()} | {revert_custom_error, binary(), list(gleeth@ethereum@abi@types:abi_value())} | {revert_unknown, bitstring()}.-file("src/gleeth/ethereum/abi/decode.gleam", 237).?DOC(" Read `length` bytes from data starting at offset.\n").-spec read_bytes_at(bitstring(), integer(), integer()) -> {ok, bitstring()} | {error, gleeth@ethereum@abi@types:abi_error()}.read_bytes_at(Data, Offset, Length) -> case gleam_stdlib:bit_array_slice(Data, Offset, Length) of {ok, Bytes} -> {ok, Bytes}; {error, _} -> {error, {decode_error, <<<<<<<<<<"Data too short: need "/utf8, (erlang:integer_to_binary(Length))/binary>>/binary, " bytes at offset "/utf8>>/binary, (erlang:integer_to_binary(Offset))/binary>>/binary, ", have "/utf8>>/binary, (erlang:integer_to_binary(erlang:byte_size(Data)))/binary>>}} end.-file("src/gleeth/ethereum/abi/decode.gleam", 122).-spec decode_address_at(bitstring(), integer()) -> {ok, gleeth@ethereum@abi@types:abi_value()} | {error, gleeth@ethereum@abi@types:abi_error()}.decode_address_at(Data, Offset) -> gleam@result:'try'( read_bytes_at(Data, Offset, 32), fun(Slot) -> case gleam_stdlib:bit_array_slice(Slot, 12, 20) of {ok, Addr_bytes} -> Hex_str = <<"0x"/utf8, (string:lowercase( gleam_stdlib:base16_encode(Addr_bytes) ))/binary>>, {ok, {address_value, Hex_str}}; {error, _} -> {error, {decode_error, <<"Failed to extract address bytes"/utf8>>}} end end ).-file("src/gleeth/ethereum/abi/decode.gleam", 140).-spec decode_fixed_bytes_at(bitstring(), integer(), integer()) -> {ok, gleeth@ethereum@abi@types:abi_value()} | {error, gleeth@ethereum@abi@types:abi_error()}.decode_fixed_bytes_at(Data, Offset, Size) -> gleam@result:'try'( read_bytes_at(Data, Offset, 32), fun(Slot) -> case gleam_stdlib:bit_array_slice(Slot, 0, Size) of {ok, Bytes} -> {ok, {fixed_bytes_value, Bytes}}; {error, _} -> {error, {decode_error, <<"Failed to extract fixed bytes"/utf8>>}} end end ).-file("src/gleeth/ethereum/abi/decode.gleam", 257).?DOC(" Convert a BitArray to an unsigned integer (big-endian).\n").-spec bytes_to_uint(bitstring(), integer(), integer()) -> integer().bytes_to_uint(Data, Index, Acc) -> case gleam_stdlib:bit_array_slice(Data, Index, 1) of {ok, <<Byte:8>>} -> bytes_to_uint(Data, Index + 1, erlang:'bsl'(Acc, 8) + Byte); _ -> Acc end.-file("src/gleeth/ethereum/abi/decode.gleam", 231).?DOC(" Read 32 bytes at the given offset and interpret as a big-endian unsigned int.\n").-spec read_uint256_at(bitstring(), integer()) -> {ok, integer()} | {error, gleeth@ethereum@abi@types:abi_error()}.read_uint256_at(Data, Offset) -> gleam@result:'try'( read_bytes_at(Data, Offset, 32), fun(Slot) -> {ok, bytes_to_uint(Slot, 0, 0)} end ).-file("src/gleeth/ethereum/abi/decode.gleam", 94).-spec decode_uint_at(bitstring(), integer(), integer()) -> {ok, gleeth@ethereum@abi@types:abi_value()} | {error, gleeth@ethereum@abi@types:abi_error()}.decode_uint_at(Data, Offset, Bit_size) -> gleam@result:'try'( read_uint256_at(Data, Offset), fun(Raw) -> Mask = erlang:'bsl'(1, Bit_size) - 1, {ok, {uint_value, erlang:'band'(Raw, Mask)}} end ).-file("src/gleeth/ethereum/abi/decode.gleam", 105).-spec decode_int_at(bitstring(), integer(), integer()) -> {ok, gleeth@ethereum@abi@types:abi_value()} | {error, gleeth@ethereum@abi@types:abi_error()}.decode_int_at(Data, Offset, Bit_size) -> gleam@result:'try'( read_uint256_at(Data, Offset), fun(Raw) -> Mask = erlang:'bsl'(1, Bit_size) - 1, Masked = erlang:'band'(Raw, Mask), Sign_bit = erlang:'bsl'(1, Bit_size - 1), Value = case erlang:'band'(Masked, Sign_bit) /= 0 of true -> Masked - erlang:'bsl'(1, Bit_size); false -> Masked end, {ok, {int_value, Value}} end ).-file("src/gleeth/ethereum/abi/decode.gleam", 135).-spec decode_bool_at(bitstring(), integer()) -> {ok, gleeth@ethereum@abi@types:abi_value()} | {error, gleeth@ethereum@abi@types:abi_error()}.decode_bool_at(Data, Offset) -> gleam@result:'try'( read_uint256_at(Data, Offset), fun(Raw) -> {ok, {bool_value, Raw /= 0}} end ).-file("src/gleeth/ethereum/abi/decode.gleam", 189).-spec decode_bytes_at(bitstring(), integer()) -> {ok, gleeth@ethereum@abi@types:abi_value()} | {error, gleeth@ethereum@abi@types:abi_error()}.decode_bytes_at(Data, Offset) -> gleam@result:'try'( read_uint256_at(Data, Offset), fun(Length) -> gleam@result:'try'( read_bytes_at(Data, Offset + 32, Length), fun(Bytes) -> {ok, {bytes_value, Bytes}} end ) end ).-file("src/gleeth/ethereum/abi/decode.gleam", 195).-spec decode_string_at(bitstring(), integer()) -> {ok, gleeth@ethereum@abi@types:abi_value()} | {error, gleeth@ethereum@abi@types:abi_error()}.decode_string_at(Data, Offset) -> gleam@result:'try'( read_uint256_at(Data, Offset), fun(Length) -> gleam@result:'try'( read_bytes_at(Data, Offset + 32, Length), fun(Bytes) -> case gleam@bit_array:to_string(Bytes) of {ok, S} -> {ok, {string_value, S}}; {error, _} -> {error, {decode_error, <<"Invalid UTF-8 in string"/utf8>>}} end end ) end ).-file("src/gleeth/ethereum/abi/decode.gleam", 452).?DOC(" Compute 4-byte selector from function name and parameter types.\n").-spec compute_selector(binary(), list(gleeth@ethereum@abi@types:abi_type())) -> {ok, bitstring()} | {error, gleeth@ethereum@abi@types:abi_error()}.compute_selector(Name, Param_types) -> Sig = <<<<<<Name/binary, "("/utf8>>/binary, (gleam@string:join( gleam@list:map( Param_types, fun gleeth@ethereum@abi@types:to_string/1 ), <<","/utf8>> ))/binary>>/binary, ")"/utf8>>, Hash = gleeth@crypto@keccak:keccak256_binary(gleam_stdlib:identity(Sig)), case gleam_stdlib:bit_array_slice(Hash, 0, 4) of {ok, Sel} -> {ok, Sel}; {error, _} -> {error, {decode_error, <<"Failed to compute selector"/utf8>>}} end.-file("src/gleeth/ethereum/abi/decode.gleam", 427).?DOC(" Match a 4-byte selector against ABI entries.\n").-spec match_selector(bitstring(), list(gleeth@ethereum@abi@json:abi_entry())) -> {ok, {binary(), list(gleeth@ethereum@abi@types:abi_type())}} | {error, gleeth@ethereum@abi@types:abi_error()}.match_selector(Selector, Entries) -> case Entries of [] -> {error, {decode_error, <<"No matching function for selector "/utf8, (gleeth@utils@hex:encode(Selector))/binary>>}}; [Entry | Rest] -> case Entry of {function_entry, Name, Inputs, _, _} -> Param_types = gleam@list:map( Inputs, fun(P) -> erlang:element(3, P) end ), case compute_selector(Name, Param_types) of {ok, Computed} when Computed =:= Selector -> {ok, {Name, Param_types}}; _ -> match_selector(Selector, Rest) end; _ -> match_selector(Selector, Rest) end end.-file("src/gleeth/ethereum/abi/decode.gleam", 493).?DOC(" Parse a comma-separated list of type strings.\n").-spec parse_type_list(binary()) -> {ok, list(gleeth@ethereum@abi@types:abi_type())} | {error, gleeth@ethereum@abi@types:abi_error()}.parse_type_list(Types_str) -> Type_strings = gleam@string:split(Types_str, <<","/utf8>>), gleam@list:try_map( Type_strings, fun(S) -> gleeth@ethereum@abi@type_parser:parse(gleam@string:trim(S)) end ).-file("src/gleeth/ethereum/abi/decode.gleam", 469).?DOC(" Parse a function signature like \"transfer(address,uint256)\" into name + types.\n").-spec parse_function_signature(binary()) -> {ok, {binary(), list(gleeth@ethereum@abi@types:abi_type())}} | {error, gleeth@ethereum@abi@types:abi_error()}.parse_function_signature(Signature) -> case gleam@string:split_once(Signature, <<"("/utf8>>) of {ok, {Name, Rest}} -> Types_str = case gleam_stdlib:string_ends_with(Rest, <<")"/utf8>>) of true -> gleam@string:drop_end(Rest, 1); false -> Rest end, case Types_str of <<""/utf8>> -> {ok, {Name, []}}; _ -> gleam@result:'try'( parse_type_list(Types_str), fun(Type_list) -> {ok, {Name, Type_list}} end ) end; {error, _} -> {error, {decode_error, <<"Invalid function signature: "/utf8, Signature/binary>>}} end.-file("src/gleeth/ethereum/abi/decode.gleam", 72).-spec decode_static_at( gleeth@ethereum@abi@types:abi_type(), bitstring(), integer()) -> {ok, gleeth@ethereum@abi@types:abi_value()} | {error, gleeth@ethereum@abi@types:abi_error()}.decode_static_at(T, Data, Offset) -> case T of {uint, Size} -> decode_uint_at(Data, Offset, Size); {int, Size@1} -> decode_int_at(Data, Offset, Size@1); address -> decode_address_at(Data, Offset); bool -> decode_bool_at(Data, Offset); {fixed_bytes, Size@2} -> decode_fixed_bytes_at(Data, Offset, Size@2); {fixed_array, Element_type, Size@3} -> decode_static_fixed_array_at(Element_type, Size@3, Data, Offset); {tuple, Element_types} -> gleam@result:'try'( decode_tuple_at(Element_types, Data, Offset), fun(Values) -> {ok, {tuple_value, Values}} end ); _ -> {error, {decode_error, <<"Expected static type, got dynamic"/utf8>>}} end.-file("src/gleeth/ethereum/abi/decode.gleam", 153).-spec decode_static_fixed_array_at( gleeth@ethereum@abi@types:abi_type(), integer(), bitstring(), integer()) -> {ok, gleeth@ethereum@abi@types:abi_value()} | {error, gleeth@ethereum@abi@types:abi_error()}.decode_static_fixed_array_at(Element_type, Count, Data, Offset) -> Element_types = gleam@list:repeat(Element_type, Count), gleam@result:'try'( decode_tuple_at(Element_types, Data, Offset), fun(Values) -> {ok, {array_value, Values}} end ).-file("src/gleeth/ethereum/abi/decode.gleam", 39).?DOC( " Decode a tuple from data starting at a given offset.\n" " The tuple's head starts at `base_offset` within `data`.\n").-spec decode_tuple_at( list(gleeth@ethereum@abi@types:abi_type()), bitstring(), integer()) -> {ok, list(gleeth@ethereum@abi@types:abi_value())} | {error, gleeth@ethereum@abi@types:abi_error()}.decode_tuple_at(Type_list, Data, Base_offset) -> gleam@result:'try'( gleam@list:try_fold( Type_list, {[], Base_offset}, fun(Acc, T) -> {Vals, Head_pos} = Acc, Slot_size = gleeth@ethereum@abi@types:head_size(T), case gleeth@ethereum@abi@types:is_dynamic(T) of false -> gleam@result:'try'( decode_static_at(T, Data, Head_pos), fun(Value) -> {ok, {[Value | Vals], Head_pos + Slot_size}} end ); true -> gleam@result:'try'( read_uint256_at(Data, Head_pos), fun(Offset) -> Abs_offset = Base_offset + Offset, gleam@result:'try'( decode_dynamic_at(T, Data, Abs_offset), fun(Value@1) -> {ok, {[Value@1 | Vals], Head_pos + 32}} end ) end ) end end ), fun(_use0) -> {Values, _} = _use0, {ok, lists:reverse(Values)} end ).-file("src/gleeth/ethereum/abi/decode.gleam", 14).?DOC( " Decode ABI-encoded data given a list of expected types.\n" " This is the top-level decoder for function return values or event data.\n").-spec decode(list(gleeth@ethereum@abi@types:abi_type()), bitstring()) -> {ok, list(gleeth@ethereum@abi@types:abi_value())} | {error, gleeth@ethereum@abi@types:abi_error()}.decode(Type_list, Data) -> decode_tuple_at(Type_list, Data, 0).-file("src/gleeth/ethereum/abi/decode.gleam", 22).?DOC(" Decode a single value of a known type from ABI-encoded data.\n").-spec decode_single(gleeth@ethereum@abi@types:abi_type(), bitstring()) -> {ok, gleeth@ethereum@abi@types:abi_value()} | {error, gleeth@ethereum@abi@types:abi_error()}.decode_single(Type_, Data) -> gleam@result:'try'(decode([Type_], Data), fun(Values) -> case Values of [Value] -> {ok, Value}; _ -> {error, {decode_error, <<"Expected exactly one value"/utf8>>}} end end).-file("src/gleeth/ethereum/abi/decode.gleam", 204).-spec decode_dynamic_array_at( gleeth@ethereum@abi@types:abi_type(), bitstring(), integer()) -> {ok, gleeth@ethereum@abi@types:abi_value()} | {error, gleeth@ethereum@abi@types:abi_error()}.decode_dynamic_array_at(Element_type, Data, Offset) -> gleam@result:'try'( read_uint256_at(Data, Offset), fun(Count) -> Element_types = gleam@list:repeat(Element_type, Count), gleam@result:'try'( decode_tuple_at(Element_types, Data, Offset + 32), fun(Values) -> {ok, {array_value, Values}} end ) end ).-file("src/gleeth/ethereum/abi/decode.gleam", 215).-spec decode_dynamic_fixed_array_at( gleeth@ethereum@abi@types:abi_type(), integer(), bitstring(), integer()) -> {ok, gleeth@ethereum@abi@types:abi_value()} | {error, gleeth@ethereum@abi@types:abi_error()}.decode_dynamic_fixed_array_at(Element_type, Count, Data, Offset) -> Element_types = gleam@list:repeat(Element_type, Count), gleam@result:'try'( decode_tuple_at(Element_types, Data, Offset), fun(Values) -> {ok, {array_value, Values}} end ).-file("src/gleeth/ethereum/abi/decode.gleam", 169).-spec decode_dynamic_at( gleeth@ethereum@abi@types:abi_type(), bitstring(), integer()) -> {ok, gleeth@ethereum@abi@types:abi_value()} | {error, gleeth@ethereum@abi@types:abi_error()}.decode_dynamic_at(T, Data, Offset) -> case T of bytes -> decode_bytes_at(Data, Offset); string -> decode_string_at(Data, Offset); {array, Element_type} -> decode_dynamic_array_at(Element_type, Data, Offset); {fixed_array, Element_type@1, Size} -> decode_dynamic_fixed_array_at(Element_type@1, Size, Data, Offset); {tuple, Element_types} -> gleam@result:'try'( decode_tuple_at(Element_types, Data, Offset), fun(Values) -> {ok, {tuple_value, Values}} end ); _ -> {error, {decode_error, <<"Expected dynamic type"/utf8>>}} end.-file("src/gleeth/ethereum/abi/decode.gleam", 281).?DOC( " Decode calldata by matching the 4-byte selector against parsed ABI entries.\n" " Returns the function name and decoded argument values.\n").-spec decode_calldata(binary(), list(gleeth@ethereum@abi@json:abi_entry())) -> {ok, decoded_calldata()} | {error, gleeth@ethereum@abi@types:abi_error()}.decode_calldata(Calldata_hex, Entries) -> gleam@result:'try'( begin _pipe = gleeth@utils@hex:decode(Calldata_hex), gleam@result:map_error( _pipe, fun(_) -> {decode_error, <<"Invalid calldata hex"/utf8>>} end ) end, fun(Calldata_bytes) -> case Calldata_bytes of <<Selector:32/bitstring, Params_data/bitstring>> -> gleam@result:'try'( match_selector(Selector, Entries), fun(_use0) -> {Name, Param_types} = _use0, gleam@result:'try'( decode(Param_types, Params_data), fun(Values) -> {ok, {decoded_calldata, Name, Values}} end ) end ); _ -> {error, {decode_error, <<"Calldata too short (need at least 4 bytes)"/utf8>>}} end end ).-file("src/gleeth/ethereum/abi/decode.gleam", 301).?DOC( " Decode calldata given a function signature string like \"transfer(address,uint256)\".\n" " Computes the selector from the signature and decodes the parameters.\n").-spec decode_function_input(binary(), binary()) -> {ok, list(gleeth@ethereum@abi@types:abi_value())} | {error, gleeth@ethereum@abi@types:abi_error()}.decode_function_input(Signature, Calldata_hex) -> gleam@result:'try'( parse_function_signature(Signature), fun(_use0) -> {Name, Param_types} = _use0, gleam@result:'try'( compute_selector(Name, Param_types), fun(Expected_selector) -> gleam@result:'try'( begin _pipe = gleeth@utils@hex:decode(Calldata_hex), gleam@result:map_error( _pipe, fun(_) -> {decode_error, <<"Invalid calldata hex"/utf8>>} end ) end, fun(Calldata_bytes) -> case Calldata_bytes of <<Actual_selector:32/bitstring, Params_data/bitstring>> -> case Actual_selector =:= Expected_selector of true -> decode(Param_types, Params_data); false -> {error, {decode_error, <<<<<<"Selector mismatch: expected "/utf8, (gleeth@utils@hex:encode( Expected_selector ))/binary>>/binary, ", got "/utf8>>/binary, (gleeth@utils@hex:encode( Actual_selector ))/binary>>}} end; _ -> {error, {decode_error, <<"Calldata too short (need at least 4 bytes)"/utf8>>}} end end ) end ) end ).-file("src/gleeth/ethereum/abi/decode.gleam", 330).?DOC( " Decode the return value of a function given its output type signature.\n" " For example: decode_function_output(\"(uint256)\", result_hex)\n").-spec decode_function_output(binary(), binary()) -> {ok, list(gleeth@ethereum@abi@types:abi_value())} | {error, gleeth@ethereum@abi@types:abi_error()}.decode_function_output(Output_types_sig, Result_hex) -> gleam@result:'try'( parse_type_list(Output_types_sig), fun(Type_list) -> gleam@result:'try'( begin _pipe = gleeth@utils@hex:decode(Result_hex), gleam@result:map_error( _pipe, fun(_) -> {decode_error, <<"Invalid result hex"/utf8>>} end ) end, fun(Result_bytes) -> decode(Type_list, Result_bytes) end ) end ).-file("src/gleeth/ethereum/abi/decode.gleam", 402).-spec decode_custom_revert( bitstring(), list(gleeth@ethereum@abi@json:abi_entry())) -> {ok, decoded_revert()} | {error, gleeth@ethereum@abi@types:abi_error()}.decode_custom_revert(Revert_bytes, Entries) -> Selector@1 = case gleam_stdlib:bit_array_slice(Revert_bytes, 0, 4) of {ok, Selector} -> Selector; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <<?FILEPATH/utf8>>, module => <<"gleeth/ethereum/abi/decode"/utf8>>, function => <<"decode_custom_revert"/utf8>>, line => 406, value => _assert_fail, start => 13713, 'end' => 13774, pattern_start => 13724, pattern_end => 13736}) end, Params_start = 4, Data_len = erlang:byte_size(Revert_bytes) - Params_start, Params_data@1 = case gleam_stdlib:bit_array_slice( Revert_bytes, Params_start, Data_len ) of {ok, Params_data} -> Params_data; _assert_fail@1 -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <<?FILEPATH/utf8>>, module => <<"gleeth/ethereum/abi/decode"/utf8>>, function => <<"decode_custom_revert"/utf8>>, line => 409, value => _assert_fail@1, start => 13866, 'end' => 13952, pattern_start => 13877, pattern_end => 13892}) end, case match_selector(Selector@1, Entries) of {ok, {Name, Param_types}} -> gleam@result:'try'( decode(Param_types, Params_data@1), fun(Values) -> {ok, {revert_custom_error, Name, Values}} end ); {error, _} -> {ok, {revert_unknown, Revert_bytes}} end.-file("src/gleeth/ethereum/abi/decode.gleam", 365).?DOC(" Decode revert data, matching custom errors against provided ABI entries.\n").-spec decode_revert_with_abi( binary(), list(gleeth@ethereum@abi@json:abi_entry())) -> {ok, decoded_revert()} | {error, gleeth@ethereum@abi@types:abi_error()}.decode_revert_with_abi(Revert_hex, Entries) -> gleam@result:'try'( begin _pipe = gleeth@utils@hex:decode(Revert_hex), gleam@result:map_error( _pipe, fun(_) -> {decode_error, <<"Invalid revert hex"/utf8>>} end ) end, fun(Revert_bytes) -> case Revert_bytes of <<>> -> {ok, {revert_unknown, <<>>}}; <<16#08, 16#c3, 16#79, 16#a0, Params/bitstring>> -> gleam@result:'try'( decode([string], Params), fun(Values) -> case Values of [{string_value, Msg}] -> {ok, {revert_string, Msg}}; _ -> {ok, {revert_unknown, Revert_bytes}} end end ); <<16#4e, 16#48, 16#7b, 16#71, Params@1/bitstring>> -> gleam@result:'try'( decode([{uint, 256}], Params@1), fun(Values@1) -> case Values@1 of [{uint_value, Code}] -> {ok, {revert_panic, Code}}; _ -> {ok, {revert_unknown, Revert_bytes}} end end ); <<_:32/bitstring, _/bitstring>> -> case Entries of [] -> {ok, {revert_unknown, Revert_bytes}}; _ -> decode_custom_revert(Revert_bytes, Entries) end; _ -> {ok, {revert_unknown, Revert_bytes}} end end ).-file("src/gleeth/ethereum/abi/decode.gleam", 360).?DOC( " Decode revert data. Handles standard Error(string) with selector 0x08c379a0,\n" " Panic(uint256) with selector 0x4e487b71, and custom errors if an ABI is provided.\n").-spec decode_revert(binary()) -> {ok, decoded_revert()} | {error, gleeth@ethereum@abi@types:abi_error()}.decode_revert(Revert_hex) -> decode_revert_with_abi(Revert_hex, []).