-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, <>} -> 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 = <<<<<>/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 <> -> 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 <> -> 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 => <>, 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 => <>, 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, []).